mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-01 15:37:32 +08:00
Type-hint imperatively-mapped RepositoryMetadata columns
RepositoryMetadata stays imperatively mapped because a declaratively mapped class cannot have a `metadata` column attribute (it collides with DeclarativeBase.metadata, see PR #12064). The downside has been that mypy can't see any of the column attributes, forcing every caller to either ignore [attr-defined] or work around it with setattr. Add Mapped[X] annotations on the class — they're inert at runtime (the mapper still installs InstrumentedAttribute descriptors via map_imperatively) and only feed the type checker. JSON columns (`metadata`, `tool_versions`) are typed as Mapped[Any] rather than Optional, matching existing caller assumptions that they're populated when accessed. Drops three now-obsolete `# type: ignore[has-type]` / `# type: ignore[attr-defined]` comments. mypy error count on the affected toolshed files goes from 62 to 53 with no new failures.
This commit is contained in:
@@ -1087,7 +1087,7 @@ def get_repository_metadata(session, repository_id):
|
||||
stmt = (
|
||||
select(RepositoryMetadata)
|
||||
.where(RepositoryMetadata.repository_id == repository_id)
|
||||
.order_by(RepositoryMetadata.changeset_revision, RepositoryMetadata.update_time.desc()) # type: ignore[attr-defined] # mapped attribute
|
||||
.order_by(RepositoryMetadata.changeset_revision, RepositoryMetadata.update_time.desc())
|
||||
)
|
||||
return session.scalars(stmt)
|
||||
|
||||
|
||||
@@ -387,13 +387,14 @@ class Repository(Base, Dictifiable):
|
||||
user = relationship("User", back_populates="active_repositories")
|
||||
downloadable_revisions = relationship(
|
||||
"RepositoryMetadata",
|
||||
primaryjoin=lambda: (Repository.id == RepositoryMetadata.repository_id) & (RepositoryMetadata.downloadable == true()), # type: ignore[has-type]
|
||||
primaryjoin=lambda: (Repository.id == RepositoryMetadata.repository_id)
|
||||
& (RepositoryMetadata.downloadable == true()),
|
||||
viewonly=True,
|
||||
order_by=lambda: desc(RepositoryMetadata.update_time), # type: ignore[attr-defined]
|
||||
order_by=lambda: desc(RepositoryMetadata.update_time),
|
||||
)
|
||||
metadata_revisions = relationship(
|
||||
"RepositoryMetadata",
|
||||
order_by=lambda: desc(RepositoryMetadata.update_time), # type: ignore[attr-defined]
|
||||
order_by=lambda: desc(RepositoryMetadata.update_time),
|
||||
back_populates="repository",
|
||||
)
|
||||
roles = relationship("RepositoryRoleAssociation", back_populates="repository")
|
||||
@@ -702,10 +703,26 @@ class Tag(Base):
|
||||
|
||||
|
||||
class RepositoryMetadata(Dictifiable):
|
||||
repository: "Repository"
|
||||
# Annotations only — runtime attributes are installed by
|
||||
# mapper_registry.map_imperatively below.
|
||||
id: Mapped[Optional[int]]
|
||||
create_time: Mapped[Optional[datetime]]
|
||||
update_time: Mapped[Optional[datetime]]
|
||||
repository_id: Mapped[Optional[int]]
|
||||
changeset_revision: Mapped[Optional[str]]
|
||||
numeric_revision: Mapped[Optional[int]]
|
||||
metadata: Mapped[Any]
|
||||
tool_versions: Mapped[Any]
|
||||
malicious: Mapped[Optional[bool]]
|
||||
downloadable: Mapped[Optional[bool]]
|
||||
missing_test_components: Mapped[Optional[bool]]
|
||||
has_repository_dependencies: Mapped[Optional[bool]]
|
||||
includes_datatypes: Mapped[Optional[bool]]
|
||||
includes_tools: Mapped[Optional[bool]]
|
||||
includes_tool_dependencies: Mapped[Optional[bool]]
|
||||
includes_workflows: Mapped[Optional[bool]]
|
||||
repository: Mapped["Repository"]
|
||||
|
||||
# Once the class has been mapped, all Column items in this table will be available
|
||||
# as instrumented class attributes on RepositoryMetadata.
|
||||
table = Table(
|
||||
"repository_metadata",
|
||||
mapper_registry.metadata,
|
||||
|
||||
@@ -91,12 +91,10 @@ def test_get_repositories_for_indexing_orders_by_last_revision_create_time(shed_
|
||||
base = datetime(2026, 1, 1, 12, 0, 0)
|
||||
rm_a = RepositoryMetadata(repository_id=repo_a.id, changeset_revision="a", downloadable=True)
|
||||
rm_b = RepositoryMetadata(repository_id=repo_b.id, changeset_revision="b", downloadable=True)
|
||||
# setattr — RepositoryMetadata is imperatively mapped, so create_time /
|
||||
# update_time aren't visible to the static type checker.
|
||||
setattr(rm_a, "create_time", base)
|
||||
setattr(rm_a, "update_time", base)
|
||||
setattr(rm_b, "create_time", base + timedelta(hours=1))
|
||||
setattr(rm_b, "update_time", base + timedelta(hours=1))
|
||||
rm_a.create_time = base
|
||||
rm_a.update_time = base
|
||||
rm_b.create_time = base + timedelta(hours=1)
|
||||
rm_b.update_time = base + timedelta(hours=1)
|
||||
session.add_all([rm_a, rm_b])
|
||||
# Make repo_a's row look "recently updated" — under the old ORDER BY this
|
||||
# surfaced repo_a first even though repo_b has the newer downloadable
|
||||
|
||||
Reference in New Issue
Block a user