class Release(db.Model): __tablename__ = "releases" @declared_attr def __table_args__(cls): # noqa return ( Index("release_created_idx", cls.created.desc()), Index("release_project_created_idx", cls.project_id, cls.created.desc()), Index("release_version_idx", cls.version), UniqueConstraint("project_id", "version"), ) __repr__ = make_repr("project", "version") __parent__ = dotted_navigator("project") __name__ = dotted_navigator("version") project_id = Column( ForeignKey("projects.id", onupdate="CASCADE", ondelete="CASCADE"), nullable=False, ) version = Column(Text, nullable=False) canonical_version = Column(Text, nullable=False) is_prerelease = orm.column_property(func.pep440_is_prerelease(version)) author = Column(Text) author_email = Column(Text) maintainer = Column(Text) maintainer_email = Column(Text) home_page = Column(Text) license = Column(Text) summary = Column(Text) keywords = Column(Text) platform = Column(Text) download_url = Column(Text) _pypi_ordering = Column(Integer) requires_python = Column(Text) created = Column(DateTime(timezone=False), nullable=False, server_default=sql.func.now()) description_id = Column( ForeignKey("release_descriptions.id", onupdate="CASCADE", ondelete="CASCADE"), nullable=False, ) description = orm.relationship( "Description", backref=orm.backref( "release", cascade="all, delete-orphan", passive_deletes=True, passive_updates=True, single_parent=True, uselist=False, ), ) yanked = Column(Boolean, nullable=False, server_default=sql.false()) yanked_reason = Column(Text, nullable=False, server_default="") _classifiers = orm.relationship( Classifier, backref="project_releases", secondary=lambda: release_classifiers, order_by=Classifier.classifier, passive_deletes=True, ) classifiers = association_proxy("_classifiers", "classifier") files = orm.relationship( "File", backref="release", cascade="all, delete-orphan", lazy="dynamic", order_by=lambda: File.filename, passive_deletes=True, ) dependencies = orm.relationship( "Dependency", backref="release", cascade="all, delete-orphan", passive_deletes=True, ) _requires = _dependency_relation(DependencyKind.requires) requires = association_proxy("_requires", "specifier") _provides = _dependency_relation(DependencyKind.provides) provides = association_proxy("_provides", "specifier") _obsoletes = _dependency_relation(DependencyKind.obsoletes) obsoletes = association_proxy("_obsoletes", "specifier") _requires_dist = _dependency_relation(DependencyKind.requires_dist) requires_dist = association_proxy("_requires_dist", "specifier") _provides_dist = _dependency_relation(DependencyKind.provides_dist) provides_dist = association_proxy("_provides_dist", "specifier") _obsoletes_dist = _dependency_relation(DependencyKind.obsoletes_dist) obsoletes_dist = association_proxy("_obsoletes_dist", "specifier") _requires_external = _dependency_relation(DependencyKind.requires_external) requires_external = association_proxy("_requires_external", "specifier") _project_urls = _dependency_relation(DependencyKind.project_url) project_urls = association_proxy("_project_urls", "specifier") uploader_id = Column( ForeignKey("users.id", onupdate="CASCADE", ondelete="SET NULL"), nullable=True, index=True, ) uploader = orm.relationship(User) uploaded_via = Column(Text) @property def urls(self): _urls = OrderedDict() if self.home_page: _urls["Homepage"] = self.home_page if self.download_url: _urls["Download"] = self.download_url for urlspec in self.project_urls: name, _, url = urlspec.partition(",") name = name.strip() url = url.strip() if name and url: _urls[name] = url return _urls @property def github_repo_info_url(self): for url in self.urls.values(): parsed = urlparse(url) segments = parsed.path.strip("/").split("/") if parsed.netloc in {"github.com", "www.github.com" } and len(segments) >= 2: user_name, repo_name = segments[:2] return f"https://api.github.com/repos/{user_name}/{repo_name}" @property def has_meta(self): return any([ self.license, self.keywords, self.author, self.author_email, self.maintainer, self.maintainer_email, self.requires_python, ])
class Release(db.ModelBase): __tablename__ = "releases" @declared_attr def __table_args__(cls): # noqa return ( Index("release_created_idx", cls.created.desc()), Index("release_name_created_idx", cls.name, cls.created.desc()), Index("release_version_idx", cls.version), ) __repr__ = make_repr("name", "version") __parent__ = dotted_navigator("project") __name__ = dotted_navigator("version") name = Column( Text, ForeignKey("packages.name", onupdate="CASCADE", ondelete="CASCADE"), primary_key=True, ) version = Column(Text, primary_key=True) canonical_version = Column(Text, nullable=False) is_prerelease = orm.column_property(func.pep440_is_prerelease(version)) author = Column(Text) author_email = Column(Text) maintainer = Column(Text) maintainer_email = Column(Text) home_page = Column(Text) license = Column(Text) summary = Column(Text) description_content_type = Column(Text) keywords = Column(Text) platform = Column(Text) download_url = Column(Text) _pypi_ordering = Column(Integer) requires_python = Column(Text) created = Column(DateTime(timezone=False), nullable=False, server_default=sql.func.now()) # We defer this column because it is a very large column (it can be MB in # size) and we very rarely actually want to access it. Typically we only # need it when rendering the page for a single project, but many of our # queries only need to access a few of the attributes of a Release. Instead # of playing whack-a-mole and using load_only() or defer() on each of # those queries, deferring this here makes the default case more # performant. description = orm.deferred(Column(Text)) _classifiers = orm.relationship( Classifier, backref="project_releases", secondary=lambda: release_classifiers, order_by=Classifier.classifier, passive_deletes=True, ) classifiers = association_proxy("_classifiers", "classifier") files = orm.relationship( "File", backref="release", cascade="all, delete-orphan", lazy="dynamic", order_by=lambda: File.filename, passive_deletes=True, ) dependencies = orm.relationship("Dependency") _requires = _dependency_relation(DependencyKind.requires) requires = association_proxy("_requires", "specifier") _provides = _dependency_relation(DependencyKind.provides) provides = association_proxy("_provides", "specifier") _obsoletes = _dependency_relation(DependencyKind.obsoletes) obsoletes = association_proxy("_obsoletes", "specifier") _requires_dist = _dependency_relation(DependencyKind.requires_dist) requires_dist = association_proxy("_requires_dist", "specifier") _provides_dist = _dependency_relation(DependencyKind.provides_dist) provides_dist = association_proxy("_provides_dist", "specifier") _obsoletes_dist = _dependency_relation(DependencyKind.obsoletes_dist) obsoletes_dist = association_proxy("_obsoletes_dist", "specifier") _requires_external = _dependency_relation(DependencyKind.requires_external) requires_external = association_proxy("_requires_external", "specifier") _project_urls = _dependency_relation(DependencyKind.project_url) project_urls = association_proxy("_project_urls", "specifier") uploader = orm.relationship( "User", secondary=lambda: JournalEntry.__table__, primaryjoin=lambda: ((JournalEntry.name == orm.foreign(Release.name)) & (JournalEntry.version == orm.foreign(Release.version)) & (JournalEntry.action == "new release")), secondaryjoin=lambda: ( (User.username == orm.foreign(JournalEntry._submitted_by))), order_by=lambda: JournalEntry.id.desc(), # TODO: We have uselist=False here which raises a warning because # multiple items were returned. This should only be temporary because # we should add a nullable FK to JournalEntry so we don't need to rely # on ordering and implicitly selecting the first object to make this # happen, uselist=False, viewonly=True, ) uploaded_via = Column(Text) @property def urls(self): _urls = OrderedDict() if self.home_page: _urls["Homepage"] = self.home_page for urlspec in self.project_urls: name, url = [x.strip() for x in urlspec.split(",", 1)] _urls[name] = url if self.download_url and "Download" not in _urls: _urls["Download"] = self.download_url return _urls @property def github_repo_info_url(self): for parsed in [urlparse(url) for url in self.urls.values()]: segments = parsed.path.strip("/").rstrip("/").split("/") if (parsed.netloc == "github.com" or parsed.netloc == "www.github.com") and len(segments) >= 2: user_name, repo_name = segments[:2] return f"https://api.github.com/repos/{user_name}/{repo_name}" @property def has_meta(self): return any([ self.license, self.keywords, self.author, self.author_email, self.maintainer, self.maintainer_email, self.requires_python, ])