chore(types): enable warn_return_any and cast all no-any-return sites

Turn on mypy warn_return_any (pyproject) and resolve the 84 resulting
[no-any-return] errors across 43 files with typing.cast() at the return
sites — runtime no-ops that make the declared return type explicit where a
dependency (SQLAlchemy scalar/first/one, httpx .json(), subprocess, docker
SDK) hands back Any. No behavior change: no DTO/table field types altered, no
validation/coercion calls added, every cast reflects the true runtime type.

Locks in return-type strictness so the class of bug where a function silently
widens to Any can't regress. mypy decnet/ clean; adversarially verified
behavior-preserving (84 casts 1:1 with prior returns).

Bump tornado 6.5.5 -> 6.5.7 (CVE-2026-49854, transitive via snakeviz).
This commit is contained in:
2026-06-12 18:21:22 -04:00
parent 337520c7ad
commit 721122a7ef
42 changed files with 128 additions and 124 deletions

View File

@@ -8,7 +8,7 @@ import os
import traceback
import uuid
from contextlib import asynccontextmanager
from typing import Any, AsyncGenerator, Optional
from typing import Any, AsyncGenerator, Optional, cast
from fastapi import FastAPI, Request, status
from fastapi.exceptions import RequestValidationError
@@ -306,7 +306,7 @@ class _ContentTypeMiddleware(BaseHTTPMiddleware):
status_code=415,
media_type="text/plain",
)
return await call_next(request)
return cast(StarletteResponse, await call_next(request))
app.add_middleware(_ContentTypeMiddleware)

View File

@@ -6,7 +6,7 @@ Repository factory — selects a :class:`BaseRepository` implementation based on
from __future__ import annotations
import os
from typing import Any
from typing import Any, cast
from decnet.web.db.repository import BaseRepository
@@ -32,4 +32,4 @@ def get_repository(**kwargs: Any) -> BaseRepository:
raise ValueError(f"Unsupported database type: {db_type}")
from decnet.telemetry import wrap_repository
return wrap_repository(repo)
return cast(BaseRepository, wrap_repository(repo))

View File

@@ -18,7 +18,7 @@ import os
import orjson
import uuid
from typing import Any, Optional, List
from typing import Any, Optional, List, cast
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, async_sessionmaker
@@ -183,7 +183,7 @@ class SQLModelRepository(
result = await session.execute(statement)
state = result.scalar_one_or_none()
if state:
return json.loads(state.value)
return cast(dict[str, Any], json.loads(state.value))
return None
async def set_state(self, key: str, value: Any) -> None: # noqa: ANN401

View File

@@ -10,7 +10,7 @@ from __future__ import annotations
import uuid as _uuid
from datetime import datetime, timezone
from typing import Any, Optional
from typing import Any, Optional, cast
from sqlalchemy import desc, or_, select
from sqlmodel import col
@@ -44,7 +44,7 @@ class AttackerIntelMixin(_MixinBase):
row_uuid = _uuid.uuid4().hex
session.add(AttackerIntel(uuid=row_uuid, **data))
await session.commit()
return row_uuid
return cast(str, row_uuid)
async def get_attacker_intel_row_by_uuid(
self,
@@ -54,7 +54,7 @@ class AttackerIntelMixin(_MixinBase):
result = await session.execute(
select(AttackerIntel).where(AttackerIntel.attacker_uuid == uuid)
)
return result.scalar_one_or_none()
return cast(Optional[AttackerIntel], result.scalar_one_or_none())
async def get_attacker_intel_by_uuid(
self,
@@ -67,7 +67,7 @@ class AttackerIntelMixin(_MixinBase):
row = result.scalar_one_or_none()
if not row:
return None
return row.model_dump(mode="json")
return cast(dict[str, Any], row.model_dump(mode="json"))
async def get_unenriched_attackers(
self, limit: int = 100,

View File

@@ -10,7 +10,7 @@ from __future__ import annotations
import json
import uuid as _uuid
from typing import Any, List, Optional
from typing import Any, List, Optional, cast
from sqlalchemy import desc, func, outerjoin, select
from sqlmodel import col
@@ -47,7 +47,7 @@ class AttackersCoreMixin(_MixinBase):
data = {**data, "uuid": row_uuid}
session.add(Attacker(**data))
await session.commit()
return row_uuid
return cast(str, row_uuid)
async def get_attacker_uuid_by_ip(self, ip: str) -> Optional[str]:
"""Return the :class:`Attacker` UUID for *ip*, or ``None``.
@@ -61,7 +61,7 @@ class AttackersCoreMixin(_MixinBase):
result = await session.execute(
select(col(Attacker.uuid)).where(Attacker.ip == ip)
)
return result.scalar_one_or_none()
return cast(Optional[str], result.scalar_one_or_none())
async def get_attacker_by_uuid(self, uuid: str) -> Optional[dict[str, Any]]:
async with self._session() as session:

View File

@@ -21,7 +21,7 @@ from __future__ import annotations
import uuid as _uuid
from datetime import datetime, timezone
from typing import Any, Optional
from typing import Any, Optional, cast
from sqlalchemy import func, select
from sqlmodel import col
@@ -66,7 +66,7 @@ class AttributionMixin(_MixinBase):
if attacker_row is None:
return None
if attacker_row.identity_id:
return attacker_row.identity_id
return cast(str, attacker_row.identity_id)
new_uuid = _uuid.uuid4().hex
now = datetime.now(timezone.utc)
session.add(

View File

@@ -9,7 +9,7 @@ the reads.
from __future__ import annotations
from datetime import datetime, timezone
from typing import Any, Optional
from typing import Any, Optional, cast
from sqlalchemy import desc, func, select, update
from sqlmodel import col
@@ -36,9 +36,9 @@ class CampaignsMixin(_MixinBase):
if campaign is None:
return None
if campaign.merged_into_uuid is None:
return campaign.model_dump(mode="json")
return cast(dict[str, Any], campaign.model_dump(mode="json"))
current_uuid = campaign.merged_into_uuid
return campaign.model_dump(mode="json")
return cast(dict[str, Any], campaign.model_dump(mode="json"))
async def list_campaigns(
self, limit: int = 50, offset: int = 0,

View File

@@ -4,7 +4,7 @@ from __future__ import annotations
import json
from datetime import datetime, timezone
from typing import Any, Optional
from typing import Any, Optional, cast
from sqlalchemy import desc, func, select, update
@@ -26,7 +26,7 @@ class CanaryMixin(_MixinBase):
)
row = existing.scalar_one_or_none()
if row:
return row.model_dump(mode="json")
return cast(dict[str, Any], row.model_dump(mode="json"))
row = CanaryBlob(**data)
session.add(row)
await session.commit()
@@ -155,7 +155,7 @@ class CanaryMixin(_MixinBase):
.values(state=state, last_error=last_error)
)
await session.commit()
return result.rowcount > 0
return cast(bool, result.rowcount > 0)
async def record_canary_trigger(self, data: dict[str, Any]) -> str:
# Persist the trigger row + bump the token's counters in the same
@@ -204,4 +204,4 @@ class CanaryMixin(_MixinBase):
.values(attacker_id=attacker_id)
)
await session.commit()
return result.rowcount > 0
return cast(bool, result.rowcount > 0)

View File

@@ -4,7 +4,7 @@ from __future__ import annotations
import json
from datetime import datetime, timezone
from typing import Any, List, Optional
from typing import Any, List, Optional, cast
from sqlalchemy import desc, func, or_, select, update
from sqlalchemy.exc import IntegrityError
@@ -67,7 +67,7 @@ class CredentialsCoreMixin(_MixinBase):
existing.outcome = payload["outcome"]
session.add(existing)
await session.commit()
return existing.id
return cast(int, existing.id)
row = Credential(
attacker_ip=payload["attacker_ip"],
decky_name=payload["decky_name"],
@@ -103,7 +103,7 @@ class CredentialsCoreMixin(_MixinBase):
existing2.outcome = payload["outcome"]
session2.add(existing2)
await session2.commit()
return existing2.id
return cast(int, existing2.id)
await session.refresh(row)
return row.id # type: ignore[return-value]

View File

@@ -7,7 +7,7 @@ from __future__ import annotations
import json
import uuid as _uuid
from datetime import datetime, timezone
from typing import Any, List, Optional
from typing import Any, List, Optional, cast
from sqlalchemy import desc, func, select
from sqlmodel import col
@@ -137,7 +137,7 @@ class CredentialReuseMixin(_MixinBase):
d = existing.model_dump(mode="json")
d["inserted"] = False
d["changed"] = changed
return d
return cast(dict[str, Any], d)
async def find_credential_reuse_candidates(
self, min_targets: int = 2
@@ -236,7 +236,7 @@ class CredentialReuseMixin(_MixinBase):
except (json.JSONDecodeError, TypeError):
d[key] = []
await self._enrich_with_secret(session, [d])
return d
return cast(dict[str, Any], d)
@staticmethod
async def _enrich_with_secret(

View File

@@ -9,7 +9,7 @@ the reads.
from __future__ import annotations
from datetime import datetime, timezone
from typing import Any, Optional
from typing import Any, Optional, cast
from sqlalchemy import desc, func, select, update
from sqlmodel import col
@@ -42,10 +42,10 @@ class IdentitiesMixin(_MixinBase):
if identity is None:
return None
if identity.merged_into_uuid is None:
return identity.model_dump(mode="json")
return cast(dict[str, Any], identity.model_dump(mode="json"))
current_uuid = identity.merged_into_uuid
# Hit the hop cap — surface what we have rather than recurse.
return identity.model_dump(mode="json")
return cast(dict[str, Any], identity.model_dump(mode="json"))
async def list_identities(
self, limit: int = 50, offset: int = 0,

View File

@@ -21,7 +21,7 @@ not validate values — that happens at construction time by the BEHAVE
from __future__ import annotations
import uuid as _uuid
from typing import Any, Optional
from typing import Any, Optional, cast
from sqlalchemy import desc, func, select
from sqlmodel import col
@@ -85,7 +85,7 @@ class ObservationsMixin(_MixinBase):
row_data = {**data, "id": row_id}
session.add(ObservationRow(**row_data))
await session.commit()
return row_id
return cast(str, row_id)
async def latest_observation_per_primitive(
self, attacker_uuid: str,
@@ -178,7 +178,7 @@ class ObservationsMixin(_MixinBase):
row = (await session.execute(stmt)).scalar_one_or_none()
if not row:
return None
return row.model_dump(mode="json")
return cast(dict[str, Any], row.model_dump(mode="json"))
async def observations_for_identity_primitive(
self, identity_uuid: str, primitive: str,

View File

@@ -13,7 +13,7 @@ caller is upgrading ``False/None`` to ``True``.
from __future__ import annotations
from datetime import datetime, timezone
from typing import Optional
from typing import Optional, cast
from sqlalchemy import select
@@ -106,4 +106,4 @@ class ObservedAttachmentsMixin(_MixinBase):
row.mal_hash_match_at = now
session.add(row)
await session.commit()
return row.uuid
return cast(str, row.uuid)

View File

@@ -3,7 +3,7 @@
from __future__ import annotations
from datetime import datetime, timedelta, timezone
from typing import Any, Optional
from typing import Any, Optional, cast
from sqlalchemy import desc, func, select, update
@@ -92,7 +92,7 @@ class RealismMixin(_MixinBase):
row = result.scalars().first()
if row is None:
return None
return row.model_dump(mode="json")
return cast(dict[str, Any], row.model_dump(mode="json"))
async def get_realism_config(
self, key: str,
@@ -103,7 +103,7 @@ class RealismMixin(_MixinBase):
row = result.scalars().first()
if row is None:
return None
return row.model_dump(mode="json")
return cast(dict[str, Any], row.model_dump(mode="json"))
async def set_realism_config(
self, key: str, value: str,
@@ -157,4 +157,4 @@ class RealismMixin(_MixinBase):
row = result.scalars().first()
if row is None:
return None
return row.model_dump(mode="json")
return cast(dict[str, Any], row.model_dump(mode="json"))

View File

@@ -5,7 +5,7 @@ from __future__ import annotations
import json
import uuid
from datetime import datetime, timezone
from typing import Any, Optional
from typing import Any, Optional, cast
from sqlalchemy import select
@@ -47,7 +47,7 @@ class TarpitMixin(_MixinBase):
return None
d = row.model_dump(mode="json")
d["ports"] = json.loads(d["ports"])
return d
return cast(dict[str, Any], d)
async def delete_tarpit_rule(self, decky_name: str) -> bool:
async with self._session() as session:

View File

@@ -3,7 +3,7 @@
from __future__ import annotations
from datetime import datetime, timezone
from typing import Any, Optional
from typing import Any, Optional, cast
import orjson
from sqlalchemy import asc, desc, select, text
@@ -102,7 +102,7 @@ class TopologyMutationsMixin(_MixinBase):
_ = now
if row is None:
return None
return row.model_dump(mode="json")
return cast(dict[str, Any], row.model_dump(mode="json"))
async def mark_mutation_applied(self, mutation_id: str) -> None:
async with self._session() as session:

View File

@@ -3,7 +3,7 @@
from __future__ import annotations
from datetime import datetime, timezone
from typing import Any, Optional
from typing import Any, Optional, cast
from sqlalchemy import select, update
from sqlmodel import col
@@ -65,7 +65,7 @@ class WebhooksMixin(_MixinBase):
.values(**patch)
)
await session.commit()
return result.rowcount > 0
return cast(bool, result.rowcount > 0)
async def delete_webhook_subscription(self, uuid: str) -> bool:
async with self._session() as session: