~launchpad-pqm/launchpad/devel

« back to all changes in this revision

Viewing changes to lib/lp/soyuz/interfaces/archive.py

  • Committer: Launchpad Patch Queue Manager
  • Date: 2011-06-10 19:46:57 UTC
  • mfrom: (13137.1.21 ppa-api)
  • Revision ID: launchpad@pqm.canonical.com-20110610194657-qpovv3hj69uyqpw8
[r=gmb][bug=776444,
 776449] Provide PPA dependency controls via the web service.

Show diffs side-by-side

added added

removed removed

Lines of Context:
32
32
    'IDistributionArchive',
33
33
    'InsufficientUploadRights',
34
34
    'InvalidComponent',
 
35
    'InvalidExternalDependencies',
35
36
    'InvalidPocketForPartnerArchive',
36
37
    'InvalidPocketForPPA',
37
38
    'IPPA',
43
44
    'PocketNotFound',
44
45
    'VersionRequiresName',
45
46
    'default_name_by_purpose',
 
47
    'validate_external_dependencies',
46
48
    ]
47
49
 
 
50
 
 
51
from urlparse import urlparse
 
52
 
48
53
from lazr.enum import DBEnumeratedType
49
54
from lazr.restful.declarations import (
50
55
    call_with,
54
59
    export_read_operation,
55
60
    export_write_operation,
56
61
    exported,
 
62
    operation_for_version,
57
63
    operation_parameters,
58
64
    operation_returns_collection_of,
59
65
    operation_returns_entry,
114
120
 
115
121
class CannotCopy(Exception):
116
122
    """Exception raised when a copy cannot be performed."""
117
 
    webservice_error(400) #Bad request.
 
123
    webservice_error(400)  # Bad request.
118
124
 
119
125
 
120
126
class CannotSwitchPrivacy(Exception):
121
127
    """Raised when switching the privacy of an archive that has
122
128
    publishing records."""
123
 
    webservice_error(400) # Bad request.
 
129
    webservice_error(400)  # Bad request.
124
130
 
125
131
 
126
132
class PocketNotFound(Exception):
127
133
    """Invalid pocket."""
128
 
    webservice_error(400) #Bad request.
 
134
    webservice_error(400)  # Bad request.
129
135
 
130
136
 
131
137
class DistroSeriesNotFound(Exception):
132
138
    """Invalid distroseries."""
133
 
    webservice_error(400) #Bad request.
 
139
    webservice_error(400)  # Bad request.
134
140
 
135
141
 
136
142
class AlreadySubscribed(Exception):
137
143
    """Raised when creating a subscription for a subscribed person."""
138
 
    webservice_error(400) # Bad request.
 
144
    webservice_error(400)  # Bad request.
139
145
 
140
146
 
141
147
class ArchiveNotPrivate(Exception):
142
148
    """Raised when creating an archive subscription for a public archive."""
143
 
    webservice_error(400) # Bad request.
 
149
    webservice_error(400)  # Bad request.
144
150
 
145
151
 
146
152
class NoTokensForTeams(Exception):
147
153
    """Raised when creating a token for a team, rather than a person."""
148
 
    webservice_error(400) # Bad request.
 
154
    webservice_error(400)  # Bad request.
149
155
 
150
156
 
151
157
class ComponentNotFound(Exception):
152
158
    """Invalid source name."""
153
 
    webservice_error(400) #Bad request.
 
159
    webservice_error(400)  # Bad request.
154
160
 
155
161
 
156
162
class InvalidComponent(Exception):
157
163
    """Invalid component name."""
158
 
    webservice_error(400) #Bad request.
 
164
    webservice_error(400)  # Bad request.
159
165
 
160
166
 
161
167
class NoSuchPPA(NameLookupFailed):
162
168
    """Raised when we try to look up an PPA that doesn't exist."""
163
 
    webservice_error(400) #Bad request.
 
169
    webservice_error(400)  # Bad request.
164
170
    _message_prefix = "No such ppa"
165
171
 
166
172
 
167
173
class VersionRequiresName(Exception):
168
174
    """Raised on some queries when version is specified but name is not."""
169
 
    webservice_error(400) # Bad request.
 
175
    webservice_error(400)  # Bad request.
170
176
 
171
177
 
172
178
class CannotRestrictArchitectures(Exception):
175
181
 
176
182
class CannotUploadToArchive(Exception):
177
183
    """A reason for not being able to upload to an archive."""
178
 
    webservice_error(403) # Forbidden.
 
184
    webservice_error(403)  # Forbidden.
179
185
 
180
186
    _fmt = '%(person)s has no upload rights to %(archive)s.'
181
187
 
192
198
 
193
199
class CannotUploadToPocket(Exception):
194
200
    """Returned when a pocket is closed for uploads."""
195
 
    webservice_error(403) # Forbidden.
 
201
    webservice_error(403)  # Forbidden.
196
202
 
197
203
    def __init__(self, distroseries, pocket):
198
204
        Exception.__init__(self,
248
254
        CannotUploadToArchive.__init__(self, archive_name=archive_name)
249
255
 
250
256
 
 
257
class InvalidExternalDependencies(Exception):
 
258
    """Tried to set external dependencies to an invalid value."""
 
259
 
 
260
    webservice_error(400)  # Bad request.
 
261
 
 
262
    def __init__(self, errors):
 
263
        error_msg = 'Invalid external dependencies:\n%s\n' % '\n'.join(errors)
 
264
        super(Exception, self).__init__(self, error_msg)
 
265
        self.errors = errors
 
266
 
 
267
 
251
268
class IArchivePublic(IHasOwner, IPrivacy):
252
269
    """An Archive interface for publicly available operations."""
253
270
    id = Attribute("The archive ID.")
332
349
 
333
350
    distribution = exported(
334
351
        Reference(
335
 
            Interface, # Redefined to IDistribution later.
 
352
            Interface,  # Redefined to IDistribution later.
336
353
            title=_("The distribution that uses or is used by this "
337
354
                    "archive.")))
338
355
 
412
429
            "A delta to apply to all build scores for the archive. Builds "
413
430
            "with a higher score will build sooner."))
414
431
 
415
 
    external_dependencies = Text(
416
 
        title=_("External dependencies"), required=False, readonly=False,
417
 
        description=_(
 
432
    external_dependencies = exported(
 
433
        Text(title=_("External dependencies"), required=False,
 
434
        readonly=False, description=_(
418
435
            "Newline-separated list of repositories to be used to retrieve "
419
436
            "any external build dependencies when building packages in the "
420
437
            "archive, in the format:\n"
422
439
                "[components]\n"
423
440
            "The series variable is replaced with the series name of the "
424
441
            "context build.\n"
425
 
            "NOTE: This is for migration of OEM PPAs only!"))
 
442
            "NOTE: This is for migration of OEM PPAs only!")))
426
443
 
427
444
    enabled_restricted_families = CollectionField(
428
445
            title=_("Enabled restricted families"),
520
537
            records.
521
538
        """
522
539
 
523
 
    def removeArchiveDependency(dependency):
524
 
        """Remove the `IArchiveDependency` record for the given dependency.
525
 
 
526
 
        :param dependency: is an `IArchive` object.
527
 
        """
528
 
 
529
 
    def addArchiveDependency(dependency, pocket, component=None):
530
 
        """Record an archive dependency record for the context archive.
531
 
 
532
 
        :param dependency: is an `IArchive` object.
533
 
        :param pocket: is an `PackagePublishingPocket` enum.
534
 
        :param component: is an optional `IComponent` object, if not given
535
 
            the archive dependency will be tied to the component used
536
 
            for a corresponding source in primary archive.
537
 
 
538
 
        :raise: `ArchiveDependencyError` if given 'dependency' does not fit
539
 
            the context archive.
540
 
        :return: a `IArchiveDependency` object targeted to the context
541
 
            `IArchive` requiring 'dependency' `IArchive`.
542
 
        """
543
 
 
544
540
    def getPermissions(person, item, perm_type):
545
541
        """Get the `IArchivePermission` record with the supplied details.
546
542
 
892
888
        :return: True if the person is allowed to upload the source package.
893
889
        """
894
890
 
895
 
    num_pkgs_building = Attribute("Tuple of packages building and waiting to build")
 
891
    num_pkgs_building = Attribute(
 
892
        "Tuple of packages building and waiting to build")
896
893
 
897
894
    def getSourcePackageReleases(build_status=None):
898
895
        """Return the releases for this archive.
944
941
    dependencies = exported(
945
942
        CollectionField(
946
943
            title=_("Archive dependencies recorded for this archive."),
947
 
            value_type=Reference(schema=Interface), #Really IArchiveDependency
 
944
            value_type=Reference(schema=Interface),
 
945
            # Really IArchiveDependency
948
946
            readonly=True))
949
947
 
950
948
    description = exported(
1111
1109
        """
1112
1110
 
1113
1111
    @operation_parameters(
1114
 
        dependency=Reference(schema=Interface)) #Really IArchive. See below.
1115
 
    @operation_returns_entry(schema=Interface) #Really IArchiveDependency.
 
1112
        dependency=Reference(schema=Interface))  # Really IArchive. See below.
 
1113
    @operation_returns_entry(schema=Interface)  # Really IArchiveDependency.
1116
1114
    @export_read_operation()
1117
1115
    def getArchiveDependency(dependency):
1118
1116
        """Return the `IArchiveDependency` object for the given dependency.
1233
1231
        source_names=List(
1234
1232
            title=_("Source package names"),
1235
1233
            value_type=TextLine()),
1236
 
        from_archive=Reference(schema=Interface), #Really IArchive, see below
 
1234
        from_archive=Reference(schema=Interface),
 
1235
        #Really IArchive, see below
1237
1236
        to_pocket=TextLine(title=_("Pocket name")),
1238
1237
        to_series=TextLine(title=_("Distroseries name"), required=False),
1239
1238
        include_binaries=Bool(
1275
1274
    @operation_parameters(
1276
1275
        source_name=TextLine(title=_("Source package name")),
1277
1276
        version=TextLine(title=_("Version")),
1278
 
        from_archive=Reference(schema=Interface), #Really IArchive, see below
 
1277
        from_archive=Reference(schema=Interface),
 
1278
        # Really IArchive, see below
1279
1279
        to_pocket=TextLine(title=_("Pocket name")),
1280
1280
        to_series=TextLine(title=_("Distroseries name"), required=False),
1281
1281
        include_binaries=Bool(
1314
1314
 
1315
1315
    @call_with(registrant=REQUEST_USER)
1316
1316
    @operation_parameters(
1317
 
        subscriber = PublicPersonChoice(
 
1317
        subscriber=PublicPersonChoice(
1318
1318
            title=_("Subscriber"),
1319
1319
            required=True,
1320
1320
            vocabulary='ValidPersonOrTeam',
1458
1458
        processed.
1459
1459
        """
1460
1460
 
 
1461
    def addArchiveDependency(dependency, pocket, component=None):
 
1462
        """Record an archive dependency record for the context archive.
 
1463
 
 
1464
        :param dependency: is an `IArchive` object.
 
1465
        :param pocket: is an `PackagePublishingPocket` enum.
 
1466
        :param component: is an optional `IComponent` object, if not given
 
1467
            the archive dependency will be tied to the component used
 
1468
            for a corresponding source in primary archive.
 
1469
 
 
1470
        :raise: `ArchiveDependencyError` if given 'dependency' does not fit
 
1471
            the context archive.
 
1472
        :return: a `IArchiveDependency` object targeted to the context
 
1473
            `IArchive` requiring 'dependency' `IArchive`.
 
1474
        """
 
1475
 
 
1476
    @operation_parameters(
 
1477
        dependency=Reference(schema=Interface, required=True),
 
1478
        #  Really IArchive
 
1479
        pocket=Choice(
 
1480
            title=_("Pocket"),
 
1481
            description=_("The pocket into which this entry is published"),
 
1482
            # Really PackagePublishingPocket.
 
1483
            vocabulary=DBEnumeratedType,
 
1484
            required=True),
 
1485
        component=TextLine(title=_("Component"), required=False),
 
1486
        )
 
1487
    @export_operation_as('addArchiveDependency')
 
1488
    @export_factory_operation(Interface, [])  # Really IArchiveDependency
 
1489
    @operation_for_version('devel')
 
1490
    def _addArchiveDependency(dependency, pocket, component=None):
 
1491
        """Record an archive dependency record for the context archive.
 
1492
 
 
1493
        :param dependency: is an `IArchive` object.
 
1494
        :param pocket: is an `PackagePublishingPocket` enum.
 
1495
        :param component: is the name of a component.  If not given,
 
1496
            the archive dependency will be tied to the component used
 
1497
            for a corresponding source in primary archive.
 
1498
 
 
1499
        :raise: `ArchiveDependencyError` if given 'dependency' does not fit
 
1500
            the context archive.
 
1501
        :return: a `IArchiveDependency` object targeted to the context
 
1502
            `IArchive` requiring 'dependency' `IArchive`.
 
1503
        """
 
1504
    @operation_parameters(
 
1505
        dependency=Reference(schema=Interface, required=True),
 
1506
        # Really IArchive
 
1507
    )
 
1508
    @export_write_operation()
 
1509
    @operation_for_version('devel')
 
1510
    def removeArchiveDependency(dependency):
 
1511
        """Remove the `IArchiveDependency` record for the given dependency.
 
1512
 
 
1513
        :param dependency: is an `IArchive` object.
 
1514
        """
 
1515
 
1461
1516
 
1462
1517
class IArchive(IArchivePublic, IArchiveAppend, IArchiveEdit, IArchiveView):
1463
1518
    """Main Archive interface."""
1690
1745
    )
1691
1746
 
1692
1747
# Circular dependency issues fixed in _schema_circular_imports.py
 
1748
 
 
1749
 
 
1750
def validate_external_dependencies(ext_deps):
 
1751
    """Validate the external_dependencies field.
 
1752
 
 
1753
    :param ext_deps: The dependencies form field to check.
 
1754
    """
 
1755
    errors = []
 
1756
    # The field can consist of multiple entries separated by
 
1757
    # newlines, so process each in turn.
 
1758
    for dep in ext_deps.splitlines():
 
1759
        try:
 
1760
            deb, url, suite, components = dep.split(" ", 3)
 
1761
        except ValueError:
 
1762
            errors.append(
 
1763
                "'%s' is not a complete and valid sources.list entry"
 
1764
                    % dep)
 
1765
            continue
 
1766
 
 
1767
        if deb != "deb":
 
1768
            errors.append("%s: Must start with 'deb'" % dep)
 
1769
        url_components = urlparse(url)
 
1770
        if not url_components[0] or not url_components[1]:
 
1771
            errors.append("%s: Invalid URL" % dep)
 
1772
 
 
1773
    return errors