fix(deps): restore CI under SQLAlchemy 2.1

SQLAlchemy 2.1 no longer installs greenlet by default, so the async
engine fails to import (Test, E2E api container), and its sources use
the `untyped-decorator` mypy code that the pinned mypy 1.9 hook rejects
(Lint).

- depend on sqlalchemy[asyncio] so greenlet is installed again
- bump the mirrors-mypy pre-commit hook to v2.4.0
- guard nullable tag values / channel_idx flagged by the newer mypy

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UFsp2i9E98EzovBaurmtEb
This commit is contained in:
Louis KingandClaude Opus 5.5 committed 2026-10-11 22:53:10 +01:00
1 parent 403a3c7a1c
commit 45bcf25fe8
4 files changed
+12 -7

No files matched your search

+2 -2
View File
@@ -29,14 +29,14 @@ repos:
- flake8-comprehensions - flake8-comprehensions
- repo: https://github.com/pre-commit/mirrors-mypy - repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.9.0 rev: v2.4.0
hooks: hooks:
- id: mypy - id: mypy
exclude: ^alembic/ exclude: ^alembic/
additional_dependencies: additional_dependencies:
- pydantic>=2.0.0 - pydantic>=2.0.0
- pydantic-settings>=2.0.0 - pydantic-settings>=2.0.0
- sqlalchemy>=2.0.0 - sqlalchemy[asyncio]>=2.0.0
- fastapi>=0.100.0 - fastapi>=0.100.0
- alembic>=1.7.0 - alembic>=1.7.0
- types-paho-mqtt>=1.6.0 - types-paho-mqtt>=1.6.0
+1 -1
View File
@@ -28,7 +28,7 @@ dependencies = [
"pydantic>=2.0.0", "pydantic>=2.0.0",
"pydantic-settings>=2.0.0", "pydantic-settings>=2.0.0",
"python-dotenv>=1.0.0", "python-dotenv>=1.0.0",
"sqlalchemy>=2.0.0", "sqlalchemy[asyncio]>=2.0.0",
"alembic>=1.12.0", "alembic>=1.12.0",
"fastapi>=0.100.0", "fastapi>=0.100.0",
"starlette>=0.40.0,<1.0.0", "starlette>=0.40.0,<1.0.0",
+4 -2
View File
@@ -84,7 +84,8 @@ def fetch_observers_for_events(
.where(NodeTag.key == "name") .where(NodeTag.key == "name")
) )
for node_id, value in session.execute(tag_query).all(): for node_id, value in session.execute(tag_query).all():
tag_names[node_id] = value if value is not None:
tag_names[node_id] = value
for row in results: for row in results:
if row.event_hash not in observers_by_hash: if row.event_hash not in observers_by_hash:
@@ -150,6 +151,7 @@ def resolve_sender_names(
.where(NodeTag.key == "name") .where(NodeTag.key == "name")
) )
for public_key, value in session.execute(tag_query).all(): for public_key, value in session.execute(tag_query).all():
tag_names[public_key[:12]] = value if value is not None:
tag_names[public_key[:12]] = value
return names, tag_names return names, tag_names
+5 -2
View File
@@ -217,7 +217,9 @@ def get_stats(
) )
channel_results = session.execute(channel_counts_query).all() channel_results = session.execute(channel_counts_query).all()
channel_message_counts = { channel_message_counts = {
int(channel): int(count) for channel, count in channel_results int(channel): int(count)
for channel, count in channel_results
if channel is not None
} }
from meshcore_hub.common.config import get_web_settings from meshcore_hub.common.config import get_web_settings
@@ -325,7 +327,8 @@ def get_recent_activity(
.where(NodeTag.key == "name") .where(NodeTag.key == "name")
) )
for public_key, value in session.execute(tag_name_query).all(): for public_key, value in session.execute(tag_name_query).all():
tag_names[public_key] = value if value is not None:
tag_names[public_key] = value
# Batch-resolve observers (via event_observers) and the legacy # Batch-resolve observers (via event_observers) and the legacy
# observed_by public key for the recent adverts, mirroring the # observed_by public key for the recent adverts, mirroring the