Made a comment Diff comments:
> diff --git a/lib/lp/registry/model/externalpackage.py > b/lib/lp/registry/model/externalpackage.py > new file mode 100644 > index 0000000..d97390d > --- /dev/null > +++ b/lib/lp/registry/model/externalpackage.py > @@ -0,0 +1,152 @@ > +# Copyright 2009-2025 Canonical Ltd. This software is licensed under the > +# GNU Affero General Public License version 3 (see the file LICENSE). > + > +"""Classes to represent external packages in a distribution.""" > + > +__all__ = [ > + "ExternalPackage", > +] > + > +from zope.interface import implementer > + > +from lp.bugs.model.bugtarget import BugTargetBase > +from lp.bugs.model.structuralsubscription import ( > + StructuralSubscriptionTargetMixin, > +) > +from lp.registry.interfaces.distribution import IDistribution > +from lp.registry.interfaces.externalpackage import ( > + ExternalPackageType, > + IExternalPackage, > +) > +from lp.registry.interfaces.sourcepackagename import ISourcePackageName > +from lp.registry.model.hasdrivers import HasDriversMixin > +from lp.services.channels import channel_list_to_string, > channel_string_to_list > +from lp.services.propertycache import cachedproperty > + > + > +@implementer(IExternalPackage) > +class ExternalPackage( > + BugTargetBase, > + HasDriversMixin, > + StructuralSubscriptionTargetMixin, > +): > + """This is a "Magic External Package". It is not a Storm model, but > instead > + it represents a package with a particular name, type and channel in a > + particular distribution. > + """ > + > + def __init__( > + self, > + distribution: IDistribution, > + sourcepackagename: ISourcePackageName, > + packagetype: ExternalPackageType, > + channel: (str, tuple, list), > + ) -> "ExternalPackage": > + self.distribution = distribution > + self.sourcepackagename = sourcepackagename > + self.packagetype = packagetype > + self.channel = self.validate_channel(channel) > + > + def __repr__(self) -> str: > + return f"<{self.__class__.__name__} '{self.display_name}'>" > + > + def validate_channel(self, channel: (str, tuple, list)) -> tuple: Potentially in the future I will make this a `classmethod` to only call it from `distribution.getExternalPackage` since that's the only place where we create new ones. This way we will not need to validate a channel every time we get an ExternalPackage from the db. > + if channel is None: > + return None > + > + if not isinstance(channel, (str, tuple, list)): > + raise ValueError("Channel must be a str, tuple or list") > + > + return channel_string_to_list(channel) > + > + @property > + def name(self) -> str: > + """See `IExternalPackage`.""" > + return self.sourcepackagename.name > + > + @property > + def display_channel(self) -> str: > + """See `IExternalPackage`.""" > + if not self.channel: > + return None > + > + return channel_list_to_string(*self.channel) > + > + @cachedproperty > + def display_name(self) -> str: > + """See `IExternalPackage`.""" > + if self.channel: > + return "%s - %s @%s in %s" % ( > + self.sourcepackagename.name, > + self.packagetype, > + self.display_channel, > + self.distribution.display_name, > + ) > + > + return "%s - %s in %s" % ( > + self.sourcepackagename.name, > + self.packagetype, > + self.distribution.display_name, > + ) > + > + # There are different places of launchpad codebase where they use > different > + # display names > + @property > + def displayname(self) -> str: > + """See `IExternalPackage`.""" > + return self.display_name > + > + @property > + def bugtargetdisplayname(self) -> str: > + """See `IExternalPackage`.""" > + return self.display_name > + > + @property > + def bugtargetname(self) -> str: > + """See `IExternalPackage`.""" > + return self.display_name > + > + @property > + def title(self) -> str: > + """See `IExternalPackage`.""" > + return self.display_name > + > + def __eq__(self, other: "ExternalPackage") -> str: > + """See `IExternalPackage`.""" > + return ( > + (IExternalPackage.providedBy(other)) > + and (self.distribution.id == other.distribution.id) > + and (self.sourcepackagename.id == other.sourcepackagename.id) > + and (self.packagetype == other.packagetype) > + and (self.channel == other.channel) > + ) > + > + def __hash__(self) -> int: > + """Return the combined attributes hash.""" > + return hash( > + ( > + self.distribution, > + self.sourcepackagename, > + self.packagetype, > + self.display_channel, > + ) > + ) > + > + @property > + def drivers(self) -> list: > + """See `IHasDrivers`.""" > + return self.distribution.drivers > + > + @property > + def official_bug_tags(self) -> list: > + """See `IHasBugs`.""" > + return self.distribution.official_bug_tags > + > + @property > + def pillar(self) -> IDistribution: > + """See `IBugTarget`.""" > + return self.distribution > + > + def _getOfficialTagClause(self): > + """See `IBugTarget`.""" > + return self.distribution._getOfficialTagClause() -- https://code.launchpad.net/~enriqueesanchz/launchpad/+git/launchpad/+merge/488673 Your team Launchpad code reviewers is requested to review the proposed merge of ~enriqueesanchz/launchpad:add-external-package into launchpad:master. _______________________________________________ Mailing list: https://launchpad.net/~launchpad-reviewers Post to : launchpad-reviewers@lists.launchpad.net Unsubscribe : https://launchpad.net/~launchpad-reviewers More help : https://help.launchpad.net/ListHelp