generated from john/python-template
chore: make ty check blocking with targeted suppressions
Co-authored-by: Copilot App <[email protected]>
This commit is contained in:
co-authored by
Copilot App
parent
a2bb1acd6b
commit
26f9c83f54
@@ -1,6 +1,5 @@
|
||||
# Quality gate for V4.6 [HIGH-06]. `ruff check` is blocking. `ty check` is advisory
|
||||
# during release stabilization: it reports its whole-project baseline without failing
|
||||
# the commit. Restore it to blocking once that baseline is clear.
|
||||
# Quality gate for V4.6 [HIGH-06]. `ruff check`, `ruff format --check`, and `ty check`
|
||||
# are blocking once known `ty` false positives are suppressed inline with rationale.
|
||||
#
|
||||
# Both tools are uv-managed dev dependencies and are not on PATH, so each entry must
|
||||
# go through `uv run`.
|
||||
@@ -21,8 +20,8 @@ repos:
|
||||
pass_filenames: false
|
||||
require_serial: true
|
||||
- id: ty
|
||||
name: ty check (advisory)
|
||||
entry: python -c "import subprocess, sys; subprocess.run(['uv', 'run', 'ty', 'check']); sys.exit(0)"
|
||||
name: ty check
|
||||
entry: uv run ty check
|
||||
language: system
|
||||
types_or: [python, pyi]
|
||||
pass_filenames: false
|
||||
|
||||
@@ -113,3 +113,17 @@ Declared with `>=` floors and moved by explicit `uv lock --upgrade`. Verify with
|
||||
`uv run ruff check .`, `uv run ty check`, and `uv run pytest -q -m "not external"`
|
||||
before committing a changed lockfile.
|
||||
|
||||
## 7. Type-check suppression policy
|
||||
|
||||
`uv run ty check` is a blocking pre-commit gate. Suppressions are allowed only for
|
||||
proven SQLAlchemy descriptor false positives where runtime behavior is correct and
|
||||
the checker cannot represent the descriptor protocol at that call site.
|
||||
|
||||
Every suppression must be:
|
||||
|
||||
1. **Targeted** to a single rule (for example `# ty: ignore[unresolved-attribute]`).
|
||||
2. **Inline** on the expression it suppresses (not file-wide).
|
||||
3. Followed by a **one-line rationale** stating it is a SQLAlchemy descriptor false positive.
|
||||
|
||||
Do not use broad or rationale-free suppressions. If a diagnostic is not a known
|
||||
false positive, fix the code instead of suppressing it.
|
||||
|
||||
@@ -178,25 +178,33 @@ class PhotosService(ServiceBase):
|
||||
async def _list_owner_photos(self, *, session: AsyncSession, person_id: UUID | None) -> list[Photo]:
|
||||
query = select(Photo)
|
||||
if person_id is None:
|
||||
query = query.where(Photo.person_id.is_(None))
|
||||
query = query.where(Photo.person_id.is_(None)) # ty: ignore[unresolved-attribute] - SQLAlchemy descriptor false positive.
|
||||
else:
|
||||
query = query.where(Photo.person_id == person_id)
|
||||
query = query.order_by(Photo.created_at.asc(), Photo.id.asc())
|
||||
query = query.order_by(
|
||||
Photo.created_at.asc(), # ty: ignore[unresolved-attribute] - SQLAlchemy descriptor false positive.
|
||||
Photo.id.asc(), # ty: ignore[unresolved-attribute] - SQLAlchemy descriptor false positive.
|
||||
)
|
||||
return list((await session.exec(query)).all())
|
||||
|
||||
async def _owner_oldest_photo(self, *, session: AsyncSession, person_id: UUID | None) -> Photo | None:
|
||||
query = select(Photo)
|
||||
if person_id is None:
|
||||
query = query.where(Photo.person_id.is_(None))
|
||||
query = query.where(Photo.person_id.is_(None)) # ty: ignore[unresolved-attribute] - SQLAlchemy descriptor false positive.
|
||||
else:
|
||||
query = query.where(Photo.person_id == person_id)
|
||||
query = query.order_by(Photo.created_at.asc(), Photo.id.asc()).limit(1)
|
||||
query = query.order_by(
|
||||
Photo.created_at.asc(), # ty: ignore[unresolved-attribute] - SQLAlchemy descriptor false positive.
|
||||
Photo.id.asc(), # ty: ignore[unresolved-attribute] - SQLAlchemy descriptor false positive.
|
||||
).limit(1)
|
||||
return (await session.exec(query)).first()
|
||||
|
||||
async def _clear_owner_primary(self, *, session: AsyncSession, person_id: UUID | None) -> None:
|
||||
query = select(Photo).where(Photo.is_primary.is_(True))
|
||||
query = select(Photo).where(
|
||||
Photo.is_primary.is_(True) # ty: ignore[unresolved-attribute] - SQLAlchemy descriptor false positive.
|
||||
)
|
||||
if person_id is None:
|
||||
query = query.where(Photo.person_id.is_(None))
|
||||
query = query.where(Photo.person_id.is_(None)) # ty: ignore[unresolved-attribute] - SQLAlchemy descriptor false positive.
|
||||
else:
|
||||
query = query.where(Photo.person_id == person_id)
|
||||
for current in (await session.exec(query)).all():
|
||||
|
||||
@@ -35,7 +35,14 @@ async def _document_ids(settings: Settings) -> set[str]:
|
||||
|
||||
async def _source_counts_by_document(settings: Settings) -> dict[str, int]:
|
||||
async with session_scope(settings=settings) as session:
|
||||
rows = await session.exec(select(Source.document_id, func.count(Source.id)).group_by(Source.document_id))
|
||||
rows = await session.exec(
|
||||
select(
|
||||
Source.document_id,
|
||||
func.count(Source.id), # ty: ignore[invalid-argument-type] - SQLAlchemy descriptor false positive.
|
||||
).group_by(
|
||||
Source.document_id # ty: ignore[invalid-argument-type] - SQLAlchemy descriptor false positive.
|
||||
)
|
||||
)
|
||||
return {str(document_id): int(count) for document_id, count in rows}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user