From 45bcf25fe8470ea1ca798851b585ddac707e8cb4 Mon Sep 17 00:00:00 2001 From: Louis King Date: Sun, 11 Oct 2026 22:53:10 +0100 Subject: [PATCH] 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 Claude-Session: https://claude.ai/code/session_01UFsp2i9E98EzovBaurmtEb --- .pre-commit-config.yaml | 4 ++-- pyproject.toml | 2 +- src/meshcore_hub/api/observer_utils.py | 6 ++++-- src/meshcore_hub/api/routes/dashboard.py | 7 +++++-- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c6b1462..9026d38 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -29,14 +29,14 @@ repos: - flake8-comprehensions - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.9.0 + rev: v2.4.0 hooks: - id: mypy exclude: ^alembic/ additional_dependencies: - pydantic>=2.0.0 - pydantic-settings>=2.0.0 - - sqlalchemy>=2.0.0 + - sqlalchemy[asyncio]>=2.0.0 - fastapi>=0.100.0 - alembic>=1.7.0 - types-paho-mqtt>=1.6.0 diff --git a/pyproject.toml b/pyproject.toml index cf7c666..5f99293 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,7 +28,7 @@ dependencies = [ "pydantic>=2.0.0", "pydantic-settings>=2.0.0", "python-dotenv>=1.0.0", - "sqlalchemy>=2.0.0", + "sqlalchemy[asyncio]>=2.0.0", "alembic>=1.12.0", "fastapi>=0.100.0", "starlette>=0.40.0,<1.0.0", diff --git a/src/meshcore_hub/api/observer_utils.py b/src/meshcore_hub/api/observer_utils.py index 3a8486f..2c66e51 100644 --- a/src/meshcore_hub/api/observer_utils.py +++ b/src/meshcore_hub/api/observer_utils.py @@ -84,7 +84,8 @@ def fetch_observers_for_events( .where(NodeTag.key == "name") ) 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: if row.event_hash not in observers_by_hash: @@ -150,6 +151,7 @@ def resolve_sender_names( .where(NodeTag.key == "name") ) 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 diff --git a/src/meshcore_hub/api/routes/dashboard.py b/src/meshcore_hub/api/routes/dashboard.py index 1b32bf9..632487e 100644 --- a/src/meshcore_hub/api/routes/dashboard.py +++ b/src/meshcore_hub/api/routes/dashboard.py @@ -217,7 +217,9 @@ def get_stats( ) channel_results = session.execute(channel_counts_query).all() 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 @@ -325,7 +327,8 @@ def get_recent_activity( .where(NodeTag.key == "name") ) 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 # observed_by public key for the recent adverts, mirroring the