diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-09-19 14:24:13 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-09-19 14:24:13 +0200 |
| commit | 86188385cbdae1ee90c1dca7a7b9db2edef1ecd4 (patch) | |
| tree | cc01153f05e84ad6cd34556caecd3f4bc335d0bd /packages/meshbay-hub/src/meshbay_hub/api | |
| parent | d2495a2c4b89fbbfc18cefec83ae96cabdd745e2 (diff) | |
| download | meshbay-86188385cbdae1ee90c1dca7a7b9db2edef1ecd4.tar.gz | |
style: ruff's own fixes, mechanically applied
`ruff check .` had gone unrun long enough to report 568 errors, which is the
same as having no linter: the next real finding would have been invisible in the
noise. This is the 521 it fixes by itself, in 173 files, and nothing else — the
98 it cannot fix are the next commit.
What actually changed: import sorting (225), imports nobody used (87, none of
them a re-export — no `__init__.py` is touched, which was the one way this could
have broken an import elsewhere), `datetime.timezone.utc` to `datetime.UTC` (69)
and `asyncio.TimeoutError` to `TimeoutError` (18), both plain aliases on the 3.12
this project requires, `Optional[X]` to `X | None` (24), and f-strings with
nothing to interpolate (19).
Checked rather than assumed: every module in the three packages still imports,
and the suite is 2893 passed — the same count, test for test, as the merge
before it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/api')
11 files changed, 71 insertions, 61 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/api/admin.py b/packages/meshbay-hub/src/meshbay_hub/api/admin.py index 7ca1e68..d8e13e9 100644 --- a/packages/meshbay-hub/src/meshbay_hub/api/admin.py +++ b/packages/meshbay-hub/src/meshbay_hub/api/admin.py @@ -6,19 +6,18 @@ Separate from moderation.py (which handles public reporting and content blocklis """ import logging -from datetime import datetime, timezone from fastapi import APIRouter, Depends, HTTPException, Query from pydantic import BaseModel from sqlalchemy import func, select from sqlalchemy.ext.asyncio import AsyncSession -from meshbay_hub.auth import decrypt_email +from meshbay_hub import hub_settings from meshbay_hub.api.deps import require_admin, require_moderator, user_is_admin from meshbay_hub.api.revocation import get_connected_node_count, is_node_connected +from meshbay_hub.auth import decrypt_email from meshbay_hub.db.engine import get_db from meshbay_hub.db.models import Group, GroupMember, IPLog, Node, User -from meshbay_hub import hub_settings log = logging.getLogger(__name__) diff --git a/packages/meshbay-hub/src/meshbay_hub/api/deps.py b/packages/meshbay-hub/src/meshbay_hub/api/deps.py index 907a481..501be9d 100644 --- a/packages/meshbay-hub/src/meshbay_hub/api/deps.py +++ b/packages/meshbay-hub/src/meshbay_hub/api/deps.py @@ -8,8 +8,8 @@ JWT scope enforcement: """ from fastapi import Depends, Header, HTTPException, status -from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select +from sqlalchemy.ext.asyncio import AsyncSession from meshbay_hub.auth import decode_access_token from meshbay_hub.db.engine import get_db diff --git a/packages/meshbay-hub/src/meshbay_hub/api/federation.py b/packages/meshbay-hub/src/meshbay_hub/api/federation.py index 9e252c6..755639e 100644 --- a/packages/meshbay-hub/src/meshbay_hub/api/federation.py +++ b/packages/meshbay-hub/src/meshbay_hub/api/federation.py @@ -23,18 +23,18 @@ Protocol version: MHP 0.1 import logging import time import uuid +from datetime import UTC import jwt -from fastapi import APIRouter, Depends, HTTPException, Header +from fastapi import APIRouter, Depends, Header, HTTPException +from meshbay_common import MHP_VERSION from pydantic import BaseModel from sqlalchemy import func, select from sqlalchemy.ext.asyncio import AsyncSession -from meshbay_common import MHP_VERSION from meshbay_hub import __version__, hub_settings from meshbay_hub.api.deps import require_admin -from meshbay_hub.auth import ( - hub_id, hub_private_key_pem, hub_public_key_pem) +from meshbay_hub.auth import hub_id, hub_private_key_pem, hub_public_key_pem from meshbay_hub.db.engine import get_db from meshbay_hub.db.models import FederatedGroup, Group, HubPeer, User @@ -244,8 +244,8 @@ async def receive_directory( if len(body.groups) > MAX_FEDERATED_GROUPS_PER_PUSH: raise HTTPException(status_code=413, detail="Too many groups in one push") - from datetime import datetime, timezone - now = datetime.now(timezone.utc) + from datetime import datetime + now = datetime.now(UTC) have = await db.scalar( select(func.count()).select_from(FederatedGroup) .where(FederatedGroup.source_hub == sender)) or 0 diff --git a/packages/meshbay-hub/src/meshbay_hub/api/groups.py b/packages/meshbay-hub/src/meshbay_hub/api/groups.py index 72ba194..3a11345 100644 --- a/packages/meshbay-hub/src/meshbay_hub/api/groups.py +++ b/packages/meshbay-hub/src/meshbay_hub/api/groups.py @@ -1,22 +1,27 @@ """Group endpoints — /v1/groups/*""" import re +from datetime import UTC, datetime from fastapi import APIRouter, Depends, HTTPException, Query, Request from pydantic import BaseModel -from datetime import datetime, timezone from sqlalchemy import func, or_, select, update from sqlalchemy.exc import IntegrityError from sqlalchemy.ext.asyncio import AsyncSession from meshbay_hub import hub_settings, mail -from meshbay_hub.auth import decrypt_email from meshbay_hub.api.deps import get_current_user, require_user_scope from meshbay_hub.api.middleware import limiter from meshbay_hub.api.netutil import client_ip +from meshbay_hub.auth import decrypt_email from meshbay_hub.db.engine import get_db from meshbay_hub.db.models import ( - FederatedGroup, Group, GroupMember, IPLog, SwarmSource, User, + FederatedGroup, + Group, + GroupMember, + IPLog, + SwarmSource, + User, ) router = APIRouter(prefix="/v1/groups", tags=["groups"]) @@ -97,7 +102,7 @@ async def touch_group_activity( await db.execute( update(Group) .where(Group.id == group_id) - .values(last_activity_at=datetime.now(timezone.utc))) + .values(last_activity_at=datetime.now(UTC))) await db.commit() return {"ok": True} @@ -261,9 +266,9 @@ async def swarm_register( detail="endpoint must be '<webrtc|quic>:<port>' — a port on the " "registering node, not an address") - from datetime import datetime, timezone + from datetime import datetime existing = await db.get(SwarmSource, (body.content_hash, current_user.id)) - now = datetime.now(timezone.utc) + now = datetime.now(UTC) if existing: existing.endpoint = body.endpoint existing.last_seen = now @@ -297,8 +302,8 @@ async def swarm_sources( Authenticated (H7): an open endpoint lets anyone probe whether a given file exists anywhere in the network and which node holds it. """ - from datetime import datetime, timezone, timedelta - cutoff = datetime.now(timezone.utc) - timedelta(minutes=30) + from datetime import datetime, timedelta + cutoff = datetime.now(UTC) - timedelta(minutes=30) result = await db.execute( select(SwarmSource) .where( diff --git a/packages/meshbay-hub/src/meshbay_hub/api/hub.py b/packages/meshbay-hub/src/meshbay_hub/api/hub.py index 94e9b3c..cab60c8 100644 --- a/packages/meshbay-hub/src/meshbay_hub/api/hub.py +++ b/packages/meshbay-hub/src/meshbay_hub/api/hub.py @@ -1,9 +1,9 @@ """Hub info endpoints — /v1/hub/*""" from fastapi import APIRouter, Depends +from meshbay_common import MHP_VERSION, MNP_VERSION from sqlalchemy.ext.asyncio import AsyncSession -from meshbay_common import MNP_VERSION, MHP_VERSION from meshbay_hub import __version__, hub_settings from meshbay_hub.api import federation from meshbay_hub.auth import hub_public_key_pem diff --git a/packages/meshbay-hub/src/meshbay_hub/api/moderation.py b/packages/meshbay-hub/src/meshbay_hub/api/moderation.py index ee10cbc..35c688c 100644 --- a/packages/meshbay-hub/src/meshbay_hub/api/moderation.py +++ b/packages/meshbay-hub/src/meshbay_hub/api/moderation.py @@ -23,7 +23,6 @@ Node integration: """ import logging -from datetime import datetime, timezone from fastapi import APIRouter, Depends, HTTPException, Query, Request from pydantic import BaseModel diff --git a/packages/meshbay-hub/src/meshbay_hub/api/nodes.py b/packages/meshbay-hub/src/meshbay_hub/api/nodes.py index 83b60f2..7478173 100644 --- a/packages/meshbay-hub/src/meshbay_hub/api/nodes.py +++ b/packages/meshbay-hub/src/meshbay_hub/api/nodes.py @@ -1,20 +1,20 @@ """Node endpoints — /v1/nodes/*""" -from datetime import datetime, timezone import base64 import time +from datetime import UTC, datetime -from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey from cryptography.exceptions import InvalidSignature +from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey from fastapi import APIRouter, Depends, HTTPException, Request from pydantic import BaseModel from sqlalchemy import func, select from sqlalchemy.ext.asyncio import AsyncSession -from meshbay_hub.auth import issue_access_token from meshbay_hub.api.deps import get_current_user from meshbay_hub.api.middleware import limiter from meshbay_hub.api.netutil import client_ip +from meshbay_hub.auth import issue_access_token from meshbay_hub.db.engine import get_db from meshbay_hub.db.models import GroupMember, IPLog, Node, User @@ -156,7 +156,7 @@ async def announce_node( if node is not None: node.endpoint_hint = body.endpoint_hint node.observed_ip = seen_from - node.last_seen = datetime.now(timezone.utc) + node.last_seen = datetime.now(UTC) db.add(IPLog(user_id=current_user.id, event="node_announce", ip_address=seen_from, detail=body.endpoint_hint)) await db.commit() @@ -182,7 +182,7 @@ async def announce_node( pk_node=body.pk_node, endpoint_hint=body.endpoint_hint, observed_ip=seen_from, - last_seen=datetime.now(timezone.utc), + last_seen=datetime.now(UTC), ) db.add(node) db.add(IPLog( diff --git a/packages/meshbay-hub/src/meshbay_hub/api/notifications.py b/packages/meshbay-hub/src/meshbay_hub/api/notifications.py index b5783ab..d96ec18 100644 --- a/packages/meshbay-hub/src/meshbay_hub/api/notifications.py +++ b/packages/meshbay-hub/src/meshbay_hub/api/notifications.py @@ -19,9 +19,9 @@ migration for no gain, and `unread_only` stays because it is what an older interface asks for and it still answers correctly — every row is unread. """ -from fastapi import APIRouter, Depends, HTTPException, Query -from datetime import datetime, timezone +from datetime import UTC, datetime +from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy import delete, func, select from sqlalchemy.ext.asyncio import AsyncSession @@ -182,7 +182,7 @@ async def create_notification( existing.detail = detail existing.link = link existing.read = False - existing.created_at = datetime.now(timezone.utc) + existing.created_at = datetime.now(UTC) await db.flush() return existing diff --git a/packages/meshbay-hub/src/meshbay_hub/api/revocation.py b/packages/meshbay-hub/src/meshbay_hub/api/revocation.py index 1f1f5c5..f8cae8a 100644 --- a/packages/meshbay-hub/src/meshbay_hub/api/revocation.py +++ b/packages/meshbay-hub/src/meshbay_hub/api/revocation.py @@ -26,23 +26,21 @@ and close active connections for that user. """ import asyncio -import base64 import json import logging import time import uuid -from datetime import datetime, timezone -from typing import Any +from datetime import UTC, datetime +import jwt from fastapi import APIRouter, Depends, HTTPException, Request, WebSocket, WebSocketDisconnect +from meshbay_common.background import spawn from pydantic import BaseModel from sqlalchemy import select, update from sqlalchemy.ext.asyncio import AsyncSession -import jwt -from meshbay_common.background import spawn -from meshbay_hub.auth import hub_public_key_pem, decode_access_token from meshbay_hub.api.deps import get_current_user, require_admin +from meshbay_hub.auth import decode_access_token from meshbay_hub.db.engine import get_db from meshbay_hub.db.models import Group, GroupMember, IPLog, Node, User @@ -143,7 +141,7 @@ async def _mark_hosted(group_ids: list[str]) -> None: await db.execute( update(Group) .where(Group.id.in_(group_ids), Group.hosted_at.is_(None)) - .values(hosted_at=datetime.now(timezone.utc))) + .values(hosted_at=datetime.now(UTC))) await db.commit() except Exception as e: # A group that stays unhosted in the table is visible to its owner and @@ -169,7 +167,7 @@ async def broadcast_revocation(token: str) -> int: def _sign_revocation(target: str, target_id: str, reason: str) -> str: """Issue a signed revocation token (JWT EdDSA).""" - from meshbay_hub.auth import _hub_sk_pem, _hub_id + from meshbay_hub.auth import _hub_id, _hub_sk_pem now = int(time.time()) payload = { "type": "revocation", @@ -208,9 +206,9 @@ async def _handle_chat_notify(group_id: str, sender_name: str, sender_user_id: s (node_id or "?")[:8]) return try: - from meshbay_hub.db.engine import get_session_factory - from meshbay_hub.db.models import GroupMember, Group from meshbay_hub.api.notifications import create_notification + from meshbay_hub.db.engine import get_session_factory + from meshbay_hub.db.models import Group, GroupMember async with get_session_factory()() as db: group = await db.get(Group, group_id) @@ -478,7 +476,7 @@ async def notify_incoming( try: await asyncio.wait_for(event.wait(), timeout=5.0) - except asyncio.TimeoutError: + except TimeoutError: raise HTTPException(status_code=504, detail="Node did not respond in time") finally: _punch_events.pop(node_id, None) diff --git a/packages/meshbay-hub/src/meshbay_hub/api/signaling.py b/packages/meshbay-hub/src/meshbay_hub/api/signaling.py index bc03b45..8a10822 100644 --- a/packages/meshbay-hub/src/meshbay_hub/api/signaling.py +++ b/packages/meshbay-hub/src/meshbay_hub/api/signaling.py @@ -25,8 +25,8 @@ from sqlalchemy.ext.asyncio import AsyncSession from meshbay_hub import hub_settings from meshbay_hub.api.deps import get_current_user from meshbay_hub.api.middleware import limiter -from meshbay_hub.db.engine import get_db from meshbay_hub.api.netutil import client_ip +from meshbay_hub.db.engine import get_db from meshbay_hub.db.models import Group, GroupMember, IPLog, User log = logging.getLogger(__name__) @@ -167,7 +167,7 @@ async def webrtc_offer( try: answer = await asyncio.wait_for(answer_future, timeout=15.0) - except asyncio.TimeoutError: + except TimeoutError: raise HTTPException( status_code=504, detail="Node did not respond with WebRTC answer") diff --git a/packages/meshbay-hub/src/meshbay_hub/api/users.py b/packages/meshbay-hub/src/meshbay_hub/api/users.py index 0394f53..ee8aabc 100644 --- a/packages/meshbay-hub/src/meshbay_hub/api/users.py +++ b/packages/meshbay-hub/src/meshbay_hub/api/users.py @@ -6,7 +6,7 @@ import re import secrets import time import uuid -from datetime import datetime, timedelta, timezone +from datetime import UTC, datetime, timedelta from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey from fastapi import APIRouter, Depends, HTTPException, Request @@ -33,8 +33,17 @@ from meshbay_hub.auth import ( from meshbay_hub.config import HubConfig from meshbay_hub.db.engine import get_db from meshbay_hub.db.models import ( - EmailVerification, Group, GroupMember, IPLog, Node, Notification, - RefreshToken, SwarmSource, User, UserDevice, UserPreference, + EmailVerification, + Group, + GroupMember, + IPLog, + Node, + Notification, + RefreshToken, + SwarmSource, + User, + UserDevice, + UserPreference, ) log = logging.getLogger(__name__) @@ -61,7 +70,7 @@ async def _refresh_expiry(db: AsyncSession, family_id: str | None = None) -> dat renewals of a tab that is being used. """ limits = await hub_settings.session_limits(db) - now = datetime.now(timezone.utc) + now = datetime.now(UTC) idle = max(limits["refresh_idle_hours"] * 3600, _ttl() + 3600) started = now if family_id is not None: @@ -69,7 +78,7 @@ async def _refresh_expiry(db: AsyncSession, family_id: str | None = None) -> dat select(func.min(RefreshToken.created_at)) .where(RefreshToken.family_id == family_id)) if first is not None: - started = first if first.tzinfo else first.replace(tzinfo=timezone.utc) + started = first if first.tzinfo else first.replace(tzinfo=UTC) return min(now + timedelta(seconds=idle), started + timedelta(hours=limits["max_hours"])) @@ -193,7 +202,7 @@ async def register( EmailVerification.user_id == found.id, EmailVerification.purpose == "registration", EmailVerification.created_at - > datetime.now(timezone.utc) + > datetime.now(UTC) - timedelta(seconds=resend_cooldown), )) if not recent.first(): @@ -270,7 +279,7 @@ async def _create_and_send_verification( code=code, purpose="registration", user_id=user.id, - expires_at=datetime.now(timezone.utc) + timedelta(seconds=VERIFICATION_TTL), + expires_at=datetime.now(UTC) + timedelta(seconds=VERIFICATION_TTL), )) await db.flush() await mail.send_off_loop( @@ -292,7 +301,7 @@ async def verify_email( ): """Verify a registration email with the code received by mail.""" eh = hash_email_blind(body.email) - now = datetime.now(timezone.utc) + now = datetime.now(UTC) result = await db.execute( select(EmailVerification).where( @@ -306,7 +315,7 @@ async def verify_email( raise HTTPException(status_code=404, detail="No pending verification for this email") - if verif.expires_at.replace(tzinfo=timezone.utc) < now: + if verif.expires_at.replace(tzinfo=UTC) < now: raise HTTPException(status_code=410, detail="Verification code expired") if verif.attempts >= VERIFICATION_MAX_ATTEMPTS: @@ -603,7 +612,7 @@ async def device_auth( await db.commit() raise HTTPException(status_code=401, detail="Invalid signature") - matched.last_seen = datetime.now(timezone.utc) + matched.last_seen = datetime.now(UTC) memberships = await db.execute( select(GroupMember.group_id).where(GroupMember.user_id == user.id)) @@ -650,7 +659,7 @@ async def token_refresh( await db.commit() raise HTTPException(status_code=401, detail="Token reuse detected — family revoked") - if rt.expires_at.replace(tzinfo=timezone.utc) < datetime.now(timezone.utc): + if rt.expires_at.replace(tzinfo=UTC) < datetime.now(UTC): raise HTTPException(status_code=401, detail="Expired refresh token") user = await db.get(User, rt.user_id) @@ -659,7 +668,7 @@ async def token_refresh( # The family's first sign-in was longer ago than any session may last. expires_at = await _refresh_expiry(db, rt.family_id) - if expires_at <= datetime.now(timezone.utc): + if expires_at <= datetime.now(UTC): await db.execute( update(RefreshToken) .where(RefreshToken.family_id == rt.family_id) @@ -779,7 +788,7 @@ async def update_profile( cooldown = await hub_settings.get_int( db, "mail.email_change_cooldown", hub_settings.mail_default("email_change_cooldown")) - since = datetime.now(timezone.utc) - timedelta(seconds=cooldown) + since = datetime.now(UTC) - timedelta(seconds=cooldown) recent = await db.execute( select(IPLog).where( IPLog.user_id == current_user.id, @@ -820,7 +829,7 @@ async def update_profile( code=code, purpose="email_change", user_id=current_user.id, - expires_at=datetime.now(timezone.utc) + timedelta(seconds=VERIFICATION_TTL), + expires_at=datetime.now(UTC) + timedelta(seconds=VERIFICATION_TTL), )) db.add(IPLog(user_id=current_user.id, event="email_change_request", ip_address=client_ip(request))) @@ -860,7 +869,7 @@ async def verify_email_change( db: AsyncSession = Depends(get_db), ): """Confirm an email change with the code sent to the new address.""" - now = datetime.now(timezone.utc) + now = datetime.now(UTC) result = await db.execute( select(EmailVerification).where( @@ -874,7 +883,7 @@ async def verify_email_change( raise HTTPException(status_code=404, detail="No pending email change") - if verif.expires_at.replace(tzinfo=timezone.utc) < now: + if verif.expires_at.replace(tzinfo=UTC) < now: raise HTTPException(status_code=410, detail="Verification code expired") if verif.attempts >= VERIFICATION_MAX_ATTEMPTS: @@ -1090,7 +1099,7 @@ async def password_reset_request( EmailVerification.user_id == user.id, EmailVerification.purpose == "password_reset", EmailVerification.created_at - > datetime.now(timezone.utc) - timedelta(seconds=reset_cooldown), + > datetime.now(UTC) - timedelta(seconds=reset_cooldown), )) if recent.first(): return {"status": "sent_if_exists"} @@ -1110,7 +1119,7 @@ async def password_reset_request( code=code, purpose="password_reset", user_id=user.id, - expires_at=datetime.now(timezone.utc) + expires_at=datetime.now(UTC) + timedelta(seconds=PASSWORD_RESET_TTL), )) db.add(IPLog(user_id=user.id, event="password_reset_request", @@ -1141,7 +1150,7 @@ async def password_reset( request: Request, db: AsyncSession = Depends(get_db), ): - now = datetime.now(timezone.utc) + now = datetime.now(UTC) result = await db.execute(select(User).where(User.username == body.username)) user = result.scalar_one_or_none() if not user: @@ -1157,7 +1166,7 @@ async def password_reset( if not verif: raise HTTPException(status_code=404, detail="No pending reset for this account") - if verif.expires_at.replace(tzinfo=timezone.utc) < now: + if verif.expires_at.replace(tzinfo=UTC) < now: raise HTTPException(status_code=410, detail="Reset code expired") if verif.attempts >= VERIFICATION_MAX_ATTEMPTS: raise HTTPException(status_code=429, detail="Too many attempts") |