summaryrefslogtreecommitdiffstats
path: root/packages
diff options
context:
space:
mode:
Diffstat (limited to 'packages')
-rw-r--r--packages/meshbay-common/src/meshbay_common/background.py3
-rw-r--r--packages/meshbay-common/src/meshbay_common/crypto.py10
-rw-r--r--packages/meshbay-common/src/meshbay_common/keyderive.py6
-rw-r--r--packages/meshbay-common/src/meshbay_common/protocol.py31
-rw-r--r--packages/meshbay-common/src/meshbay_common/webcrypto.py3
-rw-r--r--packages/meshbay-common/tests/test_background_tasks.py1
-rw-r--r--packages/meshbay-common/tests/test_handshake.py5
-rw-r--r--packages/meshbay-common/tests/test_js_python_parity.py6
-rw-r--r--packages/meshbay-common/tests/test_keyderive.py4
-rw-r--r--packages/meshbay-common/tests/test_paths.py2
-rw-r--r--packages/meshbay-common/tests/test_webcrypto.py16
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/api/admin.py16
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/api/deps.py2
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/api/federation.py12
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/api/groups.py21
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/api/hub.py2
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/api/moderation.py1
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/api/nodes.py10
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/api/notifications.py6
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/api/relay.py3
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/api/revocation.py20
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/api/signaling.py4
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/api/users.py53
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/api/webapp.py3
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/app.py42
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/auth.py4
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/csam.py11
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/db/__init__.py4
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/db/migrations/env.py6
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/db/migrations/versions/a7b8c9d0e1f2_add_group_last_activity.py11
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/db/migrations/versions/a9b8c7d6e5f4_add_login_throttle.py8
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/db/migrations/versions/b1c2d3e4f5a6_add_hub_settings.py11
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/db/migrations/versions/c3d4e5f6a7b8_group_name_unique_per_owner.py11
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/db/migrations/versions/d28b9caf9f07_initial_schema.py11
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/db/migrations/versions/d4e5f6a7b8c9_add_email_verification.py11
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/db/migrations/versions/e5f6a7b8c9d0_add_mail_quota.py8
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/db/migrations/versions/f1a2b3c4d5e6_add_user_preferences.py11
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/db/models.py31
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/login_throttle.py10
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/mail.py24
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/tasks/cleanup.py13
-rw-r--r--packages/meshbay-hub/tests/conftest.py14
-rw-r--r--packages/meshbay-hub/tests/harness/boot_guard_probe.py3
-rw-r--r--packages/meshbay-hub/tests/harness/chat_send_probe.py1
-rwxr-xr-xpackages/meshbay-hub/tests/harness/menu_scroll_probe.py3
-rw-r--r--packages/meshbay-hub/tests/harness/music_grid_probe.py6
-rwxr-xr-xpackages/meshbay-hub/tests/harness/music_queue_probe.py3
-rwxr-xr-xpackages/meshbay-hub/tests/harness/playlist_store_probe.py6
-rw-r--r--packages/meshbay-hub/tests/harness/playlist_ui_probe.py12
-rw-r--r--packages/meshbay-hub/tests/test_account_deletion.py3
-rw-r--r--packages/meshbay-hub/tests/test_account_pinning.py1
-rw-r--r--packages/meshbay-hub/tests/test_admin_views.py6
-rw-r--r--packages/meshbay-hub/tests/test_asset_versioning.py1
-rw-r--r--packages/meshbay-hub/tests/test_availability_between_members.py8
-rw-r--r--packages/meshbay-hub/tests/test_captcha_host_check.py2
-rw-r--r--packages/meshbay-hub/tests/test_device_auth.py2
-rw-r--r--packages/meshbay-hub/tests/test_files_drop_upload.py3
-rw-r--r--packages/meshbay-hub/tests/test_group_hosting.py21
-rw-r--r--packages/meshbay-hub/tests/test_group_leave_and_quota.py7
-rw-r--r--packages/meshbay-hub/tests/test_group_membership.py3
-rw-r--r--packages/meshbay-hub/tests/test_group_purge.py13
-rw-r--r--packages/meshbay-hub/tests/test_groups_self_service.py8
-rw-r--r--packages/meshbay-hub/tests/test_hook_ordering.py3
-rw-r--r--packages/meshbay-hub/tests/test_hub_api.py45
-rw-r--r--packages/meshbay-hub/tests/test_layout_measured.py6
-rw-r--r--packages/meshbay-hub/tests/test_locales.py9
-rw-r--r--packages/meshbay-hub/tests/test_login_lockout.py9
-rw-r--r--packages/meshbay-hub/tests/test_mail_is_not_a_relay.py13
-rw-r--r--packages/meshbay-hub/tests/test_memory_ceiling.py6
-rw-r--r--packages/meshbay-hub/tests/test_migrations_reach_head.py3
-rw-r--r--packages/meshbay-hub/tests/test_node_auth.py2
-rw-r--r--packages/meshbay-hub/tests/test_node_ws_auth.py6
-rw-r--r--packages/meshbay-hub/tests/test_notification_dismissal.py1
-rw-r--r--packages/meshbay-hub/tests/test_notifications.py1
-rw-r--r--packages/meshbay-hub/tests/test_notifications_behaviour.py7
-rw-r--r--packages/meshbay-hub/tests/test_packaging_hub_unit.py1
-rw-r--r--packages/meshbay-hub/tests/test_password_change.py3
-rw-r--r--packages/meshbay-hub/tests/test_password_reset.py4
-rw-r--r--packages/meshbay-hub/tests/test_public_groups_toggle.py4
-rw-r--r--packages/meshbay-hub/tests/test_revocation.py4
-rw-r--r--packages/meshbay-hub/tests/test_session_lifetime.py13
-rw-r--r--packages/meshbay-hub/tests/test_session_renewal.py2
-rw-r--r--packages/meshbay-hub/tests/test_table_rows_measured.py3
-rw-r--r--packages/meshbay-hub/tests/test_transfers.py5
-rw-r--r--packages/meshbay-node/src/meshbay_node/config.py4
-rw-r--r--packages/meshbay-node/src/meshbay_node/daemon.py42
-rw-r--r--packages/meshbay-node/src/meshbay_node/hub_client.py2
-rw-r--r--packages/meshbay-node/src/meshbay_node/indexer/__init__.py4
-rw-r--r--packages/meshbay-node/src/meshbay_node/indexer/enrich.py6
-rw-r--r--packages/meshbay-node/src/meshbay_node/indexer/enrich_photo.py4
-rw-r--r--packages/meshbay-node/src/meshbay_node/indexer/group_index.py19
-rw-r--r--packages/meshbay-node/src/meshbay_node/indexer/indexer.py11
-rw-r--r--packages/meshbay-node/src/meshbay_node/indexer/title_parse.py5
-rw-r--r--packages/meshbay-node/src/meshbay_node/keystore.py6
-rw-r--r--packages/meshbay-node/src/meshbay_node/ops.py10
-rw-r--r--packages/meshbay-node/src/meshbay_node/replication.py2
-rw-r--r--packages/meshbay-node/src/meshbay_node/revocation.py155
-rw-r--r--packages/meshbay-node/src/meshbay_node/roots.py4
-rw-r--r--packages/meshbay-node/src/meshbay_node/roster.py12
-rw-r--r--packages/meshbay-node/src/meshbay_node/transfers.py2
-rw-r--r--packages/meshbay-node/src/meshbay_node/transport/__init__.py4
-rw-r--r--packages/meshbay-node/src/meshbay_node/transport/quic_client.py7
-rw-r--r--packages/meshbay-node/src/meshbay_node/transport/quic_server.py11
-rw-r--r--packages/meshbay-node/src/meshbay_node/transport/tls_cert.py9
-rw-r--r--packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py132
-rw-r--r--packages/meshbay-node/src/meshbay_node/ui/app.py2
-rw-r--r--packages/meshbay-node/src/meshbay_node/uploads.py2
-rw-r--r--packages/meshbay-node/tests/test_admin_ops_mnp.py5
-rw-r--r--packages/meshbay-node/tests/test_app_directories.py1
-rw-r--r--packages/meshbay-node/tests/test_app_directories_signed.py1
-rw-r--r--packages/meshbay-node/tests/test_apps_enabled_policy.py3
-rw-r--r--packages/meshbay-node/tests/test_audio_root_gates_enrichment.py3
-rw-r--r--packages/meshbay-node/tests/test_audio_transcode.py6
-rw-r--r--packages/meshbay-node/tests/test_chat_is_bounded.py1
-rw-r--r--packages/meshbay-node/tests/test_chat_multidevice.py3
-rw-r--r--packages/meshbay-node/tests/test_chat_pagination.py2
-rw-r--r--packages/meshbay-node/tests/test_chat_store.py3
-rw-r--r--packages/meshbay-node/tests/test_cli_dispatch.py3
-rw-r--r--packages/meshbay-node/tests/test_daemon.py13
-rw-r--r--packages/meshbay-node/tests/test_device_linking.py4
-rw-r--r--packages/meshbay-node/tests/test_enrich.py8
-rw-r--r--packages/meshbay-node/tests/test_enrich_photo.py6
-rw-r--r--packages/meshbay-node/tests/test_group_roster.py4
-rw-r--r--packages/meshbay-node/tests/test_hot_reload_survives_client_close.py10
-rw-r--r--packages/meshbay-node/tests/test_hub_client.py13
-rw-r--r--packages/meshbay-node/tests/test_hub_ws_group_claim.py1
-rw-r--r--packages/meshbay-node/tests/test_ice_filter.py2
-rw-r--r--packages/meshbay-node/tests/test_index_cache.py1
-rw-r--r--packages/meshbay-node/tests/test_index_delta_carries_roots.py2
-rw-r--r--packages/meshbay-node/tests/test_index_no_cleartext.py3
-rw-r--r--packages/meshbay-node/tests/test_index_progress.py7
-rw-r--r--packages/meshbay-node/tests/test_indexer.py20
-rw-r--r--packages/meshbay-node/tests/test_keystore.py6
-rw-r--r--packages/meshbay-node/tests/test_leaseless_reads.py5
-rw-r--r--packages/meshbay-node/tests/test_media_cache.py3
-rw-r--r--packages/meshbay-node/tests/test_media_cache_eviction.py1
-rw-r--r--packages/meshbay-node/tests/test_multi_group.py17
-rw-r--r--packages/meshbay-node/tests/test_musicbrainz_enabled_policy.py3
-rw-r--r--packages/meshbay-node/tests/test_node_status.py23
-rw-r--r--packages/meshbay-node/tests/test_packaging_win.py5
-rw-r--r--packages/meshbay-node/tests/test_partial_uploads.py20
-rw-r--r--packages/meshbay-node/tests/test_peer_session_limits.py6
-rw-r--r--packages/meshbay-node/tests/test_platform.py2
-rw-r--r--packages/meshbay-node/tests/test_poster_cache.py1
-rw-r--r--packages/meshbay-node/tests/test_quic_transport.py10
-rw-r--r--packages/meshbay-node/tests/test_rename_reenrichment.py3
-rw-r--r--packages/meshbay-node/tests/test_replug_restores_enrichment.py4
-rw-r--r--packages/meshbay-node/tests/test_root_availability.py2
-rw-r--r--packages/meshbay-node/tests/test_root_eject.py1
-rw-r--r--packages/meshbay-node/tests/test_root_ops_reach_the_live_set.py1
-rw-r--r--packages/meshbay-node/tests/test_root_paths_are_operator_only.py1
-rw-r--r--packages/meshbay-node/tests/test_root_writable_policy.py6
-rw-r--r--packages/meshbay-node/tests/test_roots.py12
-rw-r--r--packages/meshbay-node/tests/test_roster_pairing.py7
-rw-r--r--packages/meshbay-node/tests/test_scan_settings_policy.py1
-rw-r--r--packages/meshbay-node/tests/test_season_and_search_requests.py1
-rw-r--r--packages/meshbay-node/tests/test_security_regressions.py33
-rw-r--r--packages/meshbay-node/tests/test_startup_scan_enrichment.py5
-rw-r--r--packages/meshbay-node/tests/test_stream_audio_track_selection.py1
-rw-r--r--packages/meshbay-node/tests/test_stream_audio_transcode.py3
-rw-r--r--packages/meshbay-node/tests/test_stream_capacity.py9
-rw-r--r--packages/meshbay-node/tests/test_stream_capacity_config.py1
-rw-r--r--packages/meshbay-node/tests/test_stream_seek_audio_alignment.py1
-rw-r--r--packages/meshbay-node/tests/test_stream_seek_reports_the_keyframe.py1
-rw-r--r--packages/meshbay-node/tests/test_stream_subtitle_tracks.py1
-rw-r--r--packages/meshbay-node/tests/test_stream_video_transcode.py1
-rw-r--r--packages/meshbay-node/tests/test_title_parse.py1
-rw-r--r--packages/meshbay-node/tests/test_tmdb.py1
-rw-r--r--packages/meshbay-node/tests/test_tmdb_config_policy.py3
-rw-r--r--packages/meshbay-node/tests/test_tmdb_enabled_policy.py3
-rw-r--r--packages/meshbay-node/tests/test_tmdb_language_fallback.py1
-rw-r--r--packages/meshbay-node/tests/test_tmdb_override_policy.py3
-rw-r--r--packages/meshbay-node/tests/test_tmdb_rematch_policy.py3
-rw-r--r--packages/meshbay-node/tests/test_tmdb_show_director.py1
-rw-r--r--packages/meshbay-node/tests/test_transfer_settings.py6
-rw-r--r--packages/meshbay-node/tests/test_transfer_slots.py14
-rw-r--r--packages/meshbay-node/tests/test_transfer_slots_wire.py7
-rw-r--r--packages/meshbay-node/tests/test_transport_wire_parity.py3
-rw-r--r--packages/meshbay-node/tests/test_upload_size_cap.py1
-rw-r--r--packages/meshbay-node/tests/test_video_root_gates_enrichment.py3
-rw-r--r--packages/meshbay-node/tests/test_webrtc_transport.py44
-rw-r--r--packages/meshbay-node/tests/test_windows_root_shapes.py1
-rw-r--r--packages/meshbay-node/tests/test_wizard_apps_endpoint.py4
-rwxr-xr-xpackages/meshbay-node/tests/transfer_probe.py12
184 files changed, 748 insertions, 883 deletions
diff --git a/packages/meshbay-common/src/meshbay_common/background.py b/packages/meshbay-common/src/meshbay_common/background.py
index 1f25dfd..0afc3e8 100644
--- a/packages/meshbay-common/src/meshbay_common/background.py
+++ b/packages/meshbay-common/src/meshbay_common/background.py
@@ -30,7 +30,8 @@ from __future__ import annotations
import asyncio
import logging
-from typing import Any, Coroutine
+from collections.abc import Coroutine
+from typing import Any
log = logging.getLogger(__name__)
diff --git a/packages/meshbay-common/src/meshbay_common/crypto.py b/packages/meshbay-common/src/meshbay_common/crypto.py
index eadb42b..e537500 100644
--- a/packages/meshbay-common/src/meshbay_common/crypto.py
+++ b/packages/meshbay-common/src/meshbay_common/crypto.py
@@ -5,17 +5,17 @@ Validated in Spike 1 and Spike 6 of the POC.
All operations use PyCA cryptography (OpenSSL-backed, hardware-accelerated).
"""
-import os
import base64
+import os
+import blake3
+from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey, Ed25519PublicKey
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey, X25519PublicKey
+from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305
-from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from cryptography.hazmat.primitives.kdf.argon2 import Argon2id
-from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
-from cryptography.hazmat.primitives import hashes, serialization
-import blake3
+from cryptography.hazmat.primitives.kdf.hkdf import HKDF
# ── Key serialisation helpers ─────────────────────────────────────────────────
diff --git a/packages/meshbay-common/src/meshbay_common/keyderive.py b/packages/meshbay-common/src/meshbay_common/keyderive.py
index 497f877..4b90af3 100644
--- a/packages/meshbay-common/src/meshbay_common/keyderive.py
+++ b/packages/meshbay-common/src/meshbay_common/keyderive.py
@@ -24,11 +24,11 @@ See keyderive.js for the browser-side implementation.
"""
import hashlib
+
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey
from cryptography.hazmat.primitives.kdf.argon2 import Argon2id
-
# Argon2id parameters — same as keystore (see crypto.py)
_ITERATIONS = 3
_MEMORY_COST = 65536 # 64 MB — increase to 262144 for production
@@ -85,9 +85,11 @@ def encrypt_keypair_bundle(
Returns: AES-256-GCM ciphertext (nonce prepended).
"""
import os
+
+ import msgpack
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
+
from meshbay_common.crypto import sk_to_raw
- import msgpack
# Derive an AES key from the password (different info string from key derivation)
salt = hashlib.sha256(f"meshbay:bundle:v1:{username}".encode()).digest()
diff --git a/packages/meshbay-common/src/meshbay_common/protocol.py b/packages/meshbay-common/src/meshbay_common/protocol.py
index c444711..5374742 100644
--- a/packages/meshbay-common/src/meshbay_common/protocol.py
+++ b/packages/meshbay-common/src/meshbay_common/protocol.py
@@ -32,12 +32,11 @@ which local promise a reply belongs to.
import os
from dataclasses import dataclass, field
-from typing import Any
# The wire versions live in meshbay_common/__init__.py — one source, because a
# second copy here said "0.1" while every message on the wire carried "0.2".
# Nothing imported it, which is the only reason it was harmless.
-from meshbay_common import MNP_VERSION, MHP_VERSION # noqa: F401 (re-export)
+from meshbay_common import MHP_VERSION, MNP_VERSION # noqa: F401 (re-export)
from meshbay_common.groupbox import PURPOSE_UPLOAD, seal, unseal
from meshbay_common.webcrypto import (
chunk_key_aes,
@@ -45,7 +44,6 @@ from meshbay_common.webcrypto import (
encrypt_chunk_aes,
)
-
# ── MNP message types ─────────────────────────────────────────────────────────
class MNP:
@@ -70,7 +68,8 @@ class MNP:
# and no client: removed rather than repaired.
#
# Not a Double Ratchet message, and never was — finding C1
- # (`docs/MESHBAY_DESIGN.md` §13.1) rejected exactly that for groups. Since MNP 2.0 it is AES-256-GCM under a
+ # (`docs/MESHBAY_DESIGN.md` §13.1) rejected exactly that for groups. Since
+ # MNP 2.0 it is AES-256-GCM under a
# per-device subkey of the group's chat epoch key, signed over the
# ciphertext with the sending device's pinned Ed25519 key. There is no
# plaintext form on the wire (`chatbox.py`, docs/MESHBAY_DESIGN.md §4.5);
@@ -160,21 +159,21 @@ class MNP:
MEMBER_UNPIN_ACK = "member_unpin_ack"
APPS_ENABLED = "apps_enabled" # operator → node: which group apps to show
APPS_ENABLED_ACK = "apps_enabled_ack"
- TRANSFER_LIMITS = "transfer_limits" # operator → node: per-member caps for this group
+ TRANSFER_LIMITS = "transfer_limits" # operator → node: per-member caps here
TRANSFER_LIMITS_ACK = "transfer_limits_ack" # node → this group: the new caps
SET_SCAN_SETTINGS = "set_scan_settings" # operator → node: reconcile/debounce timing
SET_SCAN_SETTINGS_ACK = "set_scan_settings_ack"
MEDIA_META_REQ = "media_meta_req" # client → node: TMDB metadata for a path
MEDIA_META_RESP = "media_meta_resp" # node → client: TMDB metadata (or none)
- TMDB_CONFIG = "tmdb_config" # operator → node: set custom TMDB token/language (node-wide)
- TMDB_CONFIG_ACK = "tmdb_config_ack" # node → everyone: new TMDB config (never the token)
- TMDB_ENABLED = "tmdb_enabled" # operator → node: enable/disable TMDB for this group
- TMDB_ENABLED_ACK = "tmdb_enabled_ack" # node → this group: new per-group TMDB enabled state
- SEASON_META_REQ = "season_meta_req" # client → node: TMDB overview/poster for one season
- SEASON_META_RESP = "season_meta_resp" # node → client: season-level TMDB fields (or none)
- TMDB_SEARCH_REQ = "tmdb_search_req" # client → node: candidate TMDB matches for a query
- TMDB_SEARCH_RESP = "tmdb_search_resp" # node → client: candidate list (id, title, year, poster)
- TMDB_OVERRIDE = "tmdb_override" # operator → node: replace a show/movie's TMDB match
+ TMDB_CONFIG = "tmdb_config" # operator → node: token/language, node-wide
+ TMDB_CONFIG_ACK = "tmdb_config_ack" # node → everyone: config, never the token
+ TMDB_ENABLED = "tmdb_enabled" # operator → node: TMDB on/off here
+ TMDB_ENABLED_ACK = "tmdb_enabled_ack" # node → this group: TMDB on/off here
+ SEASON_META_REQ = "season_meta_req" # client → node: one season's overview
+ SEASON_META_RESP = "season_meta_resp" # node → client: season fields, or none
+ TMDB_SEARCH_REQ = "tmdb_search_req" # client → node: candidates for a query
+ TMDB_SEARCH_RESP = "tmdb_search_resp" # node → client: id, title, year, poster
+ TMDB_OVERRIDE = "tmdb_override" # operator → node: replace a match
TMDB_OVERRIDE_ACK = "tmdb_override_ack"
TMDB_REMATCH = "tmdb_rematch" # operator → node: drop one file's match
TMDB_REMATCH_ACK = "tmdb_rematch_ack"
@@ -257,7 +256,7 @@ class MNP:
# key without having to reconnect.
CHAT_EPOCH = "chat_epoch"
CHAT_EPOCH_ACK = "chat_epoch_ack"
- ROOT_UPDATE = "root_update" # operator → node: change writable/removable on a root
+ ROOT_UPDATE = "root_update" # operator → node: a root's flags
ROOT_UPDATE_ACK = "root_update_ack"
ROOT_EJECT = "root_eject" # operator → node: mark removable root as ejected
ROOT_EJECT_ACK = "root_eject_ack"
@@ -271,7 +270,7 @@ class MNP:
# node's `sender_id` (Tier 2, docs/MESHBAY_DESIGN.md §3.3).
GROUP_ROSTER_REQ = "group_roster_req"
GROUP_ROSTER_RESP = "group_roster_resp"
- ROSTER_READ = "roster_read" # operator → node: list pinned identities + members
+ ROSTER_READ = "roster_read" # operator → node: identities + members
ROSTER_READ_ACK = "roster_read_ack"
DENYLIST_READ = "denylist_read" # operator → node: show denylist entries
DENYLIST_READ_ACK = "denylist_read_ack"
diff --git a/packages/meshbay-common/src/meshbay_common/webcrypto.py b/packages/meshbay-common/src/meshbay_common/webcrypto.py
index 58958f8..3bd5ae6 100644
--- a/packages/meshbay-common/src/meshbay_common/webcrypto.py
+++ b/packages/meshbay-common/src/meshbay_common/webcrypto.py
@@ -26,9 +26,10 @@ This ensures AES and ChaCha20 keys are always distinct even from the same GEK.
"""
import os
+
+from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
-from cryptography.hazmat.primitives import hashes
def chunk_key_aes(gek: bytes, file_hash: bytes, chunk_index: int) -> bytes:
diff --git a/packages/meshbay-common/tests/test_background_tasks.py b/packages/meshbay-common/tests/test_background_tasks.py
index 4f0ac59..d01235b 100644
--- a/packages/meshbay-common/tests/test_background_tasks.py
+++ b/packages/meshbay-common/tests/test_background_tasks.py
@@ -28,7 +28,6 @@ import logging
from pathlib import Path
import pytest
-
from meshbay_common import background
REPO = Path(__file__).resolve().parents[3]
diff --git a/packages/meshbay-common/tests/test_handshake.py b/packages/meshbay-common/tests/test_handshake.py
index d4f6d2b..11313aa 100644
--- a/packages/meshbay-common/tests/test_handshake.py
+++ b/packages/meshbay-common/tests/test_handshake.py
@@ -12,7 +12,6 @@ import jwt
import pytest
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
-
from meshbay_common.handshake import (
HANDSHAKE_PREFIX,
NONCE_LEN,
@@ -207,8 +206,8 @@ def test_membership_refusal_carries_a_code_a_client_can_act_on():
is exactly the kind of coupling that breaks when someone improves a message.
"""
import jwt as _jwt
- from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from cryptography.hazmat.primitives import serialization
+ from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
sk = Ed25519PrivateKey.generate()
pem_priv = sk.private_bytes(
@@ -234,8 +233,8 @@ def test_a_group_this_node_does_not_host_is_refused_with_a_code():
dark on 2026-09-11 with its real host online — or had to match on wording.
"""
import jwt as _jwt
- from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from cryptography.hazmat.primitives import serialization
+ from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
sk = Ed25519PrivateKey.generate()
pem_priv = sk.private_bytes(
diff --git a/packages/meshbay-common/tests/test_js_python_parity.py b/packages/meshbay-common/tests/test_js_python_parity.py
index 9893a9f..3887414 100644
--- a/packages/meshbay-common/tests/test_js_python_parity.py
+++ b/packages/meshbay-common/tests/test_js_python_parity.py
@@ -21,7 +21,6 @@ import tempfile
from pathlib import Path
import pytest
-
from meshbay_common.adminop import admin_transcript
from meshbay_common.handshake import handshake_transcript, webrtc_binding
from meshbay_common.join import join_transcript
@@ -312,7 +311,6 @@ def _groupbox_payload(idx: int) -> dict:
@pytest.fixture(scope="module")
def groupbox_js(tmp_path_factory):
import msgpack
-
from meshbay_common.groupbox import seal
d = tmp_path_factory.mktemp("groupbox-parity")
@@ -486,7 +484,6 @@ def chatbox_js(tmp_path_factory):
from cryptography.hazmat.primitives.asymmetric.ed25519 import (
Ed25519PrivateKey,
)
-
from meshbay_common.chatbox import seal
d = tmp_path_factory.mktemp("chatbox-parity")
@@ -537,7 +534,6 @@ def test_browser_opens_a_chat_message_python_sealed(idx, vector, chatbox_js):
@pytest.mark.parametrize("idx,vector", list(enumerate(CHAT_VECTORS)))
def test_python_opens_a_chat_message_the_browser_sealed(idx, vector, chatbox_js):
import msgpack
-
from meshbay_common.chatbox import open_message
js, vectors = chatbox_js
@@ -580,7 +576,6 @@ def test_a_message_does_not_open_under_another_epoch(chatbox_js):
would round-trip against itself and pass every other test here.
"""
import pytest as _pytest
-
from meshbay_common.chatbox import open_message
_js, vectors = chatbox_js
@@ -598,7 +593,6 @@ def test_a_message_does_not_open_under_another_devices_key(chatbox_js):
any coordination — the property per-device ratchet chains were wanted for.
"""
import pytest as _pytest
-
from meshbay_common.chatbox import open_message
_js, vectors = chatbox_js
diff --git a/packages/meshbay-common/tests/test_keyderive.py b/packages/meshbay-common/tests/test_keyderive.py
index 0aa6201..40b3c71 100644
--- a/packages/meshbay-common/tests/test_keyderive.py
+++ b/packages/meshbay-common/tests/test_keyderive.py
@@ -1,12 +1,12 @@
"""Tests for password-based key derivation."""
import pytest
+from meshbay_common.crypto import pk_to_b64
from meshbay_common.keyderive import (
+ decrypt_keypair_bundle,
derive_keys_from_password,
encrypt_keypair_bundle,
- decrypt_keypair_bundle,
)
-from meshbay_common.crypto import pk_to_b64
def test_deterministic():
diff --git a/packages/meshbay-common/tests/test_paths.py b/packages/meshbay-common/tests/test_paths.py
index b9052e3..223a2a6 100644
--- a/packages/meshbay-common/tests/test_paths.py
+++ b/packages/meshbay-common/tests/test_paths.py
@@ -7,7 +7,6 @@ one file, and whether a member can save what they downloaded.
"""
import pytest
-
from meshbay_common.paths import (
find_fold_collisions,
fold,
@@ -18,7 +17,6 @@ from meshbay_common.paths import (
sanitize_for_download,
)
-
# ── Same file or not ─────────────────────────────────────────────────────────
def test_case_differences_fold_together():
diff --git a/packages/meshbay-common/tests/test_webcrypto.py b/packages/meshbay-common/tests/test_webcrypto.py
index 2ed6405..fbffdc1 100644
--- a/packages/meshbay-common/tests/test_webcrypto.py
+++ b/packages/meshbay-common/tests/test_webcrypto.py
@@ -1,10 +1,11 @@
"""Tests for AES-256-GCM webcrypto variant."""
import os
-import pytest
+
import blake3
+import pytest
from meshbay_common.crypto import generate_gek
-from meshbay_common.webcrypto import chunk_key_aes, encrypt_chunk_aes, decrypt_chunk_aes
+from meshbay_common.webcrypto import chunk_key_aes, decrypt_chunk_aes, encrypt_chunk_aes
def test_aes_roundtrip():
@@ -49,7 +50,10 @@ def test_aes_chunk_keys_unique_per_chunk():
def test_aes_gek_wrap_unwrap_roundtrip():
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey
from meshbay_common.crypto import (
- wrap_gek_aes, unwrap_gek_aes, sk_to_raw, pk_to_raw,
+ pk_to_raw,
+ sk_to_raw,
+ unwrap_gek_aes,
+ wrap_gek_aes,
)
gek = generate_gek()
sk = X25519PrivateKey.generate()
@@ -63,7 +67,7 @@ def test_aes_gek_wrap_unwrap_roundtrip():
def test_aes_gek_wrap_wrong_key_rejected():
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey
- from meshbay_common.crypto import wrap_gek_aes, unwrap_gek_aes, sk_to_raw, pk_to_raw
+ from meshbay_common.crypto import pk_to_raw, sk_to_raw, unwrap_gek_aes, wrap_gek_aes
gek = generate_gek()
sk_a = X25519PrivateKey.generate()
@@ -77,7 +81,9 @@ def test_aes_gek_wrap_wrong_key_rejected():
def test_aes_gek_wrap_differs_from_chacha_wrap():
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey
from meshbay_common.crypto import (
- wrap_gek, wrap_gek_aes, pk_to_raw,
+ pk_to_raw,
+ wrap_gek,
+ wrap_gek_aes,
)
gek = generate_gek()
sk = X25519PrivateKey.generate()
diff --git a/packages/meshbay-hub/src/meshbay_hub/api/admin.py b/packages/meshbay-hub/src/meshbay_hub/api/admin.py
index 7ca1e68..b4b2f4f 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__)
@@ -318,7 +317,8 @@ async def admin_patch_user(
if body.role not in ("user", "moderator", "admin"):
raise HTTPException(status_code=422, detail="role must be user, moderator, or admin")
user.role = body.role
- log.info("User %s role changed to %s by %s", user.username, body.role, current_user.username)
+ log.info("User %s role changed to %s by %s",
+ user.username, body.role, current_user.username)
await create_notification(
db, user.id, "role_change",
f"Your role has been changed to {body.role}",
@@ -326,7 +326,9 @@ async def admin_patch_user(
if body.status is not None:
if body.status not in ("active", "suspended", "revoked"):
- raise HTTPException(status_code=422, detail="status must be active, suspended, or revoked")
+ raise HTTPException(
+ status_code=422,
+ detail="status must be active, suspended, or revoked")
user.status = body.status
log.info("User %s status changed to %s by %s",
user.username, body.status, current_user.username)
@@ -484,7 +486,9 @@ async def admin_patch_group(
if body.status is not None:
if body.status not in ("active", "suspended", "revoked"):
- raise HTTPException(status_code=422, detail="status must be active, suspended, or revoked")
+ raise HTTPException(
+ status_code=422,
+ detail="status must be active, suspended, or revoked")
group.status = body.status
log.info("Group %s status changed to %s by %s",
group.name, body.status, current_user.username)
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/relay.py b/packages/meshbay-hub/src/meshbay_hub/api/relay.py
index 08d935b..7bb3f66 100644
--- a/packages/meshbay-hub/src/meshbay_hub/api/relay.py
+++ b/packages/meshbay-hub/src/meshbay_hub/api/relay.py
@@ -102,7 +102,8 @@ async def relay_register(
approved = _relays.get(body.relay_id)
if not approved or approved.get("pk") != body.pk_relay:
raise HTTPException(status_code=403,
- detail="Relay not approved — ask hub admin to run POST /v1/relays/approve")
+ detail="Relay not approved — ask the hub admin to "
+ "run POST /v1/relays/approve")
if body.timestamp is None or not body.signature:
raise HTTPException(
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..2666e86 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")
@@ -1303,7 +1312,9 @@ async def register_node_key(
if len(raw) != 32:
raise ValueError
except Exception:
- raise HTTPException(status_code=400, detail="Invalid Ed25519 public key (need 32 bytes base64)")
+ raise HTTPException(
+ status_code=400,
+ detail="Invalid Ed25519 public key (need 32 bytes base64)")
current_user.pk_node_ed25519 = body.pk_node_ed25519
await db.commit()
diff --git a/packages/meshbay-hub/src/meshbay_hub/api/webapp.py b/packages/meshbay-hub/src/meshbay_hub/api/webapp.py
index 3e23961..054d04a 100644
--- a/packages/meshbay-hub/src/meshbay_hub/api/webapp.py
+++ b/packages/meshbay-hub/src/meshbay_hub/api/webapp.py
@@ -168,7 +168,8 @@ _HTML = """\
though it had scrolled away. This asks for the keyboard to resize the
layout viewport instead, so what is pinned stays where it is looked at.
Ignored by browsers that do not know it. -->
- <meta name="viewport" content="width=device-width, initial-scale=1, interactive-widget=resizes-content">
+ <meta name="viewport"
+ content="width=device-width, initial-scale=1, interactive-widget=resizes-content">
<title>MeshBay</title>
<link rel="stylesheet" href="/a/{v}/style.css">
</head>
diff --git a/packages/meshbay-hub/src/meshbay_hub/app.py b/packages/meshbay-hub/src/meshbay_hub/app.py
index afc1cde..209dc32 100644
--- a/packages/meshbay-hub/src/meshbay_hub/app.py
+++ b/packages/meshbay-hub/src/meshbay_hub/app.py
@@ -11,37 +11,41 @@ Usage:
import asyncio
from contextlib import asynccontextmanager
-from pathlib import Path
from fastapi import FastAPI
from slowapi import _rate_limit_exceeded_handler
from slowapi.errors import RateLimitExceeded
from meshbay_hub import __version__
+from meshbay_hub.api.admin import router as admin_router
+from meshbay_hub.api.deps import set_admin_usernames
+from meshbay_hub.api.federation import router as federation_router
+from meshbay_hub.api.groups import router as groups_router
+from meshbay_hub.api.groups import swarm_router
+from meshbay_hub.api.health import router as health_router
+from meshbay_hub.api.hub import router as hub_router
+from meshbay_hub.api.hub import set_config as hub_set_config
+from meshbay_hub.api.middleware import limiter
+from meshbay_hub.api.moderation import router as moderation_router
+from meshbay_hub.api.nodes import router as nodes_router
+from meshbay_hub.api.notifications import router as notifications_router
+from meshbay_hub.api.relay import router as relay_router
+from meshbay_hub.api.revocation import router as revocation_router
+from meshbay_hub.api.signaling import router as signaling_router
+from meshbay_hub.api.users import router as users_router
+from meshbay_hub.api.users import set_config as users_set_config
+from meshbay_hub.api.webapp import ASSET_V, CSP, STATIC_DIR
+from meshbay_hub.api.webapp import router as webapp_router
from meshbay_hub.auth import generate_hub_keypair, load_hub_keypair
from meshbay_hub.config import HubConfig
+from meshbay_hub.csam import csam_router
from meshbay_hub.db.engine import close_db, init_db
-from meshbay_hub.api.hub import router as hub_router, set_config as hub_set_config
-from meshbay_hub.api.users import router as users_router, set_config as users_set_config
-from meshbay_hub.api.deps import set_admin_usernames
-from meshbay_hub.api.nodes import router as nodes_router
-from meshbay_hub.api.groups import router as groups_router, swarm_router
-from meshbay_hub.api.revocation import router as revocation_router
-from meshbay_hub.api.moderation import router as moderation_router
-from meshbay_hub.api.federation import router as federation_router
-from meshbay_hub.csam import csam_router
-from meshbay_hub.api.health import router as health_router
-from meshbay_hub.api.relay import router as relay_router
-from meshbay_hub.api.signaling import router as signaling_router
-from meshbay_hub.api.admin import router as admin_router
-from meshbay_hub.api.notifications import router as notifications_router
-from meshbay_hub.api.webapp import router as webapp_router, STATIC_DIR, ASSET_V, CSP
-from meshbay_hub.api.middleware import limiter
async def _sync_admin_roles(admin_usernames: list[str]) -> None:
"""Ensure config-listed admin usernames have role='admin' in the DB."""
- from sqlalchemy import select, update
+ from sqlalchemy import select
+
from meshbay_hub.db.engine import get_session_factory
from meshbay_hub.db.models import User
@@ -122,8 +126,8 @@ def create_app(cfg: HubConfig | None = None) -> FastAPI:
from meshbay_hub.csam import get_csam_checker
get_csam_checker().load()
- from meshbay_hub.tasks.cleanup import cleanup_loop
from meshbay_hub.db.engine import get_session_factory
+ from meshbay_hub.tasks.cleanup import cleanup_loop
cleanup_task = asyncio.create_task(cleanup_loop(get_session_factory()))
yield
diff --git a/packages/meshbay-hub/src/meshbay_hub/auth.py b/packages/meshbay-hub/src/meshbay_hub/auth.py
index 7045197..d038027 100644
--- a/packages/meshbay-hub/src/meshbay_hub/auth.py
+++ b/packages/meshbay-hub/src/meshbay_hub/auth.py
@@ -18,12 +18,12 @@ from pathlib import Path
import blake3
import jwt
-from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from cryptography.hazmat.primitives import serialization
+from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
+from cryptography.hazmat.primitives.hashes import SHA256
from cryptography.hazmat.primitives.kdf.argon2 import Argon2id
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
-from cryptography.hazmat.primitives.hashes import SHA256
# Argon2id parameters — versioned for gradual migration
_ARGON2_LANES = 4
diff --git a/packages/meshbay-hub/src/meshbay_hub/csam.py b/packages/meshbay-hub/src/meshbay_hub/csam.py
index 2a0ce25..b8e8d04 100644
--- a/packages/meshbay-hub/src/meshbay_hub/csam.py
+++ b/packages/meshbay-hub/src/meshbay_hub/csam.py
@@ -19,11 +19,14 @@ IMPORTANT: Never log matched hashes or file contents. CSAM detection
must be reported to NCMEC (US law) or relevant authority immediately.
"""
-import hashlib
import logging
-import os
from pathlib import Path
+from fastapi import APIRouter, Depends, HTTPException
+
+from meshbay_hub.api.deps import require_admin
+from meshbay_hub.db.models import User
+
log = logging.getLogger(__name__)
# Default path for the CSAM hash database (blake3 hex hashes, one per line)
@@ -123,10 +126,6 @@ def check_content_hash(blake3_hex: str) -> bool:
# ── Hub API integration ───────────────────────────────────────────────────────
-from fastapi import APIRouter, Depends, HTTPException, UploadFile, File
-from meshbay_hub.api.deps import require_admin
-from meshbay_hub.db.models import User
-
csam_router = APIRouter(prefix="/v1/admin/csam", tags=["csam"])
diff --git a/packages/meshbay-hub/src/meshbay_hub/db/__init__.py b/packages/meshbay-hub/src/meshbay_hub/db/__init__.py
index 62e5388..9d7483a 100644
--- a/packages/meshbay-hub/src/meshbay_hub/db/__init__.py
+++ b/packages/meshbay-hub/src/meshbay_hub/db/__init__.py
@@ -1,6 +1,6 @@
"""Hub database layer."""
-from .engine import init_db, close_db, get_db
-from .models import Base, User, Node, Group, GroupMember, RefreshToken, IPLog
+from .engine import close_db, get_db, init_db
+from .models import Base, Group, GroupMember, IPLog, Node, RefreshToken, User
__all__ = [
"init_db", "close_db", "get_db",
diff --git a/packages/meshbay-hub/src/meshbay_hub/db/migrations/env.py b/packages/meshbay-hub/src/meshbay_hub/db/migrations/env.py
index 8908deb..61f80be 100644
--- a/packages/meshbay-hub/src/meshbay_hub/db/migrations/env.py
+++ b/packages/meshbay-hub/src/meshbay_hub/db/migrations/env.py
@@ -4,14 +4,12 @@ import asyncio
import os
from logging.config import fileConfig
+from alembic import context
+from meshbay_hub.db.models import Base
from sqlalchemy import pool
from sqlalchemy.engine import Connection
from sqlalchemy.ext.asyncio import async_engine_from_config
-from alembic import context
-
-from meshbay_hub.db.models import Base
-
config = context.config
if config.config_file_name is not None:
diff --git a/packages/meshbay-hub/src/meshbay_hub/db/migrations/versions/a7b8c9d0e1f2_add_group_last_activity.py b/packages/meshbay-hub/src/meshbay_hub/db/migrations/versions/a7b8c9d0e1f2_add_group_last_activity.py
index 8a4e2ee..034d12f 100644
--- a/packages/meshbay-hub/src/meshbay_hub/db/migrations/versions/a7b8c9d0e1f2_add_group_last_activity.py
+++ b/packages/meshbay-hub/src/meshbay_hub/db/migrations/versions/a7b8c9d0e1f2_add_group_last_activity.py
@@ -5,16 +5,15 @@ Revises: f1a2b3c4d5e6
Create Date: 2026-08-20 20:50:00.000000
"""
-from typing import Sequence, Union
+from collections.abc import Sequence
-from alembic import op
import sqlalchemy as sa
-
+from alembic import op
revision: str = 'a7b8c9d0e1f2'
-down_revision: Union[str, Sequence[str], None] = 'f1a2b3c4d5e6'
-branch_labels: Union[str, Sequence[str], None] = None
-depends_on: Union[str, Sequence[str], None] = None
+down_revision: str | Sequence[str] | None = 'f1a2b3c4d5e6'
+branch_labels: str | Sequence[str] | None = None
+depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
diff --git a/packages/meshbay-hub/src/meshbay_hub/db/migrations/versions/a9b8c7d6e5f4_add_login_throttle.py b/packages/meshbay-hub/src/meshbay_hub/db/migrations/versions/a9b8c7d6e5f4_add_login_throttle.py
index 2fead6c..45985e2 100644
--- a/packages/meshbay-hub/src/meshbay_hub/db/migrations/versions/a9b8c7d6e5f4_add_login_throttle.py
+++ b/packages/meshbay-hub/src/meshbay_hub/db/migrations/versions/a9b8c7d6e5f4_add_login_throttle.py
@@ -7,15 +7,15 @@ Revision ID: a9b8c7d6e5f4
Revises: e5f6a7b8c9d0
"""
-from typing import Sequence, Union
+from collections.abc import Sequence
import sqlalchemy as sa
from alembic import op
revision: str = "a9b8c7d6e5f4"
-down_revision: Union[str, Sequence[str], None] = "e5f6a7b8c9d0"
-branch_labels: Union[str, Sequence[str], None] = None
-depends_on: Union[str, Sequence[str], None] = None
+down_revision: str | Sequence[str] | None = "e5f6a7b8c9d0"
+branch_labels: str | Sequence[str] | None = None
+depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
diff --git a/packages/meshbay-hub/src/meshbay_hub/db/migrations/versions/b1c2d3e4f5a6_add_hub_settings.py b/packages/meshbay-hub/src/meshbay_hub/db/migrations/versions/b1c2d3e4f5a6_add_hub_settings.py
index 13f2b5c..9a90f7a 100644
--- a/packages/meshbay-hub/src/meshbay_hub/db/migrations/versions/b1c2d3e4f5a6_add_hub_settings.py
+++ b/packages/meshbay-hub/src/meshbay_hub/db/migrations/versions/b1c2d3e4f5a6_add_hub_settings.py
@@ -5,16 +5,15 @@ Revises: a7b8c9d0e1f2
Create Date: 2026-08-28 12:00:00.000000
"""
-from typing import Sequence, Union
+from collections.abc import Sequence
-from alembic import op
import sqlalchemy as sa
-
+from alembic import op
revision: str = 'b1c2d3e4f5a6'
-down_revision: Union[str, Sequence[str], None] = 'a7b8c9d0e1f2'
-branch_labels: Union[str, Sequence[str], None] = None
-depends_on: Union[str, Sequence[str], None] = None
+down_revision: str | Sequence[str] | None = 'a7b8c9d0e1f2'
+branch_labels: str | Sequence[str] | None = None
+depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
diff --git a/packages/meshbay-hub/src/meshbay_hub/db/migrations/versions/c3d4e5f6a7b8_group_name_unique_per_owner.py b/packages/meshbay-hub/src/meshbay_hub/db/migrations/versions/c3d4e5f6a7b8_group_name_unique_per_owner.py
index 6fc5b73..fea0583 100644
--- a/packages/meshbay-hub/src/meshbay_hub/db/migrations/versions/c3d4e5f6a7b8_group_name_unique_per_owner.py
+++ b/packages/meshbay-hub/src/meshbay_hub/db/migrations/versions/c3d4e5f6a7b8_group_name_unique_per_owner.py
@@ -10,16 +10,15 @@ its UUID — this only makes `name@owner` a dependable handle.
Pre-flight: abort if the data already violates it, with the offending
(admin_id, name) pairs listed, rather than silently renaming anyone's group.
"""
-from typing import Sequence, Union
+from collections.abc import Sequence
-from alembic import op
import sqlalchemy as sa
-
+from alembic import op
revision: str = 'c3d4e5f6a7b8'
-down_revision: Union[str, Sequence[str], None] = 'b1c2d3e4f5a6'
-branch_labels: Union[str, Sequence[str], None] = None
-depends_on: Union[str, Sequence[str], None] = None
+down_revision: str | Sequence[str] | None = 'b1c2d3e4f5a6'
+branch_labels: str | Sequence[str] | None = None
+depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
diff --git a/packages/meshbay-hub/src/meshbay_hub/db/migrations/versions/d28b9caf9f07_initial_schema.py b/packages/meshbay-hub/src/meshbay_hub/db/migrations/versions/d28b9caf9f07_initial_schema.py
index 6301426..a814f37 100644
--- a/packages/meshbay-hub/src/meshbay_hub/db/migrations/versions/d28b9caf9f07_initial_schema.py
+++ b/packages/meshbay-hub/src/meshbay_hub/db/migrations/versions/d28b9caf9f07_initial_schema.py
@@ -12,16 +12,15 @@ Revises:
Create Date: 2026-08-09 04:35:07.120021
"""
-from typing import Sequence, Union
+from collections.abc import Sequence
-from alembic import op
import sqlalchemy as sa
-
+from alembic import op
revision: str = 'd28b9caf9f07'
-down_revision: Union[str, Sequence[str], None] = None
-branch_labels: Union[str, Sequence[str], None] = None
-depends_on: Union[str, Sequence[str], None] = None
+down_revision: str | Sequence[str] | None = None
+branch_labels: str | Sequence[str] | None = None
+depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
diff --git a/packages/meshbay-hub/src/meshbay_hub/db/migrations/versions/d4e5f6a7b8c9_add_email_verification.py b/packages/meshbay-hub/src/meshbay_hub/db/migrations/versions/d4e5f6a7b8c9_add_email_verification.py
index 6741eb8..aa15b6d 100644
--- a/packages/meshbay-hub/src/meshbay_hub/db/migrations/versions/d4e5f6a7b8c9_add_email_verification.py
+++ b/packages/meshbay-hub/src/meshbay_hub/db/migrations/versions/d4e5f6a7b8c9_add_email_verification.py
@@ -9,16 +9,15 @@ Revises: c3d4e5f6a7b8
Create Date: 2026-08-31 14:00:00.000000
"""
-from typing import Sequence, Union
+from collections.abc import Sequence
-from alembic import op
import sqlalchemy as sa
-
+from alembic import op
revision: str = 'd4e5f6a7b8c9'
-down_revision: Union[str, Sequence[str], None] = 'c3d4e5f6a7b8'
-branch_labels: Union[str, Sequence[str], None] = None
-depends_on: Union[str, Sequence[str], None] = None
+down_revision: str | Sequence[str] | None = 'c3d4e5f6a7b8'
+branch_labels: str | Sequence[str] | None = None
+depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
diff --git a/packages/meshbay-hub/src/meshbay_hub/db/migrations/versions/e5f6a7b8c9d0_add_mail_quota.py b/packages/meshbay-hub/src/meshbay_hub/db/migrations/versions/e5f6a7b8c9d0_add_mail_quota.py
index 8f2e4d2..c729de4 100644
--- a/packages/meshbay-hub/src/meshbay_hub/db/migrations/versions/e5f6a7b8c9d0_add_mail_quota.py
+++ b/packages/meshbay-hub/src/meshbay_hub/db/migrations/versions/e5f6a7b8c9d0_add_mail_quota.py
@@ -8,15 +8,15 @@ Revision ID: e5f6a7b8c9d0
Revises: d4e5f6a7b8c9
"""
-from typing import Sequence, Union
+from collections.abc import Sequence
import sqlalchemy as sa
from alembic import op
revision: str = "e5f6a7b8c9d0"
-down_revision: Union[str, Sequence[str], None] = "d4e5f6a7b8c9"
-branch_labels: Union[str, Sequence[str], None] = None
-depends_on: Union[str, Sequence[str], None] = None
+down_revision: str | Sequence[str] | None = "d4e5f6a7b8c9"
+branch_labels: str | Sequence[str] | None = None
+depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
diff --git a/packages/meshbay-hub/src/meshbay_hub/db/migrations/versions/f1a2b3c4d5e6_add_user_preferences.py b/packages/meshbay-hub/src/meshbay_hub/db/migrations/versions/f1a2b3c4d5e6_add_user_preferences.py
index e30a4da..9f8e729 100644
--- a/packages/meshbay-hub/src/meshbay_hub/db/migrations/versions/f1a2b3c4d5e6_add_user_preferences.py
+++ b/packages/meshbay-hub/src/meshbay_hub/db/migrations/versions/f1a2b3c4d5e6_add_user_preferences.py
@@ -5,16 +5,15 @@ Revises: d28b9caf9f07
Create Date: 2026-08-19 12:00:00.000000
"""
-from typing import Sequence, Union
+from collections.abc import Sequence
-from alembic import op
import sqlalchemy as sa
-
+from alembic import op
revision: str = 'f1a2b3c4d5e6'
-down_revision: Union[str, Sequence[str], None] = 'd28b9caf9f07'
-branch_labels: Union[str, Sequence[str], None] = None
-depends_on: Union[str, Sequence[str], None] = None
+down_revision: str | Sequence[str] | None = 'd28b9caf9f07'
+branch_labels: str | Sequence[str] | None = None
+depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
diff --git a/packages/meshbay-hub/src/meshbay_hub/db/models.py b/packages/meshbay-hub/src/meshbay_hub/db/models.py
index 62e4a11..ac1828f 100644
--- a/packages/meshbay-hub/src/meshbay_hub/db/models.py
+++ b/packages/meshbay-hub/src/meshbay_hub/db/models.py
@@ -11,17 +11,23 @@ Tables:
"""
import uuid
-from datetime import datetime, timezone
+from datetime import UTC, datetime
from sqlalchemy import (
- Boolean, DateTime, ForeignKey, Index, Integer,
- String, Text, UniqueConstraint, text,
+ Boolean,
+ DateTime,
+ ForeignKey,
+ Index,
+ Integer,
+ String,
+ Text,
+ text,
)
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
def _now() -> datetime:
- return datetime.now(timezone.utc)
+ return datetime.now(UTC)
def _uuid() -> str:
return str(uuid.uuid4())
@@ -47,10 +53,12 @@ class User(Base):
# the node does the wrapping, nothing reads a key from this directory. Keys
# are generated per node and pinned there (meshbay_node/roster.py).
email_hash: Mapped[str | None] = mapped_column(String(64), nullable=True) # HMAC blind index
- pk_node_ed25519: Mapped[str | None] = mapped_column(String(64), nullable=True) # node daemon key
+ pk_node_ed25519: Mapped[str | None] = mapped_column(String(64), nullable=True)
hub_id: Mapped[str] = mapped_column(String(128), nullable=False)
- role: Mapped[str] = mapped_column(String(16), default="user") # user|moderator|admin
- status: Mapped[str] = mapped_column(String(16), default="active") # active|suspended|revoked
+ # user|moderator|admin
+ role: Mapped[str] = mapped_column(String(16), default="user")
+ # active|suspended|revoked
+ status: Mapped[str] = mapped_column(String(16), default="active")
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=_now)
nodes: Mapped[list["Node"]] = relationship(back_populates="user")
@@ -99,7 +107,8 @@ class Group(Base):
visibility: Mapped[str] = mapped_column(String(16), default="private") # public|private
join_policy: Mapped[str] = mapped_column(String(16), default="invite") # open|request|invite
description: Mapped[str | None] = mapped_column(String(512))
- status: Mapped[str] = mapped_column(String(16), default="active") # active|suspended|revoked
+ # active|suspended|revoked
+ status: Mapped[str] = mapped_column(String(16), default="active")
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=_now)
# First time a node registered on /v1/nodes/ws announcing that it hosts this
# group. Until then the group has no files, no key and nobody to serve it, so
@@ -376,7 +385,8 @@ class IPLog(Base):
__tablename__ = "ip_logs"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
- user_id: Mapped[str | None] = mapped_column(ForeignKey("users.id")) # null for failed logins
+ # null for failed logins
+ user_id: Mapped[str | None] = mapped_column(ForeignKey("users.id"))
# The name this account had, written when it is deleted. The username is
# released on deletion and the row itself is tombstoned, so the join that
# normally supplies the name would answer "deleted-3f9a1c" for exactly the
@@ -384,7 +394,8 @@ class IPLog(Base):
username: Mapped[str | None] = mapped_column(String(64))
event: Mapped[str] = mapped_column(String(32), nullable=False)
ip_address: Mapped[str] = mapped_column(String(45), nullable=False) # IPv4 or IPv6
- detail: Mapped[str | None] = mapped_column(String(256)) # e.g. username on fail
+ # e.g. username on fail
+ detail: Mapped[str | None] = mapped_column(String(256))
timestamp: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=_now)
user: Mapped["User | None"] = relationship(back_populates="ip_logs")
diff --git a/packages/meshbay-hub/src/meshbay_hub/login_throttle.py b/packages/meshbay-hub/src/meshbay_hub/login_throttle.py
index 3281088..bfd142e 100644
--- a/packages/meshbay-hub/src/meshbay_hub/login_throttle.py
+++ b/packages/meshbay-hub/src/meshbay_hub/login_throttle.py
@@ -24,7 +24,7 @@ locks somebody else's name from signing them out (§13.5b, AV26).
"""
import hashlib
-from datetime import datetime, timedelta, timezone
+from datetime import UTC, datetime, timedelta
from sqlalchemy import case, delete, select, update
from sqlalchemy.ext.asyncio import AsyncSession
@@ -39,7 +39,7 @@ def _key(username: str) -> str:
def _aware(dt: datetime) -> datetime:
# SQLite hands back naive datetimes for a timezone-aware column.
- return dt if dt.tzinfo is not None else dt.replace(tzinfo=timezone.utc)
+ return dt if dt.tzinfo is not None else dt.replace(tzinfo=UTC)
def _insert_for(db: AsyncSession):
@@ -64,7 +64,7 @@ async def reserve(db: AsyncSession, username: str) -> tuple[bool, int]:
if max_failures == 0:
return True, 0
- now = datetime.now(timezone.utc)
+ now = datetime.now(UTC)
window = timedelta(minutes=limits["lockout_minutes"])
window_start = now - window
key = _key(username)
@@ -102,7 +102,7 @@ async def locked_for(db: AsyncSession, username: str) -> int:
return 0
remaining = (_aware(row.last_failure_at)
+ timedelta(minutes=limits["lockout_minutes"])
- - datetime.now(timezone.utc)).total_seconds()
+ - datetime.now(UTC)).total_seconds()
return max(0, int(remaining + 0.999))
@@ -136,7 +136,7 @@ async def clear(db: AsyncSession, username: str) -> None:
async def purge_expired(db: AsyncSession) -> int:
"""Rows whose failures have aged out. Every unknown name typed creates one."""
limits = await hub_settings.login_limits(db)
- cutoff = datetime.now(timezone.utc) - timedelta(minutes=limits["lockout_minutes"])
+ cutoff = datetime.now(UTC) - timedelta(minutes=limits["lockout_minutes"])
result = await db.execute(
delete(LoginThrottle).where(LoginThrottle.last_failure_at < cutoff))
await db.commit()
diff --git a/packages/meshbay-hub/src/meshbay_hub/mail.py b/packages/meshbay-hub/src/meshbay_hub/mail.py
index 126ed45..1eb7c51 100644
--- a/packages/meshbay-hub/src/meshbay_hub/mail.py
+++ b/packages/meshbay-hub/src/meshbay_hub/mail.py
@@ -16,7 +16,7 @@ import asyncio
import hashlib
import logging
import smtplib
-from datetime import datetime, timedelta, timezone
+from datetime import UTC, datetime, timedelta
from email.message import EmailMessage
log = logging.getLogger(__name__)
@@ -79,7 +79,7 @@ async def _take(db, key: str, window: timedelta, ceiling: int,
"""
from meshbay_hub.db.models import MailQuota
- now = datetime.now(timezone.utc)
+ now = datetime.now(UTC)
row = await db.get(MailQuota, key)
if row is None:
row = MailQuota(key=key, window_start=now, count=0, last_sent=None)
@@ -87,14 +87,14 @@ async def _take(db, key: str, window: timedelta, ceiling: int,
started = row.window_start
if started.tzinfo is None:
- started = started.replace(tzinfo=timezone.utc)
+ started = started.replace(tzinfo=UTC)
if now - started >= window:
row.window_start, row.count = now, 0
if cooldown is not None and row.last_sent is not None:
last = row.last_sent
if last.tzinfo is None:
- last = last.replace(tzinfo=timezone.utc)
+ last = last.replace(tzinfo=UTC)
if now - last < cooldown:
raise MailRefused("too soon since the last message to this recipient")
@@ -131,12 +131,12 @@ async def _announce_exhaustion(scope: str) -> None:
try:
async with get_session_factory()() as db:
key = f"alert:{scope}"
- now = datetime.now(timezone.utc)
+ now = datetime.now(UTC)
row = await db.get(MailQuota, key)
if row is not None and row.last_sent is not None:
last = row.last_sent
if last.tzinfo is None:
- last = last.replace(tzinfo=timezone.utc)
+ last = last.replace(tzinfo=UTC)
if now - last < timedelta(hours=1):
return
if row is None:
@@ -202,9 +202,10 @@ async def reserve(db, purpose: str, address: str) -> None:
async def status(db) -> dict:
"""What the operator sees in the panel: is the hub still sending?"""
+ from sqlalchemy import func, select
+
from meshbay_hub import hub_settings
from meshbay_hub.db.models import MailQuota
- from sqlalchemy import func, select
limits = await hub_settings.mail_limits(db)
row = await db.get(MailQuota, "hour")
@@ -213,8 +214,8 @@ async def status(db) -> dict:
if row is not None:
started = row.window_start
if started.tzinfo is None:
- started = started.replace(tzinfo=timezone.utc)
- if datetime.now(timezone.utc) - started < timedelta(hours=1):
+ started = started.replace(tzinfo=UTC)
+ if datetime.now(UTC) - started < timedelta(hours=1):
used, window_start = row.count, started.isoformat()
recipients = await db.scalar(
@@ -244,10 +245,11 @@ async def status(db) -> dict:
async def purge_expired_quota(db) -> int:
"""Drop counters whose window has passed. Returns how many went."""
- from meshbay_hub.db.models import MailQuota
from sqlalchemy import delete
- cutoff = datetime.now(timezone.utc) - timedelta(days=1)
+ from meshbay_hub.db.models import MailQuota
+
+ cutoff = datetime.now(UTC) - timedelta(days=1)
result = await db.execute(
delete(MailQuota).where(MailQuota.window_start < cutoff))
await db.commit()
diff --git a/packages/meshbay-hub/src/meshbay_hub/tasks/cleanup.py b/packages/meshbay-hub/src/meshbay_hub/tasks/cleanup.py
index 5c52387..1ef7796 100644
--- a/packages/meshbay-hub/src/meshbay_hub/tasks/cleanup.py
+++ b/packages/meshbay-hub/src/meshbay_hub/tasks/cleanup.py
@@ -2,7 +2,7 @@
import asyncio
import logging
-from datetime import datetime, timedelta, timezone
+from datetime import UTC, datetime, timedelta
from sqlalchemy import delete, select
from sqlalchemy.ext.asyncio import AsyncSession
@@ -16,7 +16,7 @@ CLEANUP_INTERVAL_HOURS = 24
async def purge_old_ip_logs(db: AsyncSession, retention_days: int = RETENTION_DAYS) -> int:
- cutoff = datetime.now(timezone.utc) - timedelta(days=retention_days)
+ cutoff = datetime.now(UTC) - timedelta(days=retention_days)
result = await db.execute(delete(IPLog).where(IPLog.timestamp < cutoff))
await db.commit()
return result.rowcount
@@ -26,7 +26,7 @@ PENDING_USER_EXPIRY_DAYS = 7
async def purge_expired_verifications(db: AsyncSession) -> int:
- now = datetime.now(timezone.utc)
+ now = datetime.now(UTC)
result = await db.execute(
delete(EmailVerification).where(EmailVerification.expires_at < now))
await db.commit()
@@ -35,7 +35,7 @@ async def purge_expired_verifications(db: AsyncSession) -> int:
async def purge_stale_pending_users(db: AsyncSession,
expiry_days: int = PENDING_USER_EXPIRY_DAYS) -> int:
- cutoff = datetime.now(timezone.utc) - timedelta(days=expiry_days)
+ cutoff = datetime.now(UTC) - timedelta(days=expiry_days)
result = await db.execute(
delete(User).where(User.status == "pending", User.created_at < cutoff))
await db.commit()
@@ -50,7 +50,8 @@ async def cleanup_loop(get_session):
async with get_session() as db:
deleted = await purge_old_ip_logs(db)
if deleted:
- log.info("Purged %d IP log entries older than %d days", deleted, RETENTION_DAYS)
+ log.info("Purged %d IP log entries older than %d days",
+ deleted, RETENTION_DAYS)
expired = await purge_expired_verifications(db)
if expired:
log.info("Purged %d expired email verifications", expired)
@@ -93,7 +94,7 @@ async def find_unhosted_groups(db: AsyncSession, grace_days: int = UNHOSTED_GRAC
whole reason the column exists rather than a check against the live socket
registry, which would delete every group during a hub restart.
"""
- cutoff = datetime.now(timezone.utc) - timedelta(days=grace_days)
+ cutoff = datetime.now(UTC) - timedelta(days=grace_days)
result = await db.execute(
select(Group).where(Group.hosted_at.is_(None), Group.created_at < cutoff))
return list(result.scalars().all())
diff --git a/packages/meshbay-hub/tests/conftest.py b/packages/meshbay-hub/tests/conftest.py
index bf94464..bb0ed28 100644
--- a/packages/meshbay-hub/tests/conftest.py
+++ b/packages/meshbay-hub/tests/conftest.py
@@ -1,12 +1,12 @@
"""Shared pytest fixtures for hub tests."""
import os
+from pathlib import Path
+
import pytest
import pytest_asyncio
-from pathlib import Path
-from unittest.mock import AsyncMock
-from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from cryptography.hazmat.primitives import serialization
+from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
# Force SQLite in-memory for all hub tests
os.environ.setdefault("MESHBAY_DATABASE_URL", "sqlite+aiosqlite:///:memory:")
@@ -28,7 +28,13 @@ def hub_key_path(tmp_path_factory) -> Path:
@pytest.fixture
def hub_config(hub_key_path, tmp_path):
- from meshbay_hub.config import HubConfig, DatabaseConfig, ServerConfig, HubIdentityConfig, JWTConfig
+ from meshbay_hub.config import (
+ DatabaseConfig,
+ HubConfig,
+ HubIdentityConfig,
+ JWTConfig,
+ ServerConfig,
+ )
cfg = HubConfig(
db=DatabaseConfig(url="sqlite+aiosqlite:///:memory:"),
server=ServerConfig(host="127.0.0.1", port=8000),
diff --git a/packages/meshbay-hub/tests/harness/boot_guard_probe.py b/packages/meshbay-hub/tests/harness/boot_guard_probe.py
index 01751ba..b4a5e7c 100644
--- a/packages/meshbay-hub/tests/harness/boot_guard_probe.py
+++ b/packages/meshbay-hub/tests/harness/boot_guard_probe.py
@@ -83,7 +83,8 @@ const cases = [];
const post = (o) => fetch('/log', { method: 'POST', body: JSON.stringify(o) });
addEventListener('error', (e) => post({ error: 'page error: ' + (e.message || e) }));
addEventListener('unhandledrejection',
- (e) => post({ error: 'rejection: ' + ((e.reason && (e.reason.stack || e.reason.message)) || e.reason) }));
+ (e) => post({ error: 'rejection: '
+ + ((e.reason && (e.reason.stack || e.reason.message)) || e.reason) }));
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
const add = (src) => {
const f = document.createElement('iframe');
diff --git a/packages/meshbay-hub/tests/harness/chat_send_probe.py b/packages/meshbay-hub/tests/harness/chat_send_probe.py
index b6dd957..d343c50 100644
--- a/packages/meshbay-hub/tests/harness/chat_send_probe.py
+++ b/packages/meshbay-hub/tests/harness/chat_send_probe.py
@@ -96,7 +96,6 @@ def _page() -> str:
the page built itself would prove only that the page agrees with the page.
"""
import msgpack # noqa: F401 (imported for the failure it gives if absent)
-
from meshbay_common.groupbox import PURPOSE_CHAT_KEYS, seal
sealed = seal(GEK, PURPOSE_CHAT_KEYS, "chat_keys_resp", GROUP_ID,
diff --git a/packages/meshbay-hub/tests/harness/menu_scroll_probe.py b/packages/meshbay-hub/tests/harness/menu_scroll_probe.py
index 5fb9579..c4d3b87 100755
--- a/packages/meshbay-hub/tests/harness/menu_scroll_probe.py
+++ b/packages/meshbay-hub/tests/harness/menu_scroll_probe.py
@@ -48,7 +48,8 @@ import { Menu } from '/menu.js';
const LOGS = [];
addEventListener('error', (e) => LOGS.push('error: ' + (e.message || e)));
addEventListener('unhandledrejection',
- (e) => LOGS.push('rejection: ' + ((e.reason && (e.reason.stack || e.reason.message)) || e.reason)));
+ (e) => LOGS.push('rejection: '
+ + ((e.reason && (e.reason.stack || e.reason.message)) || e.reason)));
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
const frame = () => new Promise((r) => requestAnimationFrame(() => requestAnimationFrame(r)));
diff --git a/packages/meshbay-hub/tests/harness/music_grid_probe.py b/packages/meshbay-hub/tests/harness/music_grid_probe.py
index da73b1f..4f5b0ce 100644
--- a/packages/meshbay-hub/tests/harness/music_grid_probe.py
+++ b/packages/meshbay-hub/tests/harness/music_grid_probe.py
@@ -103,7 +103,8 @@ import { MusicPlayerBar } from '/music-player.js';
const LOGS = [];
addEventListener('error', (e) => LOGS.push('error: ' + (e.message || e)));
addEventListener('unhandledrejection',
- (e) => LOGS.push('rejection: ' + ((e.reason && (e.reason.stack || e.reason.message)) || e.reason)));
+ (e) => LOGS.push('rejection: '
+ + ((e.reason && (e.reason.stack || e.reason.message)) || e.reason)));
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
const waitFor = async (sel, tries = 60) => {
@@ -212,7 +213,8 @@ const clickMenu = async (i) => {
const last = rows[rows.length - 1];
const entry = { artist: label, top,
heading: heading ? heading.textContent : null,
- headingH: heading ? Math.round(heading.getBoundingClientRect().height) : null };
+ headingH: heading
+ ? Math.round(heading.getBoundingClientRect().height) : null };
if (last && Math.abs(last.top - top) < 8) last.cells.push(entry);
else rows.push({ top, cells: [entry] });
}
diff --git a/packages/meshbay-hub/tests/harness/music_queue_probe.py b/packages/meshbay-hub/tests/harness/music_queue_probe.py
index 483c093..9be440b 100755
--- a/packages/meshbay-hub/tests/harness/music_queue_probe.py
+++ b/packages/meshbay-hub/tests/harness/music_queue_probe.py
@@ -102,7 +102,8 @@ import { MusicPlayerBar } from '/music-player.js';
const LOGS = [];
addEventListener('error', (e) => LOGS.push('error: ' + (e.message || e)));
addEventListener('unhandledrejection',
- (e) => LOGS.push('rejection: ' + ((e.reason && (e.reason.stack || e.reason.message)) || e.reason)));
+ (e) => LOGS.push('rejection: '
+ + ((e.reason && (e.reason.stack || e.reason.message)) || e.reason)));
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
const waitFor = async (sel, tries = 60) => {
diff --git a/packages/meshbay-hub/tests/harness/playlist_store_probe.py b/packages/meshbay-hub/tests/harness/playlist_store_probe.py
index 6421426..9a54a0d 100755
--- a/packages/meshbay-hub/tests/harness/playlist_store_probe.py
+++ b/packages/meshbay-hub/tests/harness/playlist_store_probe.py
@@ -38,7 +38,8 @@ import { MANIFEST_KIND, bodyKind } from '/playlist-merge.js';
const LOGS = [];
addEventListener('error', (e) => LOGS.push('error: ' + (e.message || e)));
addEventListener('unhandledrejection',
- (e) => LOGS.push('rejection: ' + ((e.reason && (e.reason.stack || e.reason.message)) || e.reason)));
+ (e) => LOGS.push('rejection: '
+ + ((e.reason && (e.reason.stack || e.reason.message)) || e.reason)));
const USER = 'user-1';
const steps = [];
@@ -95,7 +96,8 @@ function fakeNode() {
await P.addTracks(USER, P.FAVORITES_ID, [track(9)], 'g1', 'Favoris');
steps.push({ step: 'after editing',
- list: (await P.listPlaylists(USER)).map((p) => ({ id: p.id, name: p.name, count: p.count })) });
+ list: (await P.listPlaylists(USER))
+ .map((p) => ({ id: p.id, name: p.name, count: p.count })) });
steps.push({ step: 'tracks read back',
tracks: (await P.getPlaylistTracks(USER, eveningId)).map((t) => ({
diff --git a/packages/meshbay-hub/tests/harness/playlist_ui_probe.py b/packages/meshbay-hub/tests/harness/playlist_ui_probe.py
index 2defed9..e6f99f7 100644
--- a/packages/meshbay-hub/tests/harness/playlist_ui_probe.py
+++ b/packages/meshbay-hub/tests/harness/playlist_ui_probe.py
@@ -101,7 +101,8 @@ import * as P from '/playlists.js';
const LOGS = [];
addEventListener('error', (e) => LOGS.push('error: ' + (e.message || e)));
addEventListener('unhandledrejection',
- (e) => LOGS.push('rejection: ' + ((e.reason && (e.reason.stack || e.reason.message)) || e.reason)));
+ (e) => LOGS.push('rejection: '
+ + ((e.reason && (e.reason.stack || e.reason.message)) || e.reason)));
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
const waitFor = async (sel, tries = 60) => {
@@ -256,7 +257,8 @@ const clickMenu = async (i) => {
open.click();
await waitFor('.music-detail .music-tracklist');
steps.push({ step: 'loaded into the queue',
- play: [...document.querySelectorAll('.music-detail .music-tracklist .music-track-title')]
+ play: [...document.querySelectorAll(
+ '.music-detail .music-tracklist .music-track-title')]
.map((e) => e.textContent) });
document.querySelector('.music-detail .video-close').click();
await sleep(150);
@@ -270,8 +272,10 @@ const clickMenu = async (i) => {
await clickLabel('A2-t2');
await sleep(300);
steps.push({ step: 'track removed',
- lists: (await P.listPlaylists('u1')).map((p) => ({ name: p.name, count: p.count })),
- tracks: (await P.getPlaylistTracks('u1', lists.find((p) => p.name === 'Soirée').id))
+ lists: (await P.listPlaylists('u1'))
+ .map((p) => ({ name: p.name, count: p.count })),
+ tracks: (await P.getPlaylistTracks('u1',
+ lists.find((p) => p.name === 'Soirée').id))
.map((tr) => tr.display_title) });
// 6. Delete it.
diff --git a/packages/meshbay-hub/tests/test_account_deletion.py b/packages/meshbay-hub/tests/test_account_deletion.py
index 3d3fee4..2653f0d 100644
--- a/packages/meshbay-hub/tests/test_account_deletion.py
+++ b/packages/meshbay-hub/tests/test_account_deletion.py
@@ -12,9 +12,8 @@ command.
import hashlib
import pytest
-from sqlalchemy import select
-
from meshbay_hub.db.models import GroupMember, Notification, RefreshToken, User
+from sqlalchemy import select
def _auth_key(password: str, username: str) -> str:
diff --git a/packages/meshbay-hub/tests/test_account_pinning.py b/packages/meshbay-hub/tests/test_account_pinning.py
index 192bd25..a1419c3 100644
--- a/packages/meshbay-hub/tests/test_account_pinning.py
+++ b/packages/meshbay-hub/tests/test_account_pinning.py
@@ -23,7 +23,6 @@ from pathlib import Path
import pytest
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
-
from meshbay_common.device import device_add_transcript
STATIC = (Path(__file__).resolve().parents[1]
diff --git a/packages/meshbay-hub/tests/test_admin_views.py b/packages/meshbay-hub/tests/test_admin_views.py
index 5017b76..da2d3c9 100644
--- a/packages/meshbay-hub/tests/test_admin_views.py
+++ b/packages/meshbay-hub/tests/test_admin_views.py
@@ -11,9 +11,8 @@ import base64
import hashlib
import pytest
-from sqlalchemy import select
-
from meshbay_hub.db.models import User
+from sqlalchemy import select
def _auth_key(password: str, username: str) -> str:
@@ -109,9 +108,8 @@ async def test_a_node_is_recorded_at_the_address_it_announced_from(
import base64
import time
- from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from cryptography.hazmat.primitives import serialization
-
+ from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from meshbay_hub.db.models import Node
admin = await _admin(client, db_session, "root4_test")
diff --git a/packages/meshbay-hub/tests/test_asset_versioning.py b/packages/meshbay-hub/tests/test_asset_versioning.py
index 0c93f5a..aa2dc6d 100644
--- a/packages/meshbay-hub/tests/test_asset_versioning.py
+++ b/packages/meshbay-hub/tests/test_asset_versioning.py
@@ -22,7 +22,6 @@ import re
import pytest
from fastapi.testclient import TestClient
-
from meshbay_hub.api.webapp import ASSET_V, STATIC_DIR, _asset_version
from meshbay_hub.app import create_app
diff --git a/packages/meshbay-hub/tests/test_availability_between_members.py b/packages/meshbay-hub/tests/test_availability_between_members.py
index 2be7c03..4f5cbfb 100644
--- a/packages/meshbay-hub/tests/test_availability_between_members.py
+++ b/packages/meshbay-hub/tests/test_availability_between_members.py
@@ -24,7 +24,6 @@ import time
import pytest
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey
-
from meshbay_common.crypto import pk_to_b64
@@ -158,8 +157,7 @@ async def test_one_node_cannot_spend_the_hubs_database_on_notifications():
without backpressure. The budget is what stops one group's node from
costing every other group on the instance.
"""
- from meshbay_hub.api.revocation import (
- NOTIFY_BURST, _notify_budget, _notify_window)
+ from meshbay_hub.api.revocation import NOTIFY_BURST, _notify_budget, _notify_window
node = "budget-node"
_notify_window.pop(node, None)
@@ -265,8 +263,7 @@ def test_a_node_cannot_answer_an_offer_it_was_never_sent():
"""
import asyncio
- from meshbay_hub.api.signaling import (
- _answer_owner, _webrtc_answers, handle_webrtc_answer)
+ from meshbay_hub.api.signaling import _answer_owner, _webrtc_answers, handle_webrtc_answer
loop = asyncio.new_event_loop()
try:
@@ -657,6 +654,7 @@ async def test_a_member_still_reaches_the_node_they_share_a_group_with(client):
class _AnsweringWS:
async def send_text(self, text):
import json as _json
+
from meshbay_hub.api.signaling import handle_webrtc_answer
msg = _json.loads(text)
answered.append(msg)
diff --git a/packages/meshbay-hub/tests/test_captcha_host_check.py b/packages/meshbay-hub/tests/test_captcha_host_check.py
index 68cad34..778009f 100644
--- a/packages/meshbay-hub/tests/test_captcha_host_check.py
+++ b/packages/meshbay-hub/tests/test_captcha_host_check.py
@@ -31,12 +31,10 @@ TOML list is a typo far more often than an intention.
import json
import pytest
-
from meshbay_hub.captcha import verify_captcha
from meshbay_hub.config import CaptchaConfig, load_config
-
class _Resp:
def __init__(self, payload):
self._payload = payload
diff --git a/packages/meshbay-hub/tests/test_device_auth.py b/packages/meshbay-hub/tests/test_device_auth.py
index e899f12..f9b172e 100644
--- a/packages/meshbay-hub/tests/test_device_auth.py
+++ b/packages/meshbay-hub/tests/test_device_auth.py
@@ -21,9 +21,7 @@ stopped being true.
import base64
import time
-import pytest
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
-
from meshbay_common.crypto import pk_to_b64
diff --git a/packages/meshbay-hub/tests/test_files_drop_upload.py b/packages/meshbay-hub/tests/test_files_drop_upload.py
index 5dfaf23..fc15c47 100644
--- a/packages/meshbay-hub/tests/test_files_drop_upload.py
+++ b/packages/meshbay-hub/tests/test_files_drop_upload.py
@@ -66,7 +66,8 @@ def test_names_in_a_folder_count_files_folders_and_empty_folders(tmp_path, sourc
{"path": "music/Album", "name": "t.flac"},
{"path": "musicals", "name": "not-here.txt"}]
got = _run(tmp_path, source,
- f"namesIn({json.dumps(entries)}, ['music/Empty', 'music/Album/cd1'], 'music').sort()")
+ f"namesIn({json.dumps(entries)}, "
+ f"['music/Empty', 'music/Album/cd1'], 'music').sort()")
assert got == ["Album", "Empty", "a.mp3"]
diff --git a/packages/meshbay-hub/tests/test_group_hosting.py b/packages/meshbay-hub/tests/test_group_hosting.py
index c571771..c6111f2 100644
--- a/packages/meshbay-hub/tests/test_group_hosting.py
+++ b/packages/meshbay-hub/tests/test_group_hosting.py
@@ -14,13 +14,12 @@ deleted every group during a hub restart.
import base64
import hashlib
-from datetime import datetime, timedelta, timezone
+from datetime import UTC, datetime, timedelta
import pytest
-from sqlalchemy import select
-
from meshbay_hub.db.models import Group, GroupMember
from meshbay_hub.tasks.cleanup import find_unhosted_groups, prune_unhosted_groups
+from sqlalchemy import select
def _auth_key(password: str, username: str) -> str:
@@ -49,7 +48,7 @@ async def _group(client, owner, name, visibility="private", join_policy=None):
async def _mark_hosted(db_session, group_id, when=None):
g = await db_session.get(Group, group_id)
- g.hosted_at = when or datetime.now(timezone.utc)
+ g.hosted_at = when or datetime.now(UTC)
await db_session.commit()
@@ -134,7 +133,7 @@ async def test_an_unhosted_group_past_the_grace_period_is_collected(client, db_s
gid = (await _group(client, owner, "abandoned")).json()["group_id"]
g = await db_session.get(Group, gid)
- g.created_at = datetime.now(timezone.utc) - timedelta(days=8)
+ g.created_at = datetime.now(UTC) - timedelta(days=8)
await db_session.commit()
gone = await prune_unhosted_groups(db_session)
@@ -149,8 +148,8 @@ async def test_an_old_group_that_was_hosted_is_never_collected(client, db_sessio
gid = (await _group(client, owner, "long-lived")).json()["group_id"]
g = await db_session.get(Group, gid)
- g.created_at = datetime.now(timezone.utc) - timedelta(days=400)
- g.hosted_at = datetime.now(timezone.utc) - timedelta(days=399)
+ g.created_at = datetime.now(UTC) - timedelta(days=400)
+ g.hosted_at = datetime.now(UTC) - timedelta(days=399)
await db_session.commit()
assert await find_unhosted_groups(db_session) == []
@@ -161,7 +160,7 @@ async def test_dry_run_reports_without_deleting(client, db_session):
owner = await _user(client, "reaper4_test")
gid = (await _group(client, owner, "still-here")).json()["group_id"]
g = await db_session.get(Group, gid)
- g.created_at = datetime.now(timezone.utc) - timedelta(days=30)
+ g.created_at = datetime.now(UTC) - timedelta(days=30)
await db_session.commit()
gone = await prune_unhosted_groups(db_session, dry_run=True)
@@ -178,7 +177,7 @@ async def test_collecting_a_group_takes_its_memberships_with_it(client, db_sessi
await client.post(f"/v1/groups/{gid}/members/tagalong", json={}, headers=owner)
g = await db_session.get(Group, gid)
- g.created_at = datetime.now(timezone.utc) - timedelta(days=9)
+ g.created_at = datetime.now(UTC) - timedelta(days=9)
await db_session.commit()
await prune_unhosted_groups(db_session)
@@ -247,7 +246,7 @@ async def test_the_stamp_is_not_moved_by_a_later_reconnection(client, db_session
owner = await _user(client, "stamped2")
gid = (await _group(client, owner, "steady")).json()["group_id"]
- first = datetime.now(timezone.utc) - timedelta(days=30)
+ first = datetime.now(UTC) - timedelta(days=30)
await _mark_hosted([gid])
g = await db_session.get(Group, gid)
g.hosted_at = first
@@ -259,7 +258,7 @@ async def test_the_stamp_is_not_moved_by_a_later_reconnection(client, db_session
await db_session.refresh(g)
# SQLite hands back a naive datetime where PostgreSQL keeps the offset, so
# the comparison is made on common ground rather than on the driver.
- stored = g.hosted_at.replace(tzinfo=timezone.utc) if g.hosted_at.tzinfo is None \
+ stored = g.hosted_at.replace(tzinfo=UTC) if g.hosted_at.tzinfo is None \
else g.hosted_at
assert abs((stored - first).total_seconds()) < 1, "the stamp moved"
diff --git a/packages/meshbay-hub/tests/test_group_leave_and_quota.py b/packages/meshbay-hub/tests/test_group_leave_and_quota.py
index 4431f53..8af830d 100644
--- a/packages/meshbay-hub/tests/test_group_leave_and_quota.py
+++ b/packages/meshbay-hub/tests/test_group_leave_and_quota.py
@@ -14,13 +14,12 @@ removes them, and whoever left still holds the group key they were served.
import base64
import hashlib
-from datetime import datetime, timezone
+from datetime import UTC, datetime
import pytest
-from sqlalchemy import select
-
from meshbay_hub.api.groups import MAX_PUBLIC_GROUPS
from meshbay_hub.db.models import Group, GroupMember, User
+from sqlalchemy import select
def _auth_key(password: str, username: str) -> str:
@@ -61,7 +60,7 @@ async def test_a_member_can_leave(client, db_session):
# Marked hosted, or the member would not see the group in the first place
# and the assertion below would hold whether or not leaving worked.
g = await db_session.get(Group, gid)
- g.hosted_at = datetime.now(timezone.utc)
+ g.hosted_at = datetime.now(UTC)
await db_session.commit()
before = await client.get("/v1/groups/mine", headers=member)
diff --git a/packages/meshbay-hub/tests/test_group_membership.py b/packages/meshbay-hub/tests/test_group_membership.py
index 2761595..ad1b3ab 100644
--- a/packages/meshbay-hub/tests/test_group_membership.py
+++ b/packages/meshbay-hub/tests/test_group_membership.py
@@ -11,10 +11,9 @@ import base64
import hashlib
import pytest
+from meshbay_hub.db.models import GroupMember, User
from sqlalchemy import select
-from meshbay_hub.db.models import Group, GroupMember, User
-
def _auth_key(password: str, username: str) -> str:
salt = hashlib.sha256(f"meshbay:auth:v1:{username}".encode()).digest()
diff --git a/packages/meshbay-hub/tests/test_group_purge.py b/packages/meshbay-hub/tests/test_group_purge.py
index 0611dba..e729297 100644
--- a/packages/meshbay-hub/tests/test_group_purge.py
+++ b/packages/meshbay-hub/tests/test_group_purge.py
@@ -21,13 +21,18 @@ from datetime import UTC, datetime, timedelta
import jwt
import pytest
-from sqlalchemy import delete, func, select, text
-from sqlalchemy.exc import IntegrityError
-
from meshbay_hub.db.models import (
- ContentReport, EmailVerification, Group, GroupMember, IPLog, Notification, User,
+ ContentReport,
+ EmailVerification,
+ Group,
+ GroupMember,
+ IPLog,
+ Notification,
+ User,
)
from meshbay_hub.db.purge import _referencing
+from sqlalchemy import delete, func, select, text
+from sqlalchemy.exc import IntegrityError
SEEDED = {"group_members", "notifications", "email_verifications", "content_reports"}
diff --git a/packages/meshbay-hub/tests/test_groups_self_service.py b/packages/meshbay-hub/tests/test_groups_self_service.py
index f9346c1..a3a8f2d 100644
--- a/packages/meshbay-hub/tests/test_groups_self_service.py
+++ b/packages/meshbay-hub/tests/test_groups_self_service.py
@@ -1,9 +1,10 @@
"""Integration tests for group self-service: create, join, members."""
+from datetime import UTC
+
import pytest
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey
-
from meshbay_common.crypto import pk_to_b64
@@ -43,10 +44,11 @@ async def _create_group(client, token, name="test-group", visibility="public",
async def _mark_hosted(db_session, *group_ids):
"""Pretend a node announced these groups, as /v1/nodes/ws would."""
- from datetime import datetime, timezone
+ from datetime import datetime
+
from meshbay_hub.db.models import Group
for gid in group_ids:
- (await db_session.get(Group, gid)).hosted_at = datetime.now(timezone.utc)
+ (await db_session.get(Group, gid)).hosted_at = datetime.now(UTC)
await db_session.commit()
diff --git a/packages/meshbay-hub/tests/test_hook_ordering.py b/packages/meshbay-hub/tests/test_hook_ordering.py
index 3c82bb0..d03cdf5 100644
--- a/packages/meshbay-hub/tests/test_hook_ordering.py
+++ b/packages/meshbay-hub/tests/test_hook_ordering.py
@@ -123,7 +123,8 @@ def test_the_check_would_notice():
# Inject a dependency on `last` into the first declaration's dep array.
end = broken.index("\n }, [", decls[0].start())
close = broken.index("]", end)
- broken = broken[:close] + (", " if broken[end + 7:close].strip() else "") + last + broken[close:]
+ broken = (broken[:close] + (", " if broken[end + 7:close].strip() else "")
+ + last + broken[close:])
declared_at = {m.group(1): m.start() for m in DECL.finditer(broken)}
caught = False
diff --git a/packages/meshbay-hub/tests/test_hub_api.py b/packages/meshbay-hub/tests/test_hub_api.py
index 37e8e4f..2afd27b 100644
--- a/packages/meshbay-hub/tests/test_hub_api.py
+++ b/packages/meshbay-hub/tests/test_hub_api.py
@@ -3,13 +3,11 @@ Integration tests for the Hub API.
Uses SQLite in-memory + httpx.AsyncClient — no PostgreSQL, no network.
"""
-import base64
+from datetime import UTC
+
import pytest
-import pytest_asyncio
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey
-from cryptography.hazmat.primitives import serialization
-
from meshbay_common.crypto import pk_to_b64
from meshbay_hub.api.deps import set_admin_usernames
@@ -32,7 +30,8 @@ async def _announce_signed(client, token: str) -> tuple[str, str]:
The node key is independent of the user's identity key, so this mints a fresh
one and signs the domain-separated announce message with it.
"""
- import base64 as _b64, time as _t
+ import base64 as _b64
+ import time as _t
me = await client.get("/v1/users/me",
headers={"Authorization": f"Bearer {token}"})
@@ -261,7 +260,9 @@ async def test_group_member_add(client):
"pk_user_ed25519": pk_ed, "pk_user_x25519": pk_x})
alice_token = (await client.post("/v1/users/login",
- json={"username": "alice2_test", "password": "alicepass99"})).json()["access_token"]
+ json={"username": "alice2_test",
+ "password": "alicepass99"})
+ ).json()["access_token"]
a_hdrs = {"Authorization": f"Bearer {alice_token}"}
@@ -299,9 +300,13 @@ async def test_non_admin_cannot_add_member(client):
"pk_user_ed25519": pk_ed, "pk_user_x25519": pk_x})
charlie_token = (await client.post("/v1/users/login",
- json={"username": "charlie_test", "password": "charliepass"})).json()["access_token"]
+ json={"username": "charlie_test",
+ "password": "charliepass"})
+ ).json()["access_token"]
dan_token = (await client.post("/v1/users/login",
- json={"username": "dan_test", "password": "danpass1234"})).json()["access_token"]
+ json={"username": "dan_test",
+ "password": "danpass1234"})
+ ).json()["access_token"]
r = await client.post("/v1/groups", json={"name": "charlies-group"},
headers={"Authorization": f"Bearer {charlie_token}"})
@@ -339,7 +344,9 @@ async def test_jwt_contains_groups_claim(client):
# Alice creates a group and adds Bob
alice_token = (await client.post("/v1/users/login",
- json={"username": "grp_alice", "password": "alicepass99"})).json()["access_token"]
+ json={"username": "grp_alice",
+ "password": "alicepass99"})
+ ).json()["access_token"]
r = await client.post("/v1/groups", json={"name": "testgroup"},
headers={"Authorization": f"Bearer {alice_token}"})
group_id = r.json()["group_id"]
@@ -399,9 +406,10 @@ async def test_my_groups(client, db_session):
# A group no node has announced is shown to its owner only — a member would
# otherwise see a name they cannot open. Stamped here so the rest of this
# test is about membership, which is what it was written for.
- from datetime import datetime, timezone
+ from datetime import datetime
+
from meshbay_hub.db.models import Group
- (await db_session.get(Group, group_id)).hosted_at = datetime.now(timezone.utc)
+ (await db_session.get(Group, group_id)).hosted_at = datetime.now(UTC)
await db_session.commit()
# Re-login to get fresh token with group claims
@@ -434,7 +442,6 @@ async def test_my_groups(client, db_session):
@pytest.mark.asyncio
async def test_group_online_nodes(client):
"""GET /v1/groups/{id}/nodes returns online nodes serving the group."""
- import json
from meshbay_hub.api.revocation import _connected_nodes, _node_groups
pk_ed, pk_x, _ = _gen_user_keys()
@@ -541,7 +548,7 @@ async def test_admin_can_revoke(client):
@pytest.mark.asyncio
async def test_email_encrypted_at_rest(client):
"""Email stored in DB must not contain plaintext address."""
- from meshbay_hub.auth import encrypt_email, decrypt_email
+ from meshbay_hub.auth import decrypt_email, encrypt_email
encrypted = encrypt_email("test@example.com")
assert "@" not in encrypted
assert decrypt_email(encrypted) == "test@example.com"
@@ -589,9 +596,10 @@ async def test_password_rehash_on_login(client, app):
user = result.scalar_one()
user.pw_version = 1
# Re-hash with v1 params so verify_password(version=1) succeeds
- from meshbay_hub.auth import _ARGON2_VERSIONS, _ARGON2_KEY_LEN, _ARGON2_LANES
- from cryptography.hazmat.primitives.kdf.argon2 import Argon2id
import os
+
+ from cryptography.hazmat.primitives.kdf.argon2 import Argon2id
+ from meshbay_hub.auth import _ARGON2_KEY_LEN, _ARGON2_LANES, _ARGON2_VERSIONS
salt = os.urandom(16)
params = _ARGON2_VERSIONS[1]
pw_hash = Argon2id(
@@ -760,13 +768,14 @@ async def test_webrtc_signaling_roundtrip(client, app):
@pytest.mark.asyncio
async def test_ip_log_cleanup(app):
"""Old IP log entries are purged by cleanup task."""
- from datetime import datetime, timezone, timedelta
+ from datetime import datetime, timedelta
+
from meshbay_hub.db.engine import get_db
from meshbay_hub.db.models import IPLog
from meshbay_hub.tasks.cleanup import purge_old_ip_logs
async for db in get_db():
- old_ts = datetime.now(timezone.utc) - timedelta(days=400)
+ old_ts = datetime.now(UTC) - timedelta(days=400)
db.add(IPLog(event="test_old", ip_address="1.2.3.4", timestamp=old_ts))
db.add(IPLog(event="test_recent", ip_address="5.6.7.8"))
await db.commit()
@@ -774,7 +783,7 @@ async def test_ip_log_cleanup(app):
deleted = await purge_old_ip_logs(db, retention_days=365)
assert deleted == 1
- from sqlalchemy import select, func
+ from sqlalchemy import func, select
count = (await db.execute(
select(func.count()).where(IPLog.event.in_(["test_old", "test_recent"]))
)).scalar_one()
diff --git a/packages/meshbay-hub/tests/test_layout_measured.py b/packages/meshbay-hub/tests/test_layout_measured.py
index a71b6b9..9bf1bde 100644
--- a/packages/meshbay-hub/tests/test_layout_measured.py
+++ b/packages/meshbay-hub/tests/test_layout_measured.py
@@ -178,7 +178,8 @@ GROUPED = textwrap.dedent("""
<button class="transfer-cancel">&#10005;</button>
</div>
<div class="dl-progress"><div class="dl-fill" style="width:42%"></div></div>
- <div class="transfer-meta"><span>210 MB / 493 MB</span><span>3.1 MB/s &middot; 4 min left</span></div>
+ <div class="transfer-meta"><span>210 MB / 493 MB</span>
+ <span>3.1 MB/s &middot; 4 min left</span></div>
</div>
</div>
<div class="transfer-group">
@@ -190,7 +191,8 @@ GROUPED = textwrap.dedent("""
<button class="transfer-cancel">&#10005;</button>
</div>
<div class="dl-progress dl-waiting"></div>
- <div class="transfer-meta"><span>Waiting &mdash; your slots are busy</span><span>1.2 GB</span></div>
+ <div class="transfer-meta"><span>Waiting &mdash; your slots are busy</span>
+ <span>1.2 GB</span></div>
</div>
</div>
</div>
diff --git a/packages/meshbay-hub/tests/test_locales.py b/packages/meshbay-hub/tests/test_locales.py
index 59ea2bf..602d161 100644
--- a/packages/meshbay-hub/tests/test_locales.py
+++ b/packages/meshbay-hub/tests/test_locales.py
@@ -158,7 +158,8 @@ def test_locale_resolution_is_region_aware(tmp_path):
const out = {};
for (const tags of [['pt-BR'], ['pt'], ['zh-CN'], ['zh'], ['fr-CA'],
['de-AT'], ['ru', 'it'], ['ko']]) {
- Object.defineProperty(globalThis, 'navigator', { value: { languages: tags, language: tags[0] }, configurable: true });
+ Object.defineProperty(globalThis, 'navigator',
+ { value: { languages: tags, language: tags[0] }, configurable: true });
delete store.mb_lang;
out[tags.join(',')] = await i18n.initLocale();
}
@@ -186,7 +187,8 @@ def test_counted_string_picks_the_right_polish_form(tmp_path):
setItem: (k, v) => { store[k] = v; },
};
globalThis.document = { documentElement: {} };
- Object.defineProperty(globalThis, 'navigator', { value: { languages: ['pl'], language: 'pl' }, configurable: true });
+ Object.defineProperty(globalThis, 'navigator',
+ { value: { languages: ['pl'], language: 'pl' }, configurable: true });
const i18n = await import('./i18n.js');
await i18n.initLocale();
console.log(JSON.stringify(
@@ -205,7 +207,8 @@ def test_interpolated_value_is_not_read_as_a_replacement_pattern(tmp_path):
setItem: (k, v) => { store[k] = v; },
};
globalThis.document = { documentElement: {} };
- Object.defineProperty(globalThis, 'navigator', { value: { languages: ['en'], language: 'en' }, configurable: true });
+ Object.defineProperty(globalThis, 'navigator',
+ { value: { languages: ['en'], language: 'en' }, configurable: true });
const i18n = await import('./i18n.js');
await i18n.initLocale();
console.log(JSON.stringify(
diff --git a/packages/meshbay-hub/tests/test_login_lockout.py b/packages/meshbay-hub/tests/test_login_lockout.py
index 6f17d98..601edbd 100644
--- a/packages/meshbay-hub/tests/test_login_lockout.py
+++ b/packages/meshbay-hub/tests/test_login_lockout.py
@@ -18,14 +18,13 @@ is `test_availability_between_members.py`, because it takes two accounts.
"""
import asyncio
-from datetime import datetime, timedelta, timezone
+from datetime import UTC, datetime, timedelta
import pytest
-from sqlalchemy import select, update
-
from meshbay_hub.api.deps import set_admin_usernames
from meshbay_hub.db.models import LoginThrottle
from meshbay_hub.login_throttle import _key
+from sqlalchemy import select, update
RIGHT = "r" * 44
WRONG = "w" * 44
@@ -94,7 +93,7 @@ async def test_a_lockout_ends_when_its_window_does(client, db_session):
await db_session.execute(
update(LoginThrottle).where(LoginThrottle.key == _key("dave_test"))
- .values(last_failure_at=datetime.now(timezone.utc) - timedelta(minutes=61)))
+ .values(last_failure_at=datetime.now(UTC) - timedelta(minutes=61)))
await db_session.commit()
assert (await _login(client, "dave_test", RIGHT)).status_code == 200
@@ -106,7 +105,7 @@ async def test_old_failures_do_not_carry_into_a_new_window(client, db_session):
await _fail(client, "erin_test", 3)
await db_session.execute(
update(LoginThrottle).where(LoginThrottle.key == _key("erin_test"))
- .values(last_failure_at=datetime.now(timezone.utc) - timedelta(minutes=61)))
+ .values(last_failure_at=datetime.now(UTC) - timedelta(minutes=61)))
await db_session.commit()
# One stale window of three, then one fresh failure: a count of one, not four.
diff --git a/packages/meshbay-hub/tests/test_mail_is_not_a_relay.py b/packages/meshbay-hub/tests/test_mail_is_not_a_relay.py
index fe6a5ee..a3f9a21 100644
--- a/packages/meshbay-hub/tests/test_mail_is_not_a_relay.py
+++ b/packages/meshbay-hub/tests/test_mail_is_not_a_relay.py
@@ -28,7 +28,6 @@ import base64
import pytest
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey
-
from meshbay_common.crypto import pk_to_b64
from meshbay_hub import mail as mail_mod
from meshbay_hub.api import users as users_mod
@@ -316,9 +315,8 @@ async def _signed_in(client, db_session, username: str, email: str) -> dict:
genuinely `pending` here, exactly as it would be in production, and a
pending account cannot log in.
"""
- from sqlalchemy import select, update
-
from meshbay_hub.db.models import User
+ from sqlalchemy import select, update
r = await _register(client, username, email)
assert r.status_code == 201, r.text
@@ -406,9 +404,8 @@ async def test_the_delay_survives_the_verification_row_being_deleted(
json={"email": "c-first@example.test"})
assert r.status_code == 200, r.text
- from sqlalchemy import delete
-
from meshbay_hub.db.models import EmailVerification
+ from sqlalchemy import delete
await db_session.execute(delete(EmailVerification))
await db_session.commit()
@@ -421,9 +418,8 @@ async def test_the_delay_survives_the_verification_row_being_deleted(
# ── The operator's controls ──────────────────────────────────────────────────
async def _admin(client, db_session, username: str) -> dict:
- from sqlalchemy import update
-
from meshbay_hub.db.models import User
+ from sqlalchemy import update
headers = await _signed_in(client, db_session, username,
f"{username}@example.test")
@@ -491,9 +487,8 @@ async def test_an_unknown_mail_setting_is_refused(client, db_session):
@pytest.mark.asyncio
async def test_a_moderator_may_read_the_bounds_but_not_change_them(
client, db_session):
- from sqlalchemy import update
-
from meshbay_hub.db.models import User
+ from sqlalchemy import update
headers = await _signed_in(client, db_session, "mailmod_test",
"mailmod@example.test")
diff --git a/packages/meshbay-hub/tests/test_memory_ceiling.py b/packages/meshbay-hub/tests/test_memory_ceiling.py
index 1654825..5984292 100644
--- a/packages/meshbay-hub/tests/test_memory_ceiling.py
+++ b/packages/meshbay-hub/tests/test_memory_ceiling.py
@@ -246,11 +246,11 @@ def test_no_unguarded_memory_floor(target_fn):
# definition out before looking. Comments go too — the branch that used to
# be the bug is now described in one, and a test that reads prose is the
# mistake already recorded in CLAUDE.md for the packaged systemd unit.
- start = next(n for n, l in enumerate(lines) if "const _memoryFloor" in l)
+ start = next(n for n, ln in enumerate(lines) if "const _memoryFloor" in ln)
end = next(n for n in range(start, len(lines)) if lines[n].strip() == "};")
rest = lines[:start] + lines[end + 1:]
- code = [re.sub(r"//.*$", "", l) for l in rest]
- bare = [l.strip() for l in code if re.search(r"\breturn null\b", l)]
+ code = [re.sub(r"//.*$", "", ln) for ln in rest]
+ bare = [ln.strip() for ln in code if re.search(r"\breturn null\b", ln)]
assert bare == [], (
"an unguarded in-memory fallback was added to _openDownloadTarget; "
"return _memoryFloor() instead: " + "; ".join(bare))
diff --git a/packages/meshbay-hub/tests/test_migrations_reach_head.py b/packages/meshbay-hub/tests/test_migrations_reach_head.py
index 13c5590..7f0d5df 100644
--- a/packages/meshbay-hub/tests/test_migrations_reach_head.py
+++ b/packages/meshbay-hub/tests/test_migrations_reach_head.py
@@ -30,9 +30,8 @@ from pathlib import Path
import pytest
from alembic import command
from alembic.config import Config
-from sqlalchemy import create_engine, inspect
-
from meshbay_hub.db.models import Base
+from sqlalchemy import create_engine, inspect
HUB = Path(__file__).resolve().parents[1]
diff --git a/packages/meshbay-hub/tests/test_node_auth.py b/packages/meshbay-hub/tests/test_node_auth.py
index 4137932..09816de 100644
--- a/packages/meshbay-hub/tests/test_node_auth.py
+++ b/packages/meshbay-hub/tests/test_node_auth.py
@@ -8,9 +8,9 @@ import base64
import time
import pytest
+from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey
-from cryptography.hazmat.primitives import serialization
def _gen_ed25519():
diff --git a/packages/meshbay-hub/tests/test_node_ws_auth.py b/packages/meshbay-hub/tests/test_node_ws_auth.py
index f3ac3a2..7d96851 100644
--- a/packages/meshbay-hub/tests/test_node_ws_auth.py
+++ b/packages/meshbay-hub/tests/test_node_ws_auth.py
@@ -16,7 +16,6 @@ import base64
import pytest
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey
-
from meshbay_common.crypto import pk_to_b64
@@ -405,7 +404,10 @@ async def test_empty_node_cannot_shadow_another_members_group(client):
source for that group, because it is the one clients would reach first.
"""
from meshbay_hub.api.revocation import (
- _authorize_node_ws, _node_groups, get_online_nodes_for_group)
+ _authorize_node_ws,
+ _node_groups,
+ get_online_nodes_for_group,
+ )
host = await _make_user(client, "hoster_test")
guest = await _make_user(client, "guest_test")
diff --git a/packages/meshbay-hub/tests/test_notification_dismissal.py b/packages/meshbay-hub/tests/test_notification_dismissal.py
index 1c9c7b3..d498b4d 100644
--- a/packages/meshbay-hub/tests/test_notification_dismissal.py
+++ b/packages/meshbay-hub/tests/test_notification_dismissal.py
@@ -32,7 +32,6 @@ from pathlib import Path
import pytest
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey
-
from meshbay_common.crypto import pk_to_b64
from meshbay_hub.api.deps import set_admin_usernames
diff --git a/packages/meshbay-hub/tests/test_notifications.py b/packages/meshbay-hub/tests/test_notifications.py
index 02aca1c..b87e1d5 100644
--- a/packages/meshbay-hub/tests/test_notifications.py
+++ b/packages/meshbay-hub/tests/test_notifications.py
@@ -3,7 +3,6 @@
import pytest
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey
-
from meshbay_common.crypto import pk_to_b64
from meshbay_hub.api.deps import set_admin_usernames
diff --git a/packages/meshbay-hub/tests/test_notifications_behaviour.py b/packages/meshbay-hub/tests/test_notifications_behaviour.py
index a247508..4684d3f 100644
--- a/packages/meshbay-hub/tests/test_notifications_behaviour.py
+++ b/packages/meshbay-hub/tests/test_notifications_behaviour.py
@@ -7,13 +7,12 @@ browser's localStorage and nothing read it), and there was no way to clear the
list.
"""
-import hashlib
import base64
+import hashlib
import pytest
-from sqlalchemy import select
-
from meshbay_hub.db.models import GroupMember, Notification, User
+from sqlalchemy import select
def _auth_key(password: str, username: str) -> str:
@@ -36,7 +35,7 @@ async def test_chat_keeps_one_notification_per_group(client, db_session):
"""Forty messages are one line saying when the conversation last spoke."""
from meshbay_hub.api.notifications import create_notification
- token = await _user(client, "listener")
+ await _user(client, "listener")
owner = await _user(client, "talker_test")
g = await client.post("/v1/groups", json={"name": "busy"},
headers={"Authorization": f"Bearer {owner}"})
diff --git a/packages/meshbay-hub/tests/test_packaging_hub_unit.py b/packages/meshbay-hub/tests/test_packaging_hub_unit.py
index 7d8f5d2..e29164e 100644
--- a/packages/meshbay-hub/tests/test_packaging_hub_unit.py
+++ b/packages/meshbay-hub/tests/test_packaging_hub_unit.py
@@ -85,7 +85,6 @@ def test_the_command_resolves_from_the_installed_package_not_the_checkout():
"""Derived from `meshbay_hub.__file__`, so it is correct in a venv, an RPM
and a checkout alike — which is the whole point of not writing it down."""
import meshbay_hub
-
from meshbay_hub.daemon import migrations_dir
assert migrations_dir() == (
diff --git a/packages/meshbay-hub/tests/test_password_change.py b/packages/meshbay-hub/tests/test_password_change.py
index b2fe586..bfe0c97 100644
--- a/packages/meshbay-hub/tests/test_password_change.py
+++ b/packages/meshbay-hub/tests/test_password_change.py
@@ -11,9 +11,8 @@ import base64
import hashlib
import pytest
-from sqlalchemy import select
-
from meshbay_hub.db.models import IPLog, RefreshToken, User
+from sqlalchemy import select
def _auth_key(password: str, username: str) -> str:
diff --git a/packages/meshbay-hub/tests/test_password_reset.py b/packages/meshbay-hub/tests/test_password_reset.py
index 823807c..b07f77c 100644
--- a/packages/meshbay-hub/tests/test_password_reset.py
+++ b/packages/meshbay-hub/tests/test_password_reset.py
@@ -9,7 +9,7 @@ the recovery key and is not exercised here.
import base64
import time
-from datetime import datetime, timedelta, timezone
+from datetime import UTC, datetime, timedelta
import pytest
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
@@ -126,7 +126,7 @@ async def test_an_expired_code_is_refused(client, db_session):
select(User.id).where(User.username == "carol_test"))).scalar_one()
row = (await db_session.execute(select(EmailVerification).where(
EmailVerification.user_id == uid))).scalars().one()
- row.expires_at = datetime.now(timezone.utc) - timedelta(minutes=1)
+ row.expires_at = datetime.now(UTC) - timedelta(minutes=1)
await db_session.commit()
r = await client.post("/v1/users/password/reset", json={
diff --git a/packages/meshbay-hub/tests/test_public_groups_toggle.py b/packages/meshbay-hub/tests/test_public_groups_toggle.py
index 96e3383..dbc6a38 100644
--- a/packages/meshbay-hub/tests/test_public_groups_toggle.py
+++ b/packages/meshbay-hub/tests/test_public_groups_toggle.py
@@ -17,7 +17,7 @@ and their access — plan A, not a purge.
import base64
import hashlib
-from datetime import datetime, timezone
+from datetime import UTC, datetime
import pytest
from meshbay_hub.api.deps import set_admin_usernames
@@ -61,7 +61,7 @@ async def _create_public(client, owner, name):
async def _mark_hosted(db_session, *group_ids):
"""Pretend a node announced these groups, as /v1/nodes/ws would."""
for gid in group_ids:
- (await db_session.get(Group, gid)).hosted_at = datetime.now(timezone.utc)
+ (await db_session.get(Group, gid)).hosted_at = datetime.now(UTC)
await db_session.commit()
diff --git a/packages/meshbay-hub/tests/test_revocation.py b/packages/meshbay-hub/tests/test_revocation.py
index ad9caf4..e2ca5e7 100644
--- a/packages/meshbay-hub/tests/test_revocation.py
+++ b/packages/meshbay-hub/tests/test_revocation.py
@@ -1,11 +1,9 @@
"""Tests for revocation — admin endpoint + token signing."""
-import time
-import pytest
import jwt
+import pytest
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey
-
from meshbay_common.crypto import pk_to_b64
from meshbay_hub.api.deps import set_admin_usernames
diff --git a/packages/meshbay-hub/tests/test_session_lifetime.py b/packages/meshbay-hub/tests/test_session_lifetime.py
index af73767..3a1f47a 100644
--- a/packages/meshbay-hub/tests/test_session_lifetime.py
+++ b/packages/meshbay-hub/tests/test_session_lifetime.py
@@ -6,13 +6,12 @@ maximum — plus a sign-out the hub honours and "sign out everywhere". The
browser half is `test_browser_idle_signout.py`; this is the hub's.
"""
-from datetime import datetime, timedelta, timezone
+from datetime import UTC, datetime, timedelta
import pytest
-from sqlalchemy import select, update
-
from meshbay_hub.api.deps import set_admin_usernames
from meshbay_hub.db.models import RefreshToken, User
+from sqlalchemy import select, update
AUTH_KEY = "s" * 44
@@ -40,7 +39,7 @@ async def _refresh(client, token):
def _aware(dt):
- return dt if dt.tzinfo else dt.replace(tzinfo=timezone.utc)
+ return dt if dt.tzinfo else dt.replace(tzinfo=UTC)
async def _expiry(db_session, username):
@@ -66,7 +65,7 @@ async def test_the_defaults_are_in_the_panel_and_the_browser_delay_is_public(cli
async def test_a_refresh_token_lasts_the_idle_window(client, db_session):
await _register(client, "idle_window")
await _login(client, "idle_window")
- left = await _expiry(db_session, "idle_window") - datetime.now(timezone.utc)
+ left = await _expiry(db_session, "idle_window") - datetime.now(UTC)
assert timedelta(hours=23, minutes=58) < left <= timedelta(hours=24)
@@ -78,7 +77,7 @@ async def test_the_idle_window_never_undercuts_the_access_token(client, db_sessi
json={"session": {"refresh_idle_hours": 1}})
await _register(client, "short_idle")
await _login(client, "short_idle")
- left = await _expiry(db_session, "short_idle") - datetime.now(timezone.utc)
+ left = await _expiry(db_session, "short_idle") - datetime.now(UTC)
# The test hub's access token lives 3600 s; the floor is that plus an hour.
assert left > timedelta(hours=1, minutes=58)
@@ -98,7 +97,7 @@ async def test_no_session_renews_past_its_maximum(client, db_session):
select(User.id).where(User.username == "long_session"))).scalar_one()
await db_session.execute(
update(RefreshToken).where(RefreshToken.user_id == uid)
- .values(created_at=datetime.now(timezone.utc) - timedelta(hours=721)))
+ .values(created_at=datetime.now(UTC) - timedelta(hours=721)))
await db_session.commit()
r = await _refresh(client, current)
diff --git a/packages/meshbay-hub/tests/test_session_renewal.py b/packages/meshbay-hub/tests/test_session_renewal.py
index 5d4bcfd..7b8c108 100644
--- a/packages/meshbay-hub/tests/test_session_renewal.py
+++ b/packages/meshbay-hub/tests/test_session_renewal.py
@@ -26,9 +26,9 @@ broken client.
"""
import json
+import re
import shutil
import subprocess
-import re
from pathlib import Path
import pytest
diff --git a/packages/meshbay-hub/tests/test_table_rows_measured.py b/packages/meshbay-hub/tests/test_table_rows_measured.py
index 60ec878..f203624 100644
--- a/packages/meshbay-hub/tests/test_table_rows_measured.py
+++ b/packages/meshbay-hub/tests/test_table_rows_measured.py
@@ -16,11 +16,10 @@ rectangles show it does not — which is what this file is for.
"""
import json
+import shutil
import subprocess
from pathlib import Path
-import shutil
-
import pytest
HARNESS = Path(__file__).parent / "harness" / "layout_probe.py"
diff --git a/packages/meshbay-hub/tests/test_transfers.py b/packages/meshbay-hub/tests/test_transfers.py
index a776b50..9b65ca5 100644
--- a/packages/meshbay-hub/tests/test_transfers.py
+++ b/packages/meshbay-hub/tests/test_transfers.py
@@ -243,7 +243,10 @@ class L {
acquire() { return this._wait; }
release(reason) { if (!this.closed) { this.closed = true; this.released.push(reason); } }
grant() { this.state = 'granted'; if (this._onState) this._onState(this); this._go(); }
- push(state, ahead) { this.state = state; this.ahead = ahead; if (this._onState) this._onState(this); }
+ push(state, ahead) {
+ this.state = state; this.ahead = ahead;
+ if (this._onState) this._onState(this);
+ }
}
"""
diff --git a/packages/meshbay-node/src/meshbay_node/config.py b/packages/meshbay-node/src/meshbay_node/config.py
index 2ae3c7c..437f629 100644
--- a/packages/meshbay-node/src/meshbay_node/config.py
+++ b/packages/meshbay-node/src/meshbay_node/config.py
@@ -14,9 +14,9 @@ from pathlib import Path
from meshbay_node.platform import config_dir, data_dir
try:
- import tomllib # Python 3.11+
+ import tomllib # Python 3.11+
except ImportError:
- import tomli as tomllib # type: ignore[no-redef]
+ import tomli as tomllib # type: ignore[no-redef]
log = logging.getLogger(__name__)
diff --git a/packages/meshbay-node/src/meshbay_node/daemon.py b/packages/meshbay-node/src/meshbay_node/daemon.py
index 3492d03..bb50bcf 100644
--- a/packages/meshbay-node/src/meshbay_node/daemon.py
+++ b/packages/meshbay-node/src/meshbay_node/daemon.py
@@ -25,43 +25,43 @@ Usage:
"""
import asyncio
-from dataclasses import asdict, replace
import base64
-import json
import logging
import os
import signal
import sys
import time
+from dataclasses import asdict, replace
from pathlib import Path
import uvicorn
-
+from meshbay_common import MNP_VERSION
from meshbay_common.background import spawn
from meshbay_common.paths import fold
-from meshbay_common import MNP_VERSION
from meshbay_common.protocol import MNP
-from meshbay_node.audit import RETENTION_DAYS as AUDIT_RETENTION_DAYS, AuditStore
+
+from meshbay_node import uploads as uploads_mod
+from meshbay_node.audit import RETENTION_DAYS as AUDIT_RETENTION_DAYS
+from meshbay_node.audit import AuditStore
from meshbay_node.bundle_store import BundleStore
from meshbay_node.chat.store import ChatStore
-from meshbay_node.config import Config, DEFAULT_CONFIG_PATH, load_config, write_example_config
-from meshbay_node.roots import RootSet, RootError, entry_abs_path, off_disk
+from meshbay_node.config import DEFAULT_CONFIG_PATH, Config, load_config
from meshbay_node.hub_client import HubClient, HubConfig
-from meshbay_node.indexer import DirectoryIndexer, IndexCache, GroupIndex
+from meshbay_node.indexer import DirectoryIndexer, GroupIndex, IndexCache
from meshbay_node.indexer.enrich import Enricher
from meshbay_node.indexer.enrich_audio import AudioEnricher
from meshbay_node.indexer.enrich_photo import PhotoEnricher
+from meshbay_node.keystore import create_keystore, load_keystore, load_or_create_keystore
from meshbay_node.media_cache import MediaCache
-from meshbay_node.tmdb import TmdbClient
-from meshbay_node import uploads as uploads_mod
from meshbay_node.musicbrainz import MusicBrainzClient
-from meshbay_node.keystore import create_keystore, load_keystore, load_or_create_keystore
from meshbay_node.platform import chmod_private, config_dir, data_dir, state_dir
+from meshbay_node.roots import RootError, RootSet, entry_abs_path, off_disk
from meshbay_node.roster import Roster
+from meshbay_node.tmdb import TmdbClient
from meshbay_node.transport import (
- Denylist,
QUIC_AVAILABLE,
WEBRTC_AVAILABLE,
+ Denylist,
)
from meshbay_node.transport.wire import index_delta_message, index_sync_message
@@ -109,8 +109,8 @@ def _owning_directory(path: str, directories: list[str]) -> str | None:
def calibrate_argon2(target_ms: int = 500) -> None:
"""Benchmark Argon2id and suggest parameters targeting ~target_ms."""
- import time
import os
+ import time
print(f"Calibrating Argon2id (target: {target_ms}ms) ...")
salt = os.urandom(16)
@@ -1938,8 +1938,7 @@ def _systemctl_user(verb: str, unit: str, *, not_running_hint: str,
def main() -> None:
import argparse
- from meshbay_node.platform import (configure_event_loop, force_utf8_stdio,
- load_node_env)
+ from meshbay_node.platform import configure_event_loop, force_utf8_stdio, load_node_env
force_utf8_stdio()
configure_event_loop()
# Before anything reads the environment. On Linux systemd has usually loaded
@@ -2164,10 +2163,10 @@ def main() -> None:
print("Aborted.")
return
- import subprocess as _sp
import json as _json
- import urllib.request
+ import subprocess as _sp
import urllib.error
+ import urllib.request
token_file = data_dir_ / "ui-token"
if token_file.exists():
@@ -2244,7 +2243,8 @@ def main() -> None:
_GUIDANCE = {
"node_key_link": (
"Link node key",
- f"Copy the node key above and paste it in Settings → Link Node on {cfg.hub.url}"),
+ f"Copy the node key above and paste it in "
+ f"Settings → Link Node on {cfg.hub.url}"),
"group_add": (
"Add a group",
"meshbay-node group add <name> --dir /path/to/files"),
@@ -2475,7 +2475,11 @@ def main() -> None:
if args.command == "restart-daemon":
if sys.platform == "win32":
from meshbay_node.platform import (
- autostart_end, autostart_run, service_end, service_run, service_status,
+ autostart_end,
+ autostart_run,
+ service_end,
+ service_run,
+ service_status,
)
if service_status()["installed"]:
service_end()
diff --git a/packages/meshbay-node/src/meshbay_node/hub_client.py b/packages/meshbay-node/src/meshbay_node/hub_client.py
index ef12bb4..58bf657 100644
--- a/packages/meshbay-node/src/meshbay_node/hub_client.py
+++ b/packages/meshbay-node/src/meshbay_node/hub_client.py
@@ -20,7 +20,7 @@ import logging
import time
from dataclasses import dataclass, field
from pathlib import Path
-from typing import Any, Callable
+from typing import Any
import httpx
import jwt
diff --git a/packages/meshbay-node/src/meshbay_node/indexer/__init__.py b/packages/meshbay-node/src/meshbay_node/indexer/__init__.py
index c92c730..a40e9f2 100644
--- a/packages/meshbay-node/src/meshbay_node/indexer/__init__.py
+++ b/packages/meshbay-node/src/meshbay_node/indexer/__init__.py
@@ -1,6 +1,6 @@
"""Directory indexer and Mesh Group Index."""
-from .indexer import DirectoryIndexer
-from .group_index import GroupIndex
from .cache import IndexCache
+from .group_index import GroupIndex
+from .indexer import DirectoryIndexer
__all__ = ["DirectoryIndexer", "GroupIndex", "IndexCache"]
diff --git a/packages/meshbay-node/src/meshbay_node/indexer/enrich.py b/packages/meshbay-node/src/meshbay_node/indexer/enrich.py
index b44a246..9881298 100644
--- a/packages/meshbay-node/src/meshbay_node/indexer/enrich.py
+++ b/packages/meshbay-node/src/meshbay_node/indexer/enrich.py
@@ -13,12 +13,12 @@ the rest asynchronously and hands the result back via a callback.
import asyncio
import logging
+from collections.abc import Awaitable, Callable
from pathlib import Path
-from typing import Awaitable, Callable
import blake3
-
from meshbay_common.protocol import IndexEntry
+
from meshbay_node.indexer import title_parse
from meshbay_node.indexer.indexer import MEDIA_EXTENSIONS
from meshbay_node.media_cache import MediaCache
@@ -173,7 +173,7 @@ async def _make_thumbnail(file_path: Path, duration: float | None) -> bytes | No
)
try:
stdout, _ = await asyncio.wait_for(proc.communicate(), THUMB_TIMEOUT_SECS)
- except asyncio.TimeoutError:
+ except TimeoutError:
proc.kill()
await proc.wait()
return None
diff --git a/packages/meshbay-node/src/meshbay_node/indexer/enrich_photo.py b/packages/meshbay-node/src/meshbay_node/indexer/enrich_photo.py
index 6613f85..5cacef9 100644
--- a/packages/meshbay-node/src/meshbay_node/indexer/enrich_photo.py
+++ b/packages/meshbay-node/src/meshbay_node/indexer/enrich_photo.py
@@ -22,13 +22,13 @@ import asyncio
import datetime
import io
import logging
+from collections.abc import Awaitable, Callable
from pathlib import Path
-from typing import Awaitable, Callable
import blake3
+from meshbay_common.protocol import IndexEntry
from PIL import ExifTags, Image, ImageOps
-from meshbay_common.protocol import IndexEntry
from meshbay_node.media_cache import MediaCache
log = logging.getLogger(__name__)
diff --git a/packages/meshbay-node/src/meshbay_node/indexer/group_index.py b/packages/meshbay-node/src/meshbay_node/indexer/group_index.py
index 2340c78..9e4e780 100644
--- a/packages/meshbay-node/src/meshbay_node/indexer/group_index.py
+++ b/packages/meshbay-node/src/meshbay_node/indexer/group_index.py
@@ -13,29 +13,27 @@ Delta format:
import base64
import logging
-import os
-import time
-from dataclasses import dataclass, field, asdict
-from pathlib import Path
-from typing import Iterator
+from dataclasses import asdict, dataclass, field
import blake3
import msgpack
import zstandard as zstd
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
-
from meshbay_common.crypto import (
+ pk_to_b64,
sign_chunk,
verify_chunk_signature,
- pk_to_b64,
- generate_gek,
)
+from meshbay_common.protocol import IndexDelta, IndexEntry
from meshbay_common.webcrypto import (
chunk_key_aes as derive_chunk_key,
- encrypt_chunk_aes as encrypt_chunk,
+)
+from meshbay_common.webcrypto import (
decrypt_chunk_aes as decrypt_chunk,
)
-from meshbay_common.protocol import IndexEntry, IndexDelta
+from meshbay_common.webcrypto import (
+ encrypt_chunk_aes as encrypt_chunk,
+)
log = logging.getLogger(__name__)
@@ -171,7 +169,6 @@ class GroupIndex:
) -> "GroupIndex":
"""Deserialize, verify signature, and decrypt (if private)."""
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
- from meshbay_common.crypto import verify_chunk_signature
envelope = msgpack.unpackb(data, raw=False)
diff --git a/packages/meshbay-node/src/meshbay_node/indexer/indexer.py b/packages/meshbay-node/src/meshbay_node/indexer/indexer.py
index 3fe4f3e..4b619d7 100644
--- a/packages/meshbay-node/src/meshbay_node/indexer/indexer.py
+++ b/packages/meshbay-node/src/meshbay_node/indexer/indexer.py
@@ -25,19 +25,20 @@ it is the only thing that recovers a missed event.
import asyncio
import logging
import time
+from collections.abc import Awaitable, Callable
from concurrent.futures import ThreadPoolExecutor
-from dataclasses import dataclass, field as dataclass_field
+from dataclasses import dataclass
+from dataclasses import field as dataclass_field
from pathlib import Path
-from typing import Callable, Awaitable
import blake3
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
+from meshbay_common.background import spawn
+from meshbay_common.paths import find_fold_collisions, fold, long_path
+from meshbay_common.protocol import IndexEntry
from watchdog.events import FileSystemEvent, FileSystemEventHandler
from watchdog.observers import Observer
-from meshbay_common.background import spawn
-from meshbay_common.paths import fold, find_fold_collisions, long_path
-from meshbay_common.protocol import IndexEntry
from meshbay_node.indexer.cache import IndexCache
from meshbay_node.indexer.group_index import GroupIndex
from meshbay_node.roots import Root, RootSet, off_disk
diff --git a/packages/meshbay-node/src/meshbay_node/indexer/title_parse.py b/packages/meshbay-node/src/meshbay_node/indexer/title_parse.py
index 14728ff..91bf2bd 100644
--- a/packages/meshbay-node/src/meshbay_node/indexer/title_parse.py
+++ b/packages/meshbay-node/src/meshbay_node/indexer/title_parse.py
@@ -19,7 +19,7 @@ directory listing; this module only parses strings it's handed.
from __future__ import annotations
import re
-from dataclasses import dataclass, field
+from dataclasses import dataclass
from guessit import guessit
@@ -218,7 +218,8 @@ class ParsedName:
year: int | None = None
season: int | None = None
episode: int | None = None
- confidence: bool = False # True only when display_title is set and structurally corroborated
+ # True only when display_title is set and structurally corroborated
+ confidence: bool = False
def parse_movie_filename(filename: str) -> ParsedName:
diff --git a/packages/meshbay-node/src/meshbay_node/keystore.py b/packages/meshbay-node/src/meshbay_node/keystore.py
index 00e504e..78fe122 100644
--- a/packages/meshbay-node/src/meshbay_node/keystore.py
+++ b/packages/meshbay-node/src/meshbay_node/keystore.py
@@ -38,8 +38,6 @@ from pathlib import Path
import msgpack
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey
-
-from meshbay_node.platform import chmod_private, config_dir
from meshbay_common.crypto import (
ARGON2_ITERATIONS,
ARGON2_LANES,
@@ -50,12 +48,12 @@ from meshbay_common.crypto import (
decrypt_keystore,
derive_keystore_key,
encrypt_keystore,
- generate_gek,
pk_to_b64,
sk_to_b64,
- sk_to_raw,
)
+from meshbay_node.platform import chmod_private, config_dir
+
log = logging.getLogger(__name__)
KEYSTORE_VERSION = 1
diff --git a/packages/meshbay-node/src/meshbay_node/ops.py b/packages/meshbay-node/src/meshbay_node/ops.py
index ca1b87d..baea365 100644
--- a/packages/meshbay-node/src/meshbay_node/ops.py
+++ b/packages/meshbay-node/src/meshbay_node/ops.py
@@ -24,8 +24,8 @@ from __future__ import annotations
import asyncio
import logging
-import time as _time
import re
+import time as _time
from dataclasses import asdict
from pathlib import Path
from typing import Any
@@ -37,8 +37,9 @@ from meshbay_common.crypto import (
unwrap_gek_aes,
wrap_gek_aes,
)
-from meshbay_node.config import DEFAULT_CONFIG_PATH
from meshbay_common.join import ROLE_MEMBER, ROLE_OPERATOR
+
+from meshbay_node.config import DEFAULT_CONFIG_PATH
from meshbay_node.roots import RootError, RootSet, off_disk
from meshbay_node.roster import Roster
@@ -951,6 +952,7 @@ async def remove_root(state: dict, group_id: str, root_name: str) -> dict:
raise OpError("Group not configured on this node", status=404)
from meshbay_common.paths import fold
+
from meshbay_node.roots import derive_name
target = fold(root_name)
match_idx = None
@@ -1001,6 +1003,7 @@ async def update_root(state: dict, group_id: str, root_name: str, *,
raise OpError("Group not configured on this node", status=404)
from meshbay_common.paths import fold
+
from meshbay_node.roots import RootSet
target = fold(root_name)
match = None
@@ -1462,8 +1465,7 @@ async def list_transfers(state: dict) -> dict:
ctx = getattr(webrtc, "_ctx", {}) if webrtc else {}
slots = ctx.get("_transfer_slots")
if slots is None:
- from meshbay_node.transfers import (
- DEFAULT_MAX_CONCURRENT, DEFAULT_MAX_PER_MEMBER, KINDS)
+ from meshbay_node.transfers import DEFAULT_MAX_CONCURRENT, DEFAULT_MAX_PER_MEMBER, KINDS
# No pool built means nothing has transferred since the daemon started,
# which is a real answer and not an error.
#
diff --git a/packages/meshbay-node/src/meshbay_node/replication.py b/packages/meshbay-node/src/meshbay_node/replication.py
index c2f5cd3..297ed0b 100644
--- a/packages/meshbay-node/src/meshbay_node/replication.py
+++ b/packages/meshbay-node/src/meshbay_node/replication.py
@@ -16,8 +16,6 @@ Usage:
await replicator.replicate_file(file_id, file_name, file_size)
"""
-import asyncio
-import hashlib
import logging
from pathlib import Path
diff --git a/packages/meshbay-node/src/meshbay_node/revocation.py b/packages/meshbay-node/src/meshbay_node/revocation.py
deleted file mode 100644
index d3ee18f..0000000
--- a/packages/meshbay-node/src/meshbay_node/revocation.py
+++ /dev/null
@@ -1,155 +0,0 @@
-"""
-MeshBay Node — revocation subscriber.
-
-Maintains a persistent WebSocket connection to the hub.
-When a signed revocation token arrives, verifies it (Ed25519)
-and adds the revoked target to the local blocklist.
-
-Usage in daemon:
- subscriber = RevocationSubscriber(hub_url, access_token, hub_pk_pem)
- await subscriber.start() # connects in background
- # check membership:
- if subscriber.is_revoked("user", user_id):
- refuse connection
- await subscriber.stop()
-"""
-
-import asyncio
-import json
-import logging
-import time
-from typing import Literal
-
-import httpx
-import jwt
-
-log = logging.getLogger(__name__)
-
-RevocationTarget = Literal["user", "group"]
-
-
-class RevocationSubscriber:
- """
- Background task that keeps a WebSocket connection to the hub
- and maintains a local revocation set.
- """
-
- def __init__(
- self,
- hub_url: str,
- access_token: str,
- hub_pk_pem: bytes,
- reconnect_delay: float = 5.0,
- ):
- self._hub_url = hub_url.rstrip("/")
- self._access_token = access_token
- self._hub_pk_pem = hub_pk_pem
- self._reconnect_delay = reconnect_delay
- self._revoked_users: set[str] = set()
- self._revoked_groups: set[str] = set()
- self._task: asyncio.Task | None = None
- self._running = False
-
- def is_revoked(self, target: RevocationTarget, target_id: str) -> bool:
- if target == "user":
- return target_id in self._revoked_users
- return target_id in self._revoked_groups
-
- def add_revocation(self, target: RevocationTarget, target_id: str) -> None:
- if target == "user":
- self._revoked_users.add(target_id)
- log.warning("User revoked locally: %s", target_id[:8])
- else:
- self._revoked_groups.add(target_id)
- log.warning("Group revoked locally: %s", target_id[:8])
-
- def verify_and_apply(self, token: str) -> bool:
- """Verify a revocation token and apply it. Returns True if valid."""
- try:
- from meshbay_common.handshake import JWT_LEEWAY_SECONDS
- payload = jwt.decode(token, self._hub_pk_pem, algorithms=["EdDSA"],
- leeway=JWT_LEEWAY_SECONDS,
- options={"verify_exp": False})
- if payload.get("type") != "revocation":
- return False
- target = payload["target"]
- target_id = payload["target_id"]
- self.add_revocation(target, target_id)
- return True
- except Exception as e:
- log.error("Invalid revocation token: %s", e)
- return False
-
- async def start(self) -> None:
- self._running = True
- self._task = asyncio.create_task(self._run_loop())
- log.info("RevocationSubscriber started")
-
- async def stop(self) -> None:
- self._running = False
- if self._task:
- self._task.cancel()
- try:
- await self._task
- except asyncio.CancelledError:
- pass
- log.info("RevocationSubscriber stopped")
-
- async def _run_loop(self) -> None:
- while self._running:
- try:
- await self._connect_and_listen()
- except asyncio.CancelledError:
- raise
- except Exception as e:
- log.warning("WS disconnected (%s), reconnecting in %ss", e, self._reconnect_delay)
- await asyncio.sleep(self._reconnect_delay)
-
- async def _connect_and_listen(self) -> None:
- ws_url = self._hub_url.replace("http://", "ws://").replace("https://", "wss://")
- ws_url += "/v1/nodes/ws"
-
- async with httpx.AsyncClient() as client:
- async with client.stream("GET", ws_url,
- headers={"Upgrade": "websocket"}) as resp:
- # Use websockets library for proper WS protocol
- pass
-
- # Use websockets library directly
- import websockets
- async with websockets.connect(ws_url) as ws:
- # Authenticate
- await ws.send(json.dumps({
- "type": "auth",
- "token": self._access_token,
- }))
- auth_resp = json.loads(await ws.recv())
- if auth_resp.get("type") != "auth_ok":
- raise ConnectionError(f"WS auth failed: {auth_resp}")
- log.info("WS connected to hub — node_id=%s", auth_resp.get("node_id", "?")[:8])
-
- # Listen for revocations + send keepalive pings
- ping_interval = 30.0
- last_ping = asyncio.get_event_loop().time()
-
- while self._running:
- now = asyncio.get_event_loop().time()
- if now - last_ping > ping_interval:
- await ws.send(json.dumps({"type": "ping"}))
- last_ping = now
-
- try:
- msg_raw = await asyncio.wait_for(ws.recv(), timeout=ping_interval + 5)
- msg = json.loads(msg_raw)
-
- if msg.get("type") == "revocation":
- token = msg.get("token", "")
- ok = self.verify_and_apply(token)
- log.info("Revocation received — valid=%s", ok)
- elif msg.get("type") == "pong":
- pass
- else:
- log.debug("WS message: %s", msg.get("type"))
-
- except asyncio.TimeoutError:
- continue
diff --git a/packages/meshbay-node/src/meshbay_node/roots.py b/packages/meshbay-node/src/meshbay_node/roots.py
index 67778ca..89b0441 100644
--- a/packages/meshbay-node/src/meshbay_node/roots.py
+++ b/packages/meshbay-node/src/meshbay_node/roots.py
@@ -71,7 +71,7 @@ def _free_name(directory: Path, filename: str) -> str:
raise FileExistsError(filename)
-def safe_subdir(roots: "RootSet", rel: str) -> Path | None:
+def safe_subdir(roots: RootSet, rel: str) -> Path | None:
"""
Resolve a client-supplied directory inside one of the group's roots, or refuse.
@@ -219,7 +219,7 @@ class RootSet:
# ── Construction ─────────────────────────────────────────────────────────
@classmethod
- def build(cls, specs: list[dict]) -> "RootSet":
+ def build(cls, specs: list[dict]) -> RootSet:
"""
Build from configuration, refusing anything ambiguous.
diff --git a/packages/meshbay-node/src/meshbay_node/roster.py b/packages/meshbay-node/src/meshbay_node/roster.py
index 8c9b3ef..9478a32 100644
--- a/packages/meshbay-node/src/meshbay_node/roster.py
+++ b/packages/meshbay-node/src/meshbay_node/roster.py
@@ -24,13 +24,11 @@ from __future__ import annotations
import hashlib
import json
import logging
-import os
import secrets
-from datetime import datetime, timedelta, timezone
+from datetime import UTC, datetime, timedelta
from pathlib import Path
import aiosqlite
-
from meshbay_common.paths import fold
log = logging.getLogger(__name__)
@@ -194,11 +192,11 @@ def hash_code(code: str) -> str:
def _now() -> str:
- return datetime.now(timezone.utc).isoformat(timespec="seconds")
+ return datetime.now(UTC).isoformat(timespec="seconds")
def _iso_in(seconds: int) -> str:
- return (datetime.now(timezone.utc)
+ return (datetime.now(UTC)
+ timedelta(seconds=seconds)).isoformat(timespec="seconds")
@@ -1073,7 +1071,7 @@ class Roster:
(group_id, user_id),
)
code = generate_code()
- expires = datetime.now(timezone.utc) + timedelta(seconds=ttl)
+ expires = datetime.now(UTC) + timedelta(seconds=ttl)
await self._db.execute(
"INSERT INTO invites (code_hash, group_id, user_id, username, role, "
"created_by, created_at, expires_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
@@ -1106,7 +1104,7 @@ class Roster:
# redeemed by whoever finds it first.
if invite["user_id"] != user_id:
return None
- if datetime.fromisoformat(invite["expires_at"]) < datetime.now(timezone.utc):
+ if datetime.fromisoformat(invite["expires_at"]) < datetime.now(UTC):
return None
cur = await self._db.execute(
diff --git a/packages/meshbay-node/src/meshbay_node/transfers.py b/packages/meshbay-node/src/meshbay_node/transfers.py
index 86c8d14..3345cb9 100644
--- a/packages/meshbay-node/src/meshbay_node/transfers.py
+++ b/packages/meshbay-node/src/meshbay_node/transfers.py
@@ -393,7 +393,7 @@ class TransferSlots:
# the one place it would be tempting to add one for a prettier
# log line.
for x in sorted(self.leases.values(),
- key=lambda l: (l.kind, l.state, l.created_at))
+ key=lambda ls: (ls.kind, ls.state, ls.created_at))
],
}
diff --git a/packages/meshbay-node/src/meshbay_node/transport/__init__.py b/packages/meshbay-node/src/meshbay_node/transport/__init__.py
index e423e35..86dbf23 100644
--- a/packages/meshbay-node/src/meshbay_node/transport/__init__.py
+++ b/packages/meshbay-node/src/meshbay_node/transport/__init__.py
@@ -15,8 +15,8 @@ Transport decision (2026-08-13, second security review):
# QUIC transport (MNP v2) — requires aioquic>=1.0
try:
- from .quic_server import QuicChunkServer, Denylist
from .quic_client import QuicChunkClient
+ from .quic_server import Denylist, QuicChunkServer
QUIC_AVAILABLE = True
except ImportError:
QuicChunkServer = None # type: ignore[assignment,misc]
@@ -26,7 +26,7 @@ except ImportError:
# WebRTC transport (browsers + native clients) — requires aiortc>=1.9
try:
- from .webrtc_server import WebRTCTransport, WebRTCPeerSession
+ from .webrtc_server import WebRTCPeerSession, WebRTCTransport
WEBRTC_AVAILABLE = True
except ImportError:
WebRTCTransport = None # type: ignore[assignment,misc]
diff --git a/packages/meshbay-node/src/meshbay_node/transport/quic_client.py b/packages/meshbay-node/src/meshbay_node/transport/quic_client.py
index af87b70..273f225 100644
--- a/packages/meshbay-node/src/meshbay_node/transport/quic_client.py
+++ b/packages/meshbay-node/src/meshbay_node/transport/quic_client.py
@@ -13,18 +13,14 @@ import base64
import logging
import os
import struct
-from pathlib import Path
-import jwt
import msgpack
-from aioquic.asyncio import connect, QuicConnectionProtocol
+from aioquic.asyncio import QuicConnectionProtocol, connect
from aioquic.quic.configuration import QuicConfiguration
from aioquic.quic.events import QuicEvent, StreamDataReceived
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
-
from meshbay_common import MNP_VERSION
from meshbay_common.groupbox import PURPOSE_ACK, PURPOSE_INDEX, unseal
-from meshbay_common.protocol import MNP, file_chunk_plaintext
from meshbay_common.handshake import (
MNP_MIN_SUPPORTED,
NONCE_LEN,
@@ -36,6 +32,7 @@ from meshbay_common.handshake import (
quic_binding,
verify_proof,
)
+from meshbay_common.protocol import MNP, file_chunk_plaintext
def _peer_cert_der(proto) -> bytes | None:
diff --git a/packages/meshbay-node/src/meshbay_node/transport/quic_server.py b/packages/meshbay-node/src/meshbay_node/transport/quic_server.py
index 2a2b07a..036574b 100644
--- a/packages/meshbay-node/src/meshbay_node/transport/quic_server.py
+++ b/packages/meshbay-node/src/meshbay_node/transport/quic_server.py
@@ -24,17 +24,16 @@ import os
import struct
import uuid
from pathlib import Path
-from typing import Any, Callable
+from typing import Any
-import jwt
import msgpack
from aioquic.asyncio import QuicConnectionProtocol, serve
from aioquic.quic.configuration import QuicConfiguration
from aioquic.quic.events import QuicEvent, StreamDataReceived, StreamReset
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
-
from meshbay_common import MNP_VERSION
-from meshbay_node.roots import ROOT_NOT_SERVED, RootSet, entry_abs_path
+from meshbay_common.crypto import pk_to_b64
+from meshbay_common.groupbox import PURPOSE_ACK, seal
from meshbay_common.handshake import (
MNP_MIN_SUPPORTED,
NONCE_LEN,
@@ -48,10 +47,10 @@ from meshbay_common.handshake import (
quic_binding,
verify_proof,
)
-from meshbay_common.crypto import pk_to_b64
-from meshbay_common.groupbox import PURPOSE_ACK, seal
from meshbay_common.protocol import MNP, file_chunk_wire
+
from meshbay_node.indexer import GroupIndex
+from meshbay_node.roots import ROOT_NOT_SERVED, RootSet, entry_abs_path
from meshbay_node.transport.wire import index_sync_message
log = logging.getLogger(__name__)
diff --git a/packages/meshbay-node/src/meshbay_node/transport/tls_cert.py b/packages/meshbay-node/src/meshbay_node/transport/tls_cert.py
index 8d680ea..cfc93c7 100644
--- a/packages/meshbay-node/src/meshbay_node/transport/tls_cert.py
+++ b/packages/meshbay-node/src/meshbay_node/transport/tls_cert.py
@@ -10,11 +10,10 @@ the QUIC handshake proof (11.5.6), since QUIC has no DTLS fingerprint to bind to
Certificate is generated once and cached at ~/.config/meshbay/node_tls.crt/.key.
"""
-import logging
-import os
-from pathlib import Path
import datetime
import ipaddress
+import logging
+from pathlib import Path
from cryptography import x509
from cryptography.hazmat.primitives import hashes, serialization
@@ -47,8 +46,8 @@ def generate_self_signed_cert(
.issuer_name(issuer)
.public_key(rsa_key.public_key())
.serial_number(x509.random_serial_number())
- .not_valid_before(datetime.datetime.now(datetime.timezone.utc))
- .not_valid_after(datetime.datetime.now(datetime.timezone.utc)
+ .not_valid_before(datetime.datetime.now(datetime.UTC))
+ .not_valid_after(datetime.datetime.now(datetime.UTC)
+ datetime.timedelta(days=3650))
.add_extension(
x509.SubjectAlternativeName([
diff --git a/packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py b/packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py
index 8a5bbff..e92ec13 100644
--- a/packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py
+++ b/packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py
@@ -25,8 +25,6 @@ Signaling flow (handled externally by the hub):
import asyncio
import base64
import contextvars
-import hashlib
-import hmac
import logging
import os
import re
@@ -38,63 +36,53 @@ from pathlib import Path
from typing import Any
import blake3
-import jwt
import msgpack
-from aiortc import RTCPeerConnection, RTCSessionDescription, RTCDataChannel
+from aiortc import RTCDataChannel, RTCPeerConnection, RTCSessionDescription
from cryptography.hazmat.primitives.asymmetric.ed25519 import (
Ed25519PrivateKey,
Ed25519PublicKey,
)
-
from meshbay_common import MNP_VERSION
-from meshbay_common.handshake import (
- MNP_MIN_SUPPORTED,
- NONCE_LEN,
- ROLE_CLIENT,
- ROLE_NODE,
- HandshakeError,
- authorize_token,
- check_version,
- handshake_transcript,
- make_proof,
- verify_proof,
- webrtc_binding,
-)
from meshbay_common.adminop import (
ADMIN_CHALLENGE_TTL,
+ OP_APP_DIRECTORIES,
+ OP_APPS_ENABLED,
+ OP_CHAT_DIRECTORY,
+ OP_CHAT_EPOCH,
+ OP_CHAT_LINK_PREVIEW,
OP_DIR_DELETE,
OP_FILE_DELETE,
+ OP_GEK_ROTATE,
+ OP_GROUP_ATTACH,
+ OP_GROUP_DETACH,
OP_INVITE_CREATE,
OP_MEMBER_REVOKE,
- OP_GEK_ROTATE,
OP_MEMBER_UNPIN,
- OP_APPS_ENABLED,
+ OP_MUSICBRAINZ_ENABLED,
+ OP_ROOT_ADD,
+ OP_ROOT_EJECT,
+ OP_ROOT_PLUG,
+ OP_ROOT_REMOVE,
+ OP_ROOT_UPDATE,
+ OP_SEARCH_LISTED,
OP_SET_SCAN_SETTINGS,
- OP_TRANSFER_LIMITS,
OP_TMDB_CONFIG,
OP_TMDB_ENABLED,
OP_TMDB_OVERRIDE,
OP_TMDB_REMATCH,
- OP_MUSICBRAINZ_ENABLED,
- OP_APP_DIRECTORIES,
- OP_CHAT_DIRECTORY,
- OP_CHAT_EPOCH,
- OP_CHAT_LINK_PREVIEW,
- OP_SEARCH_LISTED,
- OP_ROOT_ADD,
- OP_ROOT_REMOVE,
- OP_ROOT_UPDATE,
- OP_ROOT_EJECT,
- OP_ROOT_PLUG,
- OP_GROUP_ATTACH,
- OP_GROUP_DETACH,
+ OP_TRANSFER_LIMITS,
admin_transcript,
)
+from meshbay_common.chatbox import (
+ NONCE_LEN as CHAT_NONCE_LEN,
+)
+from meshbay_common.chatbox import (
+ SIG_LEN as CHAT_SIG_LEN,
+)
from meshbay_common.crypto import pk_to_b64, wrap_gek_aes
from meshbay_common.device import (
DEVICE_TTL,
device_add_transcript,
- device_code_hash,
device_hello_transcript,
device_request_transcript,
)
@@ -104,44 +92,62 @@ from meshbay_common.groupbox import (
PURPOSE_ROSTER,
seal,
)
+from meshbay_common.handshake import (
+ MNP_MIN_SUPPORTED,
+ NONCE_LEN,
+ ROLE_CLIENT,
+ ROLE_NODE,
+ HandshakeError,
+ authorize_token,
+ check_version,
+ handshake_transcript,
+ make_proof,
+ verify_proof,
+ webrtc_binding,
+)
from meshbay_common.join import (
JOIN_TTL,
ROLE_MEMBER,
ROLE_OPERATOR,
join_transcript,
)
-from meshbay_common.chatbox import (
- NONCE_LEN as CHAT_NONCE_LEN,
- SIG_LEN as CHAT_SIG_LEN,
-)
from meshbay_common.protocol import (
MNP,
+ UPLOAD_PROBE_INDEX,
chunk_ciphertext,
file_chunk_wire,
- UPLOAD_PROBE_INDEX,
file_upload_ack_wire,
file_upload_payload,
)
-from meshbay_node.chat import FORMAT_SEALED_V1, ReplayedMessage
-from meshbay_node.transport.wire import index_sync_message
-from meshbay_node.indexer import GroupIndex
-from meshbay_node.indexer.indexer import DirectoryIndexer
+
from meshbay_node import hwaccel, linkpreview, ops, platform
from meshbay_node import transfers as transfers_mod
from meshbay_node import uploads as uploads_mod
-from meshbay_node.transfers import TransferSlots
+from meshbay_node.chat import FORMAT_SEALED_V1, ReplayedMessage
+from meshbay_node.indexer import GroupIndex
+from meshbay_node.indexer.indexer import DirectoryIndexer
+
# Re-imported under its original name: every call site and existing test in
# this module still refers to it as `_probe_video`. The implementation lives
# in media_probe.py so the indexer package (imported just above) can call it
# too, for index-time enrichment, without a circular import.
from meshbay_node.media_probe import (
BROWSER_INCOMPATIBLE_VIDEO_CODECS,
+)
+from meshbay_node.media_probe import (
probe_video as _probe_video,
)
from meshbay_node.roots import (
- ROOT_NOT_SERVED, RootSet, entry_abs_path, off_disk, SAFE_UPLOAD_NAME, safe_subdir,
+ ROOT_NOT_SERVED,
+ SAFE_UPLOAD_NAME,
+ RootSet,
_free_name,
+ entry_abs_path,
+ off_disk,
+ safe_subdir,
)
+from meshbay_node.transfers import TransferSlots
+from meshbay_node.transport.wire import index_sync_message
log = logging.getLogger(__name__)
@@ -3924,7 +3930,8 @@ class WebRTCPeerSession:
if not entry:
thumb = await self._try_serve_thumbnail(file_id, chunk_index, ctx.get("gek"))
if thumb is not None:
- log.debug("file_req file_id=%s chunk=%s: served as thumbnail", file_id[:16], chunk_index)
+ log.debug("file_req file_id=%s chunk=%s: served as thumbnail",
+ file_id[:16], chunk_index)
self._send(thumb)
return
log.warning("File not found: %s", file_id[:16])
@@ -3992,7 +3999,8 @@ class WebRTCPeerSession:
self._leaseless.finish(str(file_id))
@staticmethod
- async def _fetch_and_cache_poster(media_cache, tmdb_client, poster_path: str | None) -> str | None:
+ async def _fetch_and_cache_poster(media_cache, tmdb_client,
+ poster_path: str | None) -> str | None:
"""
Downloads a TMDB poster/backdrop once, caches it under its own
blake3 like a video thumbnail (docs/MESHBAY_DESIGN.md §9.7), and
@@ -4020,7 +4028,8 @@ class WebRTCPeerSession:
return thumb_hash
@staticmethod
- async def _fetch_and_cache_cover(media_cache, musicbrainz_client, mbid: str | None) -> str | None:
+ async def _fetch_and_cache_cover(media_cache, musicbrainz_client,
+ mbid: str | None) -> str | None:
"""
Music app equivalent of `_fetch_and_cache_poster` — a release's
Cover Art Archive image, fetched once per mbid and cached under its
@@ -4504,7 +4513,9 @@ class WebRTCPeerSession:
# itself.
if not fetched.get("overview"):
fallback = await tmdb_client.tv_season(tmdb_id, season, language="en-US") or {}
- fetched = {**fallback, **{k: v for k, v in fetched.items() if v not in (None, "", [])}}
+ fetched = {**fallback,
+ **{k: v for k, v in fetched.items()
+ if v not in (None, "", [])}}
await media_cache.set_season_meta(tmdb_id, season, fetched)
details = fetched
@@ -4833,8 +4844,10 @@ class WebRTCPeerSession:
# back to English per field rather than discarding an otherwise-good
# localized response over one empty one — mirrored here the same
# way, at field granularity, not by abandoning the whole response.
- if not details.get("overview") or not details.get("poster_path") or not details.get("genres"):
- fallback = (await tmdb_client.tv_details(tmdb_id, language="en-US") if media_type == "tv"
+ if (not details.get("overview") or not details.get("poster_path")
+ or not details.get("genres")):
+ fallback = (await tmdb_client.tv_details(tmdb_id, language="en-US")
+ if media_type == "tv"
else await tmdb_client.movie_details(tmdb_id, language="en-US")) or {}
details = {**fallback, **{k: v for k, v in details.items() if v not in (None, "", [])}}
credits = (await tmdb_client.tv_credits(tmdb_id) if media_type == "tv"
@@ -5909,7 +5922,8 @@ class WebRTCPeerSession:
transcript = admin_transcript(
op=pending["op"],
node_pk_b64=self._node_pk_b64(),
- group_id=pending["group_id"] if pending.get("group_id") is not None else (self._group_id or ""),
+ group_id=(pending["group_id"] if pending.get("group_id") is not None
+ else (self._group_id or "")),
subject=pending["subject"],
nonce=pending["nonce"],
ts=pending["ts"],
@@ -6160,7 +6174,7 @@ class WebRTCPeerSession:
# total budget is unchanged.
await asyncio.wait_for(self._stream_credit_evt.wait(),
timeout=STREAM_CREDIT_POLL)
- except asyncio.TimeoutError:
+ except TimeoutError:
silent = time.monotonic() - self._stream_heard_at
if silent >= STREAM_CREDIT_TIMEOUT:
log.info("Stream stalled: nothing from peer=%s for %.0fs",
@@ -6208,7 +6222,7 @@ class WebRTCPeerSession:
await asyncio.wait_for(asyncio.shield(prev), timeout=15)
log.info("stream: previous stream ended in %.1fs",
time.monotonic() - t0)
- except asyncio.TimeoutError:
+ except TimeoutError:
log.warning("stream: previous stream STILL RUNNING after 15s")
except Exception:
pass # it failed on its own; the slot is free either way
@@ -6851,7 +6865,7 @@ async def _transcode_audio_to_aac(file_path: Path) -> bytes:
try:
_, stderr = await asyncio.wait_for(
proc.communicate(), timeout=AUDIO_TRANSCODE_TIMEOUT_SECS)
- except asyncio.TimeoutError:
+ except TimeoutError:
proc.kill()
await proc.wait()
raise RuntimeError(f"ffmpeg timed out after {AUDIO_TRANSCODE_TIMEOUT_SECS}s")
@@ -6912,7 +6926,7 @@ async def _seek_lands_at(file_path: Path, t: float, map_args: list[str]) -> floa
)
stdout, _ = await asyncio.wait_for(
probe.communicate(), timeout=SEEK_PROBE_TIMEOUT_SECS)
- except (asyncio.TimeoutError, OSError) as e:
+ except (TimeoutError, OSError) as e:
log.warning("stream: seek probe failed at %.1fs: %r", t, e)
return None
finally:
@@ -6967,7 +6981,7 @@ async def _extract_subtitle_to_webvtt(file_path: Path, ordinal: int,
try:
_, stderr = await asyncio.wait_for(
proc.communicate(), timeout=timeout)
- except asyncio.TimeoutError:
+ except TimeoutError:
proc.kill()
await proc.wait()
raise RuntimeError(f"ffmpeg timed out after {timeout:.0f}s")
@@ -7152,7 +7166,7 @@ class WebRTCTransport:
Returns (answer_sdp, ice_candidates) to relay back via hub signaling.
ICE candidates are embedded in the SDP (aiortc gathers before returning).
"""
- from aiortc import RTCIceServer, RTCConfiguration
+ from aiortc import RTCConfiguration, RTCIceServer
# aiortc keeps only the first STUN entry it sees here; the actual
# multi-server fan-out is done by transport/stun_multi, which patches
diff --git a/packages/meshbay-node/src/meshbay_node/ui/app.py b/packages/meshbay-node/src/meshbay_node/ui/app.py
index 77491a0..888487c 100644
--- a/packages/meshbay-node/src/meshbay_node/ui/app.py
+++ b/packages/meshbay-node/src/meshbay_node/ui/app.py
@@ -16,8 +16,8 @@ import logging
from fastapi import FastAPI, HTTPException, Query
from fastapi.responses import JSONResponse
-
from meshbay_common.background import spawn
+
from meshbay_node import __version__, ops
from meshbay_node.indexer.indexer import DirectoryIndexer
diff --git a/packages/meshbay-node/src/meshbay_node/uploads.py b/packages/meshbay-node/src/meshbay_node/uploads.py
index f8ae7f9..dd31e1e 100644
--- a/packages/meshbay-node/src/meshbay_node/uploads.py
+++ b/packages/meshbay-node/src/meshbay_node/uploads.py
@@ -25,9 +25,9 @@ build first.
from __future__ import annotations
import time
+from collections.abc import Iterable
from dataclasses import dataclass, field
from pathlib import Path
-from typing import Iterable
# What an unfinished upload is called on disk while it is being written. The
# node has always used this; it is named here because the reaper below has to
diff --git a/packages/meshbay-node/tests/test_admin_ops_mnp.py b/packages/meshbay-node/tests/test_admin_ops_mnp.py
index 92e50b5..7fd1c2b 100644
--- a/packages/meshbay-node/tests/test_admin_ops_mnp.py
+++ b/packages/meshbay-node/tests/test_admin_ops_mnp.py
@@ -15,13 +15,10 @@ thing that finishes a revocation: the ex-member still holds the current key.
"""
import base64
-import time
from pathlib import Path
import pytest
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
-
-from conftest import one_root
from meshbay_common.adminop import (
OP_GEK_ROTATE,
OP_MEMBER_UNPIN,
@@ -34,6 +31,8 @@ from meshbay_node.indexer.group_index import GroupIndex
from meshbay_node.roster import open_roster
from meshbay_node.transport.webrtc_server import WebRTCPeerSession
+from conftest import one_root
+
GROUP = "g" * 32
diff --git a/packages/meshbay-node/tests/test_app_directories.py b/packages/meshbay-node/tests/test_app_directories.py
index 726f5bd..955a769 100644
--- a/packages/meshbay-node/tests/test_app_directories.py
+++ b/packages/meshbay-node/tests/test_app_directories.py
@@ -26,7 +26,6 @@ from types import SimpleNamespace
import pytest
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
-
from meshbay_common.adminop import OP_APP_DIRECTORIES
from meshbay_node import ops
from meshbay_node.indexer.group_index import GroupIndex
diff --git a/packages/meshbay-node/tests/test_app_directories_signed.py b/packages/meshbay-node/tests/test_app_directories_signed.py
index eda09d8..af362a1 100644
--- a/packages/meshbay-node/tests/test_app_directories_signed.py
+++ b/packages/meshbay-node/tests/test_app_directories_signed.py
@@ -24,7 +24,6 @@ from pathlib import Path
import pytest
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
-
from meshbay_common.adminop import OP_APP_DIRECTORIES, admin_transcript
from meshbay_common.crypto import pk_to_b64
from meshbay_common.join import ROLE_OPERATOR
diff --git a/packages/meshbay-node/tests/test_apps_enabled_policy.py b/packages/meshbay-node/tests/test_apps_enabled_policy.py
index 40c7cc8..1fa3d5c 100644
--- a/packages/meshbay-node/tests/test_apps_enabled_policy.py
+++ b/packages/meshbay-node/tests/test_apps_enabled_policy.py
@@ -13,12 +13,11 @@ way an unsigned upload is refused, so the check has to happen up front.
from pathlib import Path
import pytest
-
+from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from meshbay_common.adminop import OP_APPS_ENABLED
from meshbay_node.indexer.group_index import GroupIndex
from meshbay_node.roster import Roster
from meshbay_node.transport.webrtc_server import WebRTCPeerSession
-from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from conftest import one_root
diff --git a/packages/meshbay-node/tests/test_audio_root_gates_enrichment.py b/packages/meshbay-node/tests/test_audio_root_gates_enrichment.py
index 251617c..b8c4dd6 100644
--- a/packages/meshbay-node/tests/test_audio_root_gates_enrichment.py
+++ b/packages/meshbay-node/tests/test_audio_root_gates_enrichment.py
@@ -17,10 +17,9 @@ import os
import pytest
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
-
from meshbay_common.crypto import generate_gek
from meshbay_node import ops
-from meshbay_node.config import Config, HubConfig, NodeConfig, GroupConfig, KeystoreConfig
+from meshbay_node.config import Config, GroupConfig, HubConfig, KeystoreConfig, NodeConfig
from meshbay_node.daemon import NodeDaemon
from meshbay_node.indexer import DirectoryIndexer
from meshbay_node.indexer.enrich_audio import AudioEnricher
diff --git a/packages/meshbay-node/tests/test_audio_transcode.py b/packages/meshbay-node/tests/test_audio_transcode.py
index a6557f0..acb39b7 100644
--- a/packages/meshbay-node/tests/test_audio_transcode.py
+++ b/packages/meshbay-node/tests/test_audio_transcode.py
@@ -145,7 +145,8 @@ async def test_missing_file_id_is_an_error(tmp_path, media_cache):
sk_node = Ed25519PrivateKey.generate()
session = WebRTCPeerSession.__new__(WebRTCPeerSession)
session._ctx = {
- "roots": one_root(tmp_path), "index": GroupIndex(group_id="g" * 32, sk_node=sk_node, gek=gek),
+ "roots": one_root(tmp_path),
+ "index": GroupIndex(group_id="g" * 32, sk_node=sk_node, gek=gek),
"gek": gek, "sk_node": sk_node, "media_cache": media_cache,
}
session._group_id = None
@@ -170,7 +171,8 @@ async def test_multi_chunk_cached_blob_reassembles_correctly(tmp_path, media_cac
sk_node = Ed25519PrivateKey.generate()
session = WebRTCPeerSession.__new__(WebRTCPeerSession)
session._ctx = {
- "roots": one_root(tmp_path), "index": GroupIndex(group_id="g" * 32, sk_node=sk_node, gek=gek),
+ "roots": one_root(tmp_path),
+ "index": GroupIndex(group_id="g" * 32, sk_node=sk_node, gek=gek),
"gek": gek, "sk_node": sk_node, "media_cache": media_cache,
}
session._group_id = None
diff --git a/packages/meshbay-node/tests/test_chat_is_bounded.py b/packages/meshbay-node/tests/test_chat_is_bounded.py
index 4e767bc..5dc1128 100644
--- a/packages/meshbay-node/tests/test_chat_is_bounded.py
+++ b/packages/meshbay-node/tests/test_chat_is_bounded.py
@@ -33,7 +33,6 @@ import os
import pytest
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
-
from meshbay_common.chatbox import NONCE_LEN, SIG_LEN
from meshbay_common.crypto import pk_to_b64
from meshbay_node.chat import FORMAT_SEALED_V1, ChatStore
diff --git a/packages/meshbay-node/tests/test_chat_multidevice.py b/packages/meshbay-node/tests/test_chat_multidevice.py
index bb61285..058544a 100644
--- a/packages/meshbay-node/tests/test_chat_multidevice.py
+++ b/packages/meshbay-node/tests/test_chat_multidevice.py
@@ -16,10 +16,9 @@ account where it should be keyed by connection" mistake as `pin_identity`'s
old INSERT OR REPLACE.
"""
-from pathlib import Path
-
import base64
import hashlib
+from pathlib import Path
from aiortc import RTCPeerConnection
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
diff --git a/packages/meshbay-node/tests/test_chat_pagination.py b/packages/meshbay-node/tests/test_chat_pagination.py
index caf1f73..f3949a9 100644
--- a/packages/meshbay-node/tests/test_chat_pagination.py
+++ b/packages/meshbay-node/tests/test_chat_pagination.py
@@ -13,12 +13,10 @@ The cursor is the row id rather than the timestamp. `timestamp` is a float from
timestamp cursor would then skip a message or return it twice.
"""
-import asyncio
from pathlib import Path
import pytest
import pytest_asyncio
-
from meshbay_node.chat.store import ChatStore
diff --git a/packages/meshbay-node/tests/test_chat_store.py b/packages/meshbay-node/tests/test_chat_store.py
index d74310d..2533921 100644
--- a/packages/meshbay-node/tests/test_chat_store.py
+++ b/packages/meshbay-node/tests/test_chat_store.py
@@ -2,10 +2,9 @@
Tests for the SQLite-backed chat message store.
"""
+
import pytest
import pytest_asyncio
-from pathlib import Path
-
from meshbay_node.chat.store import ChatStore
diff --git a/packages/meshbay-node/tests/test_cli_dispatch.py b/packages/meshbay-node/tests/test_cli_dispatch.py
index ad60b91..cf2fd6e 100644
--- a/packages/meshbay-node/tests/test_cli_dispatch.py
+++ b/packages/meshbay-node/tests/test_cli_dispatch.py
@@ -14,10 +14,8 @@ branch nobody ever ran.
"""
import sys
-from pathlib import Path
import pytest
-
from meshbay_node import daemon as daemon_mod
# Each verb, with the arguments that reach its branch. `--yes` where the command
@@ -155,7 +153,6 @@ def test_the_verb_list_here_matches_the_parser():
A verb added to the parser and not to this file would go untested, which is
exactly how `reload` shipped broken.
"""
- import argparse
import inspect
source = inspect.getsource(daemon_mod.main)
diff --git a/packages/meshbay-node/tests/test_daemon.py b/packages/meshbay-node/tests/test_daemon.py
index 8bd169d..8c4da2d 100644
--- a/packages/meshbay-node/tests/test_daemon.py
+++ b/packages/meshbay-node/tests/test_daemon.py
@@ -9,20 +9,21 @@ Hub interaction is mocked.
import asyncio
import base64
import os
+from unittest.mock import AsyncMock, MagicMock, patch
import pytest
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey
-from unittest.mock import AsyncMock, MagicMock, patch
-
from meshbay_common.crypto import generate_gek
from meshbay_common.groupbox import PURPOSE_INDEX, unseal
-from meshbay_node.config import Config, HubConfig, NodeConfig, GroupConfig, KeystoreConfig
-from conftest import one_root
+from meshbay_node.config import Config, GroupConfig, HubConfig, KeystoreConfig, NodeConfig
from meshbay_node.daemon import NodeDaemon
from meshbay_node.indexer import DirectoryIndexer
+from conftest import one_root
+
+
def _mock_keystore_keys(sk_ed):
"""Create a mock keystore with real Ed25519 + X25519 key material."""
sk_x = X25519PrivateKey.generate()
@@ -125,7 +126,7 @@ async def test_daemon_creates_chat_store(tmp_path, node_config, gek, hub_pk_pem)
patch("signal.SIGTERM", 15):
try:
await asyncio.wait_for(daemon.run(), timeout=5)
- except (asyncio.TimeoutError, Exception):
+ except (TimeoutError, Exception):
pass
task = asyncio.create_task(run_daemon())
@@ -204,7 +205,7 @@ async def test_daemon_no_groups_stays_up(tmp_path, hub_pk_pem):
patch("signal.SIGTERM", 15):
try:
await asyncio.wait_for(daemon.run(), timeout=5)
- except (asyncio.TimeoutError, Exception):
+ except (TimeoutError, Exception):
pass
task = asyncio.create_task(run_daemon())
diff --git a/packages/meshbay-node/tests/test_device_linking.py b/packages/meshbay-node/tests/test_device_linking.py
index 6d50580..53387ca 100644
--- a/packages/meshbay-node/tests/test_device_linking.py
+++ b/packages/meshbay-node/tests/test_device_linking.py
@@ -25,8 +25,6 @@ from pathlib import Path
import pytest
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
-
-from conftest import one_root
from meshbay_common.crypto import pk_to_b64
from meshbay_common.device import (
device_add_transcript,
@@ -39,6 +37,8 @@ from meshbay_node.indexer.group_index import GroupIndex
from meshbay_node.roster import generate_code, normalize_code, open_roster
from meshbay_node.transport.webrtc_server import WebRTCPeerSession
+from conftest import one_root
+
GROUP = "g" * 32
NONCE = b"\x11" * 32
diff --git a/packages/meshbay-node/tests/test_enrich.py b/packages/meshbay-node/tests/test_enrich.py
index e059bbe..ca03392 100644
--- a/packages/meshbay-node/tests/test_enrich.py
+++ b/packages/meshbay-node/tests/test_enrich.py
@@ -3,15 +3,15 @@
import asyncio
import shutil
import subprocess
-from pathlib import Path
-
import sys
+from pathlib import Path
import pytest
-
from meshbay_common.protocol import IndexEntry
from meshbay_node.indexer.enrich import (
- Enricher, _season_and_show_from_ancestors, _synthetic_episode_number,
+ Enricher,
+ _season_and_show_from_ancestors,
+ _synthetic_episode_number,
_title_from_siblings,
)
from meshbay_node.media_cache import MediaCache
diff --git a/packages/meshbay-node/tests/test_enrich_photo.py b/packages/meshbay-node/tests/test_enrich_photo.py
index 81367eb..d1bbd28 100644
--- a/packages/meshbay-node/tests/test_enrich_photo.py
+++ b/packages/meshbay-node/tests/test_enrich_photo.py
@@ -6,11 +6,10 @@ from pathlib import Path
import piexif
import pytest
-from PIL import Image
-
from meshbay_common.protocol import IndexEntry
from meshbay_node.indexer.enrich_photo import PhotoEnricher
from meshbay_node.media_cache import MediaCache
+from PIL import Image
@pytest.fixture
@@ -167,7 +166,8 @@ async def test_enricher_corrects_orientation(tmp_path, media_cache):
"width/height must reflect the EXIF-corrected orientation, not the raw stored frame")
thumb_bytes = await media_cache.get_thumb(fields["thumb_hash"])
thumb = Image.open(io.BytesIO(thumb_bytes))
- assert thumb.size[0] < thumb.size[1], "the stored thumbnail itself must be portrait, not sideways"
+ assert thumb.size[0] < thumb.size[1], (
+ "the stored thumbnail itself must be portrait, not sideways")
def test_gps_is_never_read_by_this_module():
diff --git a/packages/meshbay-node/tests/test_group_roster.py b/packages/meshbay-node/tests/test_group_roster.py
index a41cce2..74908e7 100644
--- a/packages/meshbay-node/tests/test_group_roster.py
+++ b/packages/meshbay-node/tests/test_group_roster.py
@@ -23,8 +23,6 @@ import time
import pytest
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
-
-from conftest import one_root
from meshbay_common.crypto import generate_gek, pk_to_b64
from meshbay_common.device import device_add_transcript
from meshbay_common.groupbox import PURPOSE_ROSTER, unseal
@@ -34,6 +32,8 @@ from meshbay_node.indexer.group_index import GroupIndex
from meshbay_node.roster import open_roster
from meshbay_node.transport.webrtc_server import WebRTCPeerSession
+from conftest import one_root
+
GROUP = "g" * 32
NONCE = b"\x11" * 32
diff --git a/packages/meshbay-node/tests/test_hot_reload_survives_client_close.py b/packages/meshbay-node/tests/test_hot_reload_survives_client_close.py
index fc6af70..fb9eade 100644
--- a/packages/meshbay-node/tests/test_hot_reload_survives_client_close.py
+++ b/packages/meshbay-node/tests/test_hot_reload_survives_client_close.py
@@ -18,17 +18,15 @@ import asyncio
import base64
import os
from pathlib import Path
+from unittest.mock import AsyncMock, MagicMock, patch
+import meshbay_node.indexer.indexer as indexer_mod
import pytest
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey
-from unittest.mock import AsyncMock, MagicMock, patch
-
from meshbay_node import ops
-from meshbay_node.config import Config, HubConfig, NodeConfig, GroupConfig, KeystoreConfig
from meshbay_node.daemon import NodeDaemon
-import meshbay_node.indexer.indexer as indexer_mod
def _free_port() -> int:
@@ -149,7 +147,7 @@ async def test_hot_loaded_group_finishes_scanning_without_anyone_awaiting_the_re
with patch("signal.SIGINT", 2), patch("signal.SIGTERM", 15):
try:
await asyncio.wait_for(daemon.run(), timeout=15)
- except (asyncio.TimeoutError, Exception):
+ except (TimeoutError, Exception):
pass
run_task = asyncio.create_task(run_daemon())
@@ -270,7 +268,7 @@ async def test_group_scoped_ops_404_until_listed_then_succeed(tmp_path):
with patch("signal.SIGINT", 2), patch("signal.SIGTERM", 15):
try:
await asyncio.wait_for(daemon.run(), timeout=15)
- except (asyncio.TimeoutError, Exception):
+ except (TimeoutError, Exception):
pass
run_task = asyncio.create_task(run_daemon())
diff --git a/packages/meshbay-node/tests/test_hub_client.py b/packages/meshbay-node/tests/test_hub_client.py
index 2975ee8..e733610 100644
--- a/packages/meshbay-node/tests/test_hub_client.py
+++ b/packages/meshbay-node/tests/test_hub_client.py
@@ -2,24 +2,17 @@
Tests for meshbay_node.hub_client — uses httpx.MockTransport to avoid network.
"""
-import json
-import os
import time
-import pytest
-from pathlib import Path
-from unittest.mock import AsyncMock, MagicMock, patch
import httpx
import jwt
+import pytest
+from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey
-from cryptography.hazmat.primitives import serialization
-
-
-from meshbay_node.hub_client import HubClient, HubConfig, HubSession
+from meshbay_node.hub_client import HubClient, HubConfig
from meshbay_node.keystore import NodeKeys
-
# ── Test fixtures ──────────────────────────────────────────────────────────────
@pytest.fixture
diff --git a/packages/meshbay-node/tests/test_hub_ws_group_claim.py b/packages/meshbay-node/tests/test_hub_ws_group_claim.py
index 49ac776..98b944b 100644
--- a/packages/meshbay-node/tests/test_hub_ws_group_claim.py
+++ b/packages/meshbay-node/tests/test_hub_ws_group_claim.py
@@ -15,7 +15,6 @@ import asyncio
import json
import pytest
-
from meshbay_node.hub_client import HubClient, HubConfig, HubSession
diff --git a/packages/meshbay-node/tests/test_ice_filter.py b/packages/meshbay-node/tests/test_ice_filter.py
index a995c2b..0fec2d2 100644
--- a/packages/meshbay-node/tests/test_ice_filter.py
+++ b/packages/meshbay-node/tests/test_ice_filter.py
@@ -3,8 +3,8 @@ transport/ice_filter — the interface filter must name the same adapter on
every platform, and must never leave the gather with no address at all.
"""
-import ifaddr
import aioice.ice
+import ifaddr
import pytest
from meshbay_node.transport import ice_filter
diff --git a/packages/meshbay-node/tests/test_index_cache.py b/packages/meshbay-node/tests/test_index_cache.py
index 036b4d7..8f0ff25 100644
--- a/packages/meshbay-node/tests/test_index_cache.py
+++ b/packages/meshbay-node/tests/test_index_cache.py
@@ -1,7 +1,6 @@
"""Tests for the (path, size, mtime) -> hash cache (indexer/cache.py)."""
import pytest
-
from meshbay_node.indexer.cache import IndexCache
diff --git a/packages/meshbay-node/tests/test_index_delta_carries_roots.py b/packages/meshbay-node/tests/test_index_delta_carries_roots.py
index 227f2d0..c939b4a 100644
--- a/packages/meshbay-node/tests/test_index_delta_carries_roots.py
+++ b/packages/meshbay-node/tests/test_index_delta_carries_roots.py
@@ -19,14 +19,12 @@ Additive on the wire (MNP 1.1): a 1.0 client sees a field it does not read.
from pathlib import Path
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
-
from meshbay_common.crypto import generate_gek
from meshbay_common.groupbox import PURPOSE_INDEX, unseal
from meshbay_node.indexer.group_index import GroupIndex
from meshbay_node.roots import RootSet
from meshbay_node.transport.wire import index_delta_message, index_sync_message
-
GROUP = "g" * 32
diff --git a/packages/meshbay-node/tests/test_index_no_cleartext.py b/packages/meshbay-node/tests/test_index_no_cleartext.py
index e5e4555..30d1226 100644
--- a/packages/meshbay-node/tests/test_index_no_cleartext.py
+++ b/packages/meshbay-node/tests/test_index_no_cleartext.py
@@ -14,7 +14,6 @@ framing or in a field name.
import msgpack
import pytest
-from conftest import one_root
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from meshbay_common.crypto import generate_gek
from meshbay_common.groupbox import PURPOSE_ACK, PURPOSE_INDEX, seal, unseal
@@ -22,6 +21,8 @@ from meshbay_common.protocol import MNP
from meshbay_node.indexer import DirectoryIndexer, GroupIndex
from meshbay_node.transport.wire import index_delta_message, index_sync_message
+from conftest import one_root
+
# A filename and a folder name that appear nowhere else in the tree.
SECRET_FILE = "quixotry-ledger-2019.pdf"
SECRET_DIR = "zarfwidget-archive"
diff --git a/packages/meshbay-node/tests/test_index_progress.py b/packages/meshbay-node/tests/test_index_progress.py
index df51e85..ecdc223 100644
--- a/packages/meshbay-node/tests/test_index_progress.py
+++ b/packages/meshbay-node/tests/test_index_progress.py
@@ -6,16 +6,15 @@ itself (see test_daemon.py for that) and never anything sent to the hub.
"""
import asyncio
+from unittest.mock import MagicMock
import pytest
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
-from unittest.mock import MagicMock
from fastapi.testclient import TestClient
-
-from meshbay_node.config import Config, HubConfig, NodeConfig, GroupConfig, KeystoreConfig
+from meshbay_node.config import Config, HubConfig, KeystoreConfig, NodeConfig
from meshbay_node.daemon import NodeDaemon
-from meshbay_node.indexer.indexer import IndexProgress
from meshbay_node.indexer.group_index import GroupIndex
+from meshbay_node.indexer.indexer import IndexProgress
from meshbay_node.transport.webrtc_server import WebRTCPeerSession
from meshbay_node.ui.app import create_ui_app
diff --git a/packages/meshbay-node/tests/test_indexer.py b/packages/meshbay-node/tests/test_indexer.py
index 6aee1b5..e97e2ef 100644
--- a/packages/meshbay-node/tests/test_indexer.py
+++ b/packages/meshbay-node/tests/test_indexer.py
@@ -4,16 +4,15 @@ import asyncio
import os
import threading
import time
-import pytest
from pathlib import Path
-from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
-from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey
+import meshbay_node.indexer.indexer as indexer_mod
+import pytest
+from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from meshbay_common.crypto import generate_gek
from meshbay_node.indexer import DirectoryIndexer, GroupIndex, IndexCache
-import meshbay_node.indexer.indexer as indexer_mod
+
from conftest import one_root
-from meshbay_node.keystore import NodeKeys
@pytest.fixture
@@ -397,7 +396,8 @@ async def test_scan_interrupted_partway_leaves_only_completed_files_cached(
indexer_mod._scan_file = real_scan_file
assert resumed.index.count == 5
- assert len(calls) == 2, f"expected only the 2 not-yet-cached files to be hashed, got {len(calls)}"
+ assert len(calls) == 2, (
+ f"expected only the 2 not-yet-cached files to be hashed, got {len(calls)}")
# ── Progress state ───────────────────────────────────────────────────────────
@@ -741,7 +741,7 @@ def test_small_file_gets_hash_version_1(tmp_path):
def test_large_file_gets_hash_version_2(tmp_path):
- from meshbay_node.indexer.indexer import _scan_file, _PARTIAL_THRESHOLD
+ from meshbay_node.indexer.indexer import _PARTIAL_THRESHOLD, _scan_file
d = tmp_path / "root"
d.mkdir()
f = d / "big.mkv"
@@ -755,7 +755,7 @@ def test_large_file_gets_hash_version_2(tmp_path):
def test_file_at_threshold_gets_hash_version_1(tmp_path):
- from meshbay_node.indexer.indexer import _scan_file, _PARTIAL_THRESHOLD
+ from meshbay_node.indexer.indexer import _PARTIAL_THRESHOLD, _scan_file
d = tmp_path / "root"
d.mkdir()
f = d / "exact.mkv"
@@ -770,7 +770,7 @@ def test_partial_hash_differs_from_full_hash(tmp_path):
"""For a file above the threshold, the partial hash must differ from what
a full-file blake3 would produce (they read different bytes)."""
import blake3 as b3
- from meshbay_node.indexer.indexer import _scan_file, _PARTIAL_THRESHOLD
+ from meshbay_node.indexer.indexer import _PARTIAL_THRESHOLD, _scan_file
d = tmp_path / "root"
d.mkdir()
f = d / "big.mkv"
@@ -785,7 +785,7 @@ def test_partial_hash_differs_from_full_hash(tmp_path):
def test_partial_hash_is_deterministic(tmp_path):
- from meshbay_node.indexer.indexer import _scan_file, _PARTIAL_THRESHOLD
+ from meshbay_node.indexer.indexer import _PARTIAL_THRESHOLD, _scan_file
d = tmp_path / "root"
d.mkdir()
f = d / "big.mkv"
diff --git a/packages/meshbay-node/tests/test_keystore.py b/packages/meshbay-node/tests/test_keystore.py
index 7dfefce..b9dd3a4 100644
--- a/packages/meshbay-node/tests/test_keystore.py
+++ b/packages/meshbay-node/tests/test_keystore.py
@@ -3,15 +3,13 @@
import sys
import pytest
-from pathlib import Path
+from meshbay_common.crypto import generate_gek
from meshbay_node.keystore import (
- NodeKeys,
create_keystore,
load_keystore,
- save_keystore,
load_or_create_keystore,
+ save_keystore,
)
-from meshbay_common.crypto import generate_gek
def test_create_and_load(tmp_path):
diff --git a/packages/meshbay-node/tests/test_leaseless_reads.py b/packages/meshbay-node/tests/test_leaseless_reads.py
index 6bd7f1f..2c4c6cd 100644
--- a/packages/meshbay-node/tests/test_leaseless_reads.py
+++ b/packages/meshbay-node/tests/test_leaseless_reads.py
@@ -23,9 +23,10 @@ import re
from pathlib import Path
import pytest
-
from meshbay_node.transfers import (
- LEASELESS_IDLE_SECS, MAX_LEASELESS_IN_FLIGHT, LeaselessReads,
+ LEASELESS_IDLE_SECS,
+ MAX_LEASELESS_IN_FLIGHT,
+ LeaselessReads,
)
SPA = (Path(__file__).resolve().parents[2] / "meshbay-hub" / "src"
diff --git a/packages/meshbay-node/tests/test_media_cache.py b/packages/meshbay-node/tests/test_media_cache.py
index dc78d8d..ec4e56e 100644
--- a/packages/meshbay-node/tests/test_media_cache.py
+++ b/packages/meshbay-node/tests/test_media_cache.py
@@ -3,8 +3,7 @@
import time
import pytest
-
-from meshbay_node.media_cache import MediaCache, TMDB_META_TTL_SECS, MUSICBRAINZ_META_TTL_SECS
+from meshbay_node.media_cache import MUSICBRAINZ_META_TTL_SECS, TMDB_META_TTL_SECS, MediaCache
@pytest.fixture
diff --git a/packages/meshbay-node/tests/test_media_cache_eviction.py b/packages/meshbay-node/tests/test_media_cache_eviction.py
index 587063a..f0292b5 100644
--- a/packages/meshbay-node/tests/test_media_cache_eviction.py
+++ b/packages/meshbay-node/tests/test_media_cache_eviction.py
@@ -18,7 +18,6 @@ about `create_all()`. Every existing node has a `thumbs` table without it.
import sqlite3
import pytest
-
from meshbay_node.media_cache import MediaCache
diff --git a/packages/meshbay-node/tests/test_multi_group.py b/packages/meshbay-node/tests/test_multi_group.py
index 3dc4778..b75bad0 100644
--- a/packages/meshbay-node/tests/test_multi_group.py
+++ b/packages/meshbay-node/tests/test_multi_group.py
@@ -7,19 +7,18 @@ Verifies that:
- A user in both groups can access both
"""
-import os
import time
+
import jwt
import pytest
-from pathlib import Path
-from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from cryptography.hazmat.primitives import serialization
-
+from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from meshbay_common.crypto import generate_gek, pk_to_b64
from meshbay_node.indexer import DirectoryIndexer
-from conftest import one_root
-from meshbay_node.transport.quic_server import QuicChunkServer
from meshbay_node.transport.quic_client import QuicChunkClient
+from meshbay_node.transport.quic_server import QuicChunkServer
+
+from conftest import one_root
@pytest.fixture
@@ -73,10 +72,12 @@ async def multi_group_server(sk_node, sk_hub, gek_a, gek_b, dir_a, dir_b, tmp_pa
hub_pk_pem = sk_hub.public_key().public_bytes(
serialization.Encoding.PEM, serialization.PublicFormat.SubjectPublicKeyInfo)
- indexer_a = DirectoryIndexer(roots=one_root(dir_a), group_id="group-a", sk_node=sk_node, gek=gek_a)
+ indexer_a = DirectoryIndexer(roots=one_root(dir_a), group_id="group-a",
+ sk_node=sk_node, gek=gek_a)
await indexer_a.initial_scan()
- indexer_b = DirectoryIndexer(roots=one_root(dir_b), group_id="group-b", sk_node=sk_node, gek=gek_b)
+ indexer_b = DirectoryIndexer(roots=one_root(dir_b), group_id="group-b",
+ sk_node=sk_node, gek=gek_b)
await indexer_b.initial_scan()
# RootSet, not a bare Path — what the daemon actually puts in a group context.
diff --git a/packages/meshbay-node/tests/test_musicbrainz_enabled_policy.py b/packages/meshbay-node/tests/test_musicbrainz_enabled_policy.py
index 76b687e..00c1b69 100644
--- a/packages/meshbay-node/tests/test_musicbrainz_enabled_policy.py
+++ b/packages/meshbay-node/tests/test_musicbrainz_enabled_policy.py
@@ -13,12 +13,11 @@ The contact string stays node-wide — see test_musicbrainz_config_policy.py.
from pathlib import Path
import pytest
-
+from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from meshbay_common.adminop import OP_MUSICBRAINZ_ENABLED
from meshbay_node.indexer.group_index import GroupIndex
from meshbay_node.roster import Roster
from meshbay_node.transport.webrtc_server import WebRTCPeerSession
-from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from conftest import one_root
diff --git a/packages/meshbay-node/tests/test_node_status.py b/packages/meshbay-node/tests/test_node_status.py
index 897be33..8bb2f39 100644
--- a/packages/meshbay-node/tests/test_node_status.py
+++ b/packages/meshbay-node/tests/test_node_status.py
@@ -12,22 +12,22 @@ from pathlib import Path
import pytest
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
-
-from conftest import one_root
from meshbay_common.adminop import (
- OP_ROOT_ADD, OP_ROOT_REMOVE, OP_GROUP_ATTACH, OP_MEMBER_UNPIN,
+ OP_MEMBER_UNPIN,
admin_transcript,
)
-from meshbay_node.transport.quic_server import Denylist
from meshbay_common.crypto import pk_to_b64
from meshbay_common.join import ROLE_OPERATOR
from meshbay_common.protocol import MNP
from meshbay_node import ops
from meshbay_node.indexer.group_index import GroupIndex
-from meshbay_node.roster import open_roster
from meshbay_node.roots import RootSet
+from meshbay_node.roster import open_roster
+from meshbay_node.transport.quic_server import Denylist
from meshbay_node.transport.webrtc_server import WebRTCPeerSession
+from conftest import one_root
+
GROUP = "g" * 32
@@ -207,7 +207,6 @@ async def test_node_status_catches_send_failure(tmp_path, roster):
not silently vanish — it used to, because _send was outside the try block."""
session = await _session(tmp_path, roster, operator=True,
node_user_id="grenet")
- original_send = session._send
sent = []
call_count = [0]
@@ -260,7 +259,7 @@ async def test_add_root_creates_directory_and_returns_info(tmp_path):
shared.mkdir()
new_dir = tmp_path / "new_root"
- from meshbay_node.config import NodeConfig, GroupConfig, RootSpec
+ from meshbay_node.config import GroupConfig, NodeConfig, RootSpec
cfg = GroupConfig(id=GROUP, name="test", roots=[
RootSpec(path=str(shared), name="shared", kind="generic", writable=True),
])
@@ -300,7 +299,7 @@ async def test_remove_root_requires_at_least_one_remaining(tmp_path):
shared = tmp_path / "shared"
shared.mkdir()
- from meshbay_node.config import GroupConfig, RootSpec, NodeConfig
+ from meshbay_node.config import GroupConfig, NodeConfig, RootSpec
cfg = GroupConfig(id=GROUP, name="test", roots=[
RootSpec(path=str(shared), name="shared", kind="generic", writable=True),
])
@@ -333,7 +332,7 @@ async def test_removing_a_writable_root_is_allowed(tmp_path):
d1.mkdir()
d2.mkdir()
- from meshbay_node.config import GroupConfig, RootSpec, NodeConfig
+ from meshbay_node.config import GroupConfig, NodeConfig, RootSpec
cfg = GroupConfig(id=GROUP, name="test", roots=[
RootSpec(path=str(d1), name="incoming", kind="generic", writable=True),
RootSpec(path=str(d2), name="shared", kind="generic", writable=False),
@@ -369,7 +368,7 @@ async def test_update_root_rewrites_the_flags_in_node_toml(tmp_path):
d1 = tmp_path / "media"
d1.mkdir()
- from meshbay_node.config import GroupConfig, RootSpec, NodeConfig
+ from meshbay_node.config import GroupConfig, NodeConfig, RootSpec
cfg = GroupConfig(id=GROUP, name="test", roots=[
RootSpec(path=str(d1), name="media", kind="generic", writable=False),
])
@@ -420,7 +419,7 @@ async def test_update_root_replaces_a_legacy_upload_line(tmp_path):
d1 = tmp_path / "media"
d1.mkdir()
- from meshbay_node.config import GroupConfig, RootSpec, NodeConfig
+ from meshbay_node.config import GroupConfig, NodeConfig, RootSpec
cfg = GroupConfig(id=GROUP, name="test", roots=[
RootSpec(path=str(d1), name="media", kind="generic", writable=True),
])
@@ -452,7 +451,7 @@ async def test_remove_root_succeeds_with_two_roots(tmp_path):
d1.mkdir()
d2.mkdir()
- from meshbay_node.config import GroupConfig, RootSpec, NodeConfig
+ from meshbay_node.config import GroupConfig, NodeConfig, RootSpec
cfg = GroupConfig(id=GROUP, name="test", roots=[
RootSpec(path=str(d1), name="dir1", kind="generic", writable=True),
RootSpec(path=str(d2), name="dir2", kind="generic", writable=False),
diff --git a/packages/meshbay-node/tests/test_packaging_win.py b/packages/meshbay-node/tests/test_packaging_win.py
index 7b44bbe..aa77c29 100644
--- a/packages/meshbay-node/tests/test_packaging_win.py
+++ b/packages/meshbay-node/tests/test_packaging_win.py
@@ -1230,13 +1230,14 @@ def test_service_mode_ps1_starts_the_task_after_installing_it():
already-running task pointlessly on every mode switch away from service.
"""
src = (WIN / "service-mode.ps1").read_text(encoding="utf-8")
- install_branch, remove_branch = src.split('$Action -eq "install"', 1)[1], None
+ install_branch = src.split('$Action -eq "install"', 1)[1]
assert '"run"' in install_branch or "'run'" in install_branch, (
"service-mode.ps1 registers the task but never starts it -- the "
"daemon stays down until the next reboot")
# The run step must be conditioned on install having actually succeeded,
# not fired unconditionally regardless of $Action.
- guard_line = src[src.index('$Action -eq "install"') - 40:src.index('$Action -eq "install"') + 40]
+ at = src.index('$Action -eq "install"')
+ guard_line = src[at - 40:at + 40]
assert "-and" in guard_line or "-not $failed" in install_branch, (
"the follow-up `run` must be gated on Action=install and success, "
"not run unconditionally on every invocation including remove")
diff --git a/packages/meshbay-node/tests/test_partial_uploads.py b/packages/meshbay-node/tests/test_partial_uploads.py
index 0270a69..80ab454 100644
--- a/packages/meshbay-node/tests/test_partial_uploads.py
+++ b/packages/meshbay-node/tests/test_partial_uploads.py
@@ -23,21 +23,23 @@ from pathlib import Path
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from meshbay_common.crypto import generate_gek
+from meshbay_common.protocol import (
+ UPLOAD_PROBE_INDEX,
+ file_upload_ack_payload,
+)
from meshbay_node.daemon import NodeDaemon
from meshbay_node.indexer.group_index import GroupIndex
from meshbay_node.roots import Root, RootSet
from meshbay_node.transport.webrtc_server import WebRTCPeerSession
-
-from meshbay_common.protocol import (
- UPLOAD_PROBE_INDEX, file_upload_ack_payload,
-)
-
-from conftest import one_root, sealed_upload
-
from meshbay_node.uploads import (
- ORPHAN_AFTER_SECS, PART_SUFFIX, PartialUploads, find_parts, orphaned_parts,
+ ORPHAN_AFTER_SECS,
+ PART_SUFFIX,
+ PartialUploads,
+ find_parts,
+ orphaned_parts,
)
+from conftest import one_root, sealed_upload
# ── the state ───────────────────────────────────────────────────────────────
@@ -466,7 +468,7 @@ async def test_an_upload_chunk_says_its_slot_is_in_use(tmp_path):
The download twin of this was fixed a day earlier; the same omission was
still here, invisible until uploads took a real lease.
"""
- from meshbay_node.transfers import TransferSlots, UPLOAD
+ from meshbay_node.transfers import UPLOAD, TransferSlots
ctx = _group_ctx(tmp_path)
peer = _peer(ctx)
diff --git a/packages/meshbay-node/tests/test_peer_session_limits.py b/packages/meshbay-node/tests/test_peer_session_limits.py
index 64961ad..d5c909b 100644
--- a/packages/meshbay-node/tests/test_peer_session_limits.py
+++ b/packages/meshbay-node/tests/test_peer_session_limits.py
@@ -15,10 +15,12 @@ import asyncio
from unittest.mock import MagicMock
import pytest
-
from meshbay_node.transport import webrtc_server as ws_mod
from meshbay_node.transport.webrtc_server import (
- MAX_PEER_SESSIONS, UNAUTHENTICATED_SESSION_TIMEOUT, WebRTCTransport)
+ MAX_PEER_SESSIONS,
+ UNAUTHENTICATED_SESSION_TIMEOUT,
+ WebRTCTransport,
+)
def _transport() -> WebRTCTransport:
diff --git a/packages/meshbay-node/tests/test_platform.py b/packages/meshbay-node/tests/test_platform.py
index 3fb27f3..5c80c5b 100644
--- a/packages/meshbay-node/tests/test_platform.py
+++ b/packages/meshbay-node/tests/test_platform.py
@@ -5,8 +5,8 @@ here by monkeypatching that (and `os.environ`) rather than only on the OS the
suite happens to run on.
"""
-import os
import asyncio
+import os
import sys
from pathlib import Path
from unittest.mock import Mock
diff --git a/packages/meshbay-node/tests/test_poster_cache.py b/packages/meshbay-node/tests/test_poster_cache.py
index bbd824d..908fb69 100644
--- a/packages/meshbay-node/tests/test_poster_cache.py
+++ b/packages/meshbay-node/tests/test_poster_cache.py
@@ -15,7 +15,6 @@ already uses to resolve a thumbnail by id.
"""
import pytest
-
from meshbay_node.media_cache import MediaCache
from meshbay_node.transport.webrtc_server import WebRTCPeerSession
diff --git a/packages/meshbay-node/tests/test_quic_transport.py b/packages/meshbay-node/tests/test_quic_transport.py
index fb28295..8eb0622 100644
--- a/packages/meshbay-node/tests/test_quic_transport.py
+++ b/packages/meshbay-node/tests/test_quic_transport.py
@@ -6,17 +6,17 @@ Same structure as test_transport.py but uses QUIC instead of TCP+TLS.
import asyncio
import os
import time
+
import jwt
import pytest
-from pathlib import Path
-from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from cryptography.hazmat.primitives import serialization
-
+from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from meshbay_common.crypto import generate_gek, pk_to_b64
from meshbay_node.indexer import DirectoryIndexer
-from conftest import one_root
-from meshbay_node.transport.quic_server import QuicChunkServer, Denylist
from meshbay_node.transport.quic_client import QuicChunkClient
+from meshbay_node.transport.quic_server import Denylist, QuicChunkServer
+
+from conftest import one_root
@pytest.fixture
diff --git a/packages/meshbay-node/tests/test_rename_reenrichment.py b/packages/meshbay-node/tests/test_rename_reenrichment.py
index f6761d9..7ddca6f 100644
--- a/packages/meshbay-node/tests/test_rename_reenrichment.py
+++ b/packages/meshbay-node/tests/test_rename_reenrichment.py
@@ -17,9 +17,8 @@ import asyncio
import pytest
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
-
from meshbay_common.crypto import generate_gek
-from meshbay_node.config import Config, HubConfig, NodeConfig, GroupConfig, KeystoreConfig
+from meshbay_node.config import Config, GroupConfig, HubConfig, KeystoreConfig, NodeConfig
from meshbay_node.daemon import NodeDaemon
from meshbay_node.indexer import DirectoryIndexer
from meshbay_node.media_cache import MediaCache
diff --git a/packages/meshbay-node/tests/test_replug_restores_enrichment.py b/packages/meshbay-node/tests/test_replug_restores_enrichment.py
index 04a06ae..a2d2db4 100644
--- a/packages/meshbay-node/tests/test_replug_restores_enrichment.py
+++ b/packages/meshbay-node/tests/test_replug_restores_enrichment.py
@@ -46,10 +46,8 @@ import os
import pytest
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
-
from meshbay_common.crypto import generate_gek
-from meshbay_node.config import (Config, GroupConfig, HubConfig, KeystoreConfig,
- NodeConfig)
+from meshbay_node.config import Config, GroupConfig, HubConfig, KeystoreConfig, NodeConfig
from meshbay_node.daemon import NodeDaemon
from meshbay_node.indexer import DirectoryIndexer
from meshbay_node.indexer.enrich_audio import AudioEnricher
diff --git a/packages/meshbay-node/tests/test_root_availability.py b/packages/meshbay-node/tests/test_root_availability.py
index 2d848c7..82bf6bd 100644
--- a/packages/meshbay-node/tests/test_root_availability.py
+++ b/packages/meshbay-node/tests/test_root_availability.py
@@ -13,13 +13,11 @@ an indexer that treats a vanished root as a set of deletions, which is what the
straightforward implementation does.
"""
-import asyncio
import os
from pathlib import Path
import pytest
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
-
from meshbay_node.indexer.indexer import DirectoryIndexer
from meshbay_node.roots import RootSet
diff --git a/packages/meshbay-node/tests/test_root_eject.py b/packages/meshbay-node/tests/test_root_eject.py
index f57fb92..d73e71c 100644
--- a/packages/meshbay-node/tests/test_root_eject.py
+++ b/packages/meshbay-node/tests/test_root_eject.py
@@ -21,7 +21,6 @@ from pathlib import Path
import pytest
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
-
from meshbay_node.indexer.indexer import DirectoryIndexer
from meshbay_node.roots import RootSet
from meshbay_node.roster import Roster
diff --git a/packages/meshbay-node/tests/test_root_ops_reach_the_live_set.py b/packages/meshbay-node/tests/test_root_ops_reach_the_live_set.py
index 976af82..6a52c06 100644
--- a/packages/meshbay-node/tests/test_root_ops_reach_the_live_set.py
+++ b/packages/meshbay-node/tests/test_root_ops_reach_the_live_set.py
@@ -28,7 +28,6 @@ from types import SimpleNamespace
import pytest
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
-
from meshbay_node import ops
from meshbay_node.config import GroupConfig, NodeConfig, RootSpec
from meshbay_node.indexer.group_index import GroupIndex
diff --git a/packages/meshbay-node/tests/test_root_paths_are_operator_only.py b/packages/meshbay-node/tests/test_root_paths_are_operator_only.py
index 080d4be..0e89afa 100644
--- a/packages/meshbay-node/tests/test_root_paths_are_operator_only.py
+++ b/packages/meshbay-node/tests/test_root_paths_are_operator_only.py
@@ -21,7 +21,6 @@ import inspect
import re
from pathlib import Path
-
from meshbay_node import daemon as daemon_mod
from meshbay_node import ops
from meshbay_node.roots import RootSet
diff --git a/packages/meshbay-node/tests/test_root_writable_policy.py b/packages/meshbay-node/tests/test_root_writable_policy.py
index 3c8a837..0cd9af8 100644
--- a/packages/meshbay-node/tests/test_root_writable_policy.py
+++ b/packages/meshbay-node/tests/test_root_writable_policy.py
@@ -26,15 +26,15 @@ from pathlib import Path
import pytest
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
-
-from conftest import sealed_upload
-from meshbay_common.adminop import OP_ROOT_UPDATE, OP_ROOT_EJECT, OP_ROOT_PLUG
+from meshbay_common.adminop import OP_ROOT_EJECT, OP_ROOT_PLUG, OP_ROOT_UPDATE
from meshbay_common.crypto import generate_gek
from meshbay_common.protocol import MNP
from meshbay_node.indexer.group_index import GroupIndex
from meshbay_node.roots import RootSet
from meshbay_node.transport.webrtc_server import WebRTCPeerSession
+from conftest import sealed_upload
+
pytestmark = pytest.mark.asyncio
diff --git a/packages/meshbay-node/tests/test_roots.py b/packages/meshbay-node/tests/test_roots.py
index 9233180..004f004 100644
--- a/packages/meshbay-node/tests/test_roots.py
+++ b/packages/meshbay-node/tests/test_roots.py
@@ -7,15 +7,17 @@ file went to the wrong disk" or "the same film is listed twice and deleting one
copy breaks the other".
"""
-from pathlib import Path
import pytest
-
+from meshbay_common.protocol import IndexEntry
from meshbay_node.roots import (
- Root, RootError, RootSet, entry_abs_path,
- SAFE_UPLOAD_NAME, safe_subdir, _free_name,
+ SAFE_UPLOAD_NAME,
+ RootError,
+ RootSet,
+ _free_name,
+ entry_abs_path,
+ safe_subdir,
)
-from meshbay_common.protocol import IndexEntry
def _spec(path, **kw):
diff --git a/packages/meshbay-node/tests/test_roster_pairing.py b/packages/meshbay-node/tests/test_roster_pairing.py
index 2e8c183..82336e2 100644
--- a/packages/meshbay-node/tests/test_roster_pairing.py
+++ b/packages/meshbay-node/tests/test_roster_pairing.py
@@ -18,14 +18,13 @@ import pytest
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey
-
from meshbay_common.crypto import generate_gek, pk_to_b64, unwrap_gek_aes
from meshbay_common.join import ROLE_MEMBER, ROLE_OPERATOR, join_transcript
from meshbay_node.indexer.group_index import GroupIndex
-from conftest import one_root
from meshbay_node.roster import Roster, hash_code, normalize_code
from meshbay_node.transport.webrtc_server import WebRTCPeerSession
+from conftest import one_root
# ── Fixtures ──────────────────────────────────────────────────────────────────
@@ -570,7 +569,8 @@ def test_challenge_carries_node_pk_in_source():
builds, whatever the surrounding handshake does.
"""
source = (Path(__file__).parent.parent
- / "src" / "meshbay_node" / "transport" / "webrtc_server.py").read_text(encoding="utf-8")
+ / "src" / "meshbay_node" / "transport"
+ / "webrtc_server.py").read_text(encoding="utf-8")
challenge = source[source.find("MNP.HANDSHAKE_CHALLENGE,"):]
challenge = challenge[:challenge.find("})")]
assert "node_pk" in challenge, (
@@ -694,7 +694,6 @@ async def test_unpinned_operator_loses_authority_immediately(tmp_path, roster):
def _ui_client(tmp_path, roster, **extra):
from fastapi.testclient import TestClient
-
from meshbay_node.config import Config
from meshbay_node.ui.app import create_ui_app
diff --git a/packages/meshbay-node/tests/test_scan_settings_policy.py b/packages/meshbay-node/tests/test_scan_settings_policy.py
index 94f4421..926c94f 100644
--- a/packages/meshbay-node/tests/test_scan_settings_policy.py
+++ b/packages/meshbay-node/tests/test_scan_settings_policy.py
@@ -13,7 +13,6 @@ from pathlib import Path
import pytest
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
-
from meshbay_common.adminop import OP_SET_SCAN_SETTINGS
from meshbay_common.crypto import generate_gek
from meshbay_node import ops
diff --git a/packages/meshbay-node/tests/test_season_and_search_requests.py b/packages/meshbay-node/tests/test_season_and_search_requests.py
index 4141d13..7e81e63 100644
--- a/packages/meshbay-node/tests/test_season_and_search_requests.py
+++ b/packages/meshbay-node/tests/test_season_and_search_requests.py
@@ -10,7 +10,6 @@ test_tmdb_override_policy.py.
"""
import pytest
-
from meshbay_common.protocol import MNP
from meshbay_node.media_cache import MediaCache
from meshbay_node.transport.webrtc_server import WebRTCPeerSession
diff --git a/packages/meshbay-node/tests/test_security_regressions.py b/packages/meshbay-node/tests/test_security_regressions.py
index d6ecb71..7010523 100644
--- a/packages/meshbay-node/tests/test_security_regressions.py
+++ b/packages/meshbay-node/tests/test_security_regressions.py
@@ -9,20 +9,18 @@ only ever exercised happy paths, never an authorization boundary.
If one of these starts failing, a fix has been reverted. Do not "fix" the test.
"""
-import base64
import struct
from pathlib import Path
import pytest
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
-
from meshbay_common.crypto import generate_gek
-from meshbay_common.protocol import IndexEntry
from meshbay_node.indexer.group_index import GroupIndex
from meshbay_node.roots import RootSet
-from conftest import one_root, opened_ack, sealed_upload
from meshbay_node.transport.webrtc_server import WebRTCPeerSession
+from conftest import one_root, opened_ack, sealed_upload
+
def _safe_name_re():
"""
@@ -64,7 +62,7 @@ def test_daemon_exposes_no_plaintext_listener():
C1: the daemon must not bind anything that serves content without a handshake.
NodeConfig no longer carries an HTTP port at all.
"""
- from meshbay_node.config import NodeConfig, GroupConfig
+ from meshbay_node.config import GroupConfig, NodeConfig
assert "http_port" not in NodeConfig.__dataclass_fields__
assert "http_port" not in GroupConfig.__dataclass_fields__
@@ -464,7 +462,8 @@ def test_no_member_can_hand_the_node_key_material(tmp_path):
)
source = (Path(__file__).parent.parent
- / "src" / "meshbay_node" / "transport" / "webrtc_server.py").read_text(encoding="utf-8")
+ / "src" / "meshbay_node" / "transport"
+ / "webrtc_server.py").read_text(encoding="utf-8")
assert "_do_gek_bundle_store" not in source
assert "_admin_exec_bundle_store" not in source
@@ -568,6 +567,7 @@ def test_admin_signature_does_not_transfer_between_operations(tmp_path):
def test_admin_challenge_expires(tmp_path):
"""H5: a stale challenge must not be usable."""
import time as _time
+
from meshbay_common.adminop import ADMIN_CHALLENGE_TTL, OP_FILE_DELETE
session = _session(tmp_path, "operator")
@@ -633,6 +633,7 @@ def test_keystore_records_argon2_params_for_migration(tmp_path):
envelope records the parameters it was written with.
"""
import json
+
from meshbay_node.keystore import create_keystore, load_keystore
path = tmp_path / "keystore.enc"
@@ -648,11 +649,17 @@ def test_legacy_keystore_still_opens(tmp_path):
"""M2: a keystore written under the 64 MB profile must still unlock."""
import base64 as _b64
import json
+
import msgpack
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey
from meshbay_common.crypto import (
- LEGACY_ARGON2_ITERATIONS, LEGACY_ARGON2_LANES, LEGACY_ARGON2_MEMORY_COST,
- derive_keystore_key, encrypt_keystore, pk_to_b64, sk_to_b64,
+ LEGACY_ARGON2_ITERATIONS,
+ LEGACY_ARGON2_LANES,
+ LEGACY_ARGON2_MEMORY_COST,
+ derive_keystore_key,
+ encrypt_keystore,
+ pk_to_b64,
+ sk_to_b64,
)
from meshbay_node.keystore import load_keystore
@@ -717,7 +724,9 @@ def test_pre_handshake_message_budget_is_small():
unauthenticated peer could announce a huge frame and dribble bytes into it.
"""
from meshbay_node.transport.webrtc_server import (
- MAX_MSG, PRE_HANDSHAKE_MAX_MSG, _DataChannelBuffer,
+ MAX_MSG,
+ PRE_HANDSHAKE_MAX_MSG,
+ _DataChannelBuffer,
)
assert PRE_HANDSHAKE_MAX_MSG <= 1024 * 1024
assert PRE_HANDSHAKE_MAX_MSG < MAX_MSG
@@ -918,8 +927,8 @@ async def test_an_identified_device_that_is_not_an_operator_is_refused(tmp_path)
and `operator_pks()` is rebuilt from the roster on every call so a revoked
one stops working at once.
"""
- from meshbay_node.roster import Roster
from meshbay_common.crypto import pk_to_b64
+ from meshbay_node.roster import Roster
roster = Roster(db_path=tmp_path / "roster.db")
await roster.open()
@@ -940,9 +949,9 @@ async def test_an_identified_device_that_is_not_an_operator_is_refused(tmp_path)
async def test_a_paired_operator_device_is_what_opens_it(tmp_path):
"""The positive case, so the test above is about authority and not about
everything being refused."""
- from meshbay_node.roster import Roster
- from meshbay_common.join import ROLE_OPERATOR
from meshbay_common.crypto import pk_to_b64
+ from meshbay_common.join import ROLE_OPERATOR
+ from meshbay_node.roster import Roster
roster = Roster(db_path=tmp_path / "roster.db")
await roster.open()
diff --git a/packages/meshbay-node/tests/test_startup_scan_enrichment.py b/packages/meshbay-node/tests/test_startup_scan_enrichment.py
index f43c6e9..f3ad2ca 100644
--- a/packages/meshbay-node/tests/test_startup_scan_enrichment.py
+++ b/packages/meshbay-node/tests/test_startup_scan_enrichment.py
@@ -21,12 +21,9 @@ is exercised the same way a real operator's group would be.
import asyncio
import pytest
-from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
-from unittest.mock import MagicMock
-
from meshbay_common.crypto import generate_gek
-from meshbay_node.config import Config, HubConfig, NodeConfig, GroupConfig, KeystoreConfig
+from meshbay_node.config import Config, GroupConfig, HubConfig, KeystoreConfig, NodeConfig
from meshbay_node.daemon import NodeDaemon
from meshbay_node.indexer import DirectoryIndexer
from meshbay_node.indexer.enrich import Enricher
diff --git a/packages/meshbay-node/tests/test_stream_audio_track_selection.py b/packages/meshbay-node/tests/test_stream_audio_track_selection.py
index 53781f9..b9e2105 100644
--- a/packages/meshbay-node/tests/test_stream_audio_track_selection.py
+++ b/packages/meshbay-node/tests/test_stream_audio_track_selection.py
@@ -23,7 +23,6 @@ from pathlib import Path
import pytest
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
-
from meshbay_common.crypto import generate_gek
from meshbay_common.webcrypto import chunk_key_aes, decrypt_chunk_aes
from meshbay_node.indexer.group_index import GroupIndex
diff --git a/packages/meshbay-node/tests/test_stream_audio_transcode.py b/packages/meshbay-node/tests/test_stream_audio_transcode.py
index 92518ba..6c9bde7 100644
--- a/packages/meshbay-node/tests/test_stream_audio_transcode.py
+++ b/packages/meshbay-node/tests/test_stream_audio_transcode.py
@@ -22,7 +22,6 @@ from pathlib import Path
import pytest
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
-
from meshbay_common.crypto import generate_gek
from meshbay_common.webcrypto import chunk_key_aes, decrypt_chunk_aes
from meshbay_node.indexer.group_index import GroupIndex
@@ -44,7 +43,7 @@ def _make_clip(path: Path, *, acodec: str, channels: int = 2) -> None:
subprocess.run(
["ffmpeg", "-hide_banner", "-loglevel", "error", "-y",
"-f", "lavfi", "-i", "testsrc=size=320x240:rate=25:duration=1",
- "-f", "lavfi", "-i", f"sine=frequency=440:duration=1:sample_rate=48000",
+ "-f", "lavfi", "-i", "sine=frequency=440:duration=1:sample_rate=48000",
"-ac", str(channels),
"-c:v", "libx264", "-preset", "ultrafast", "-c:a", acodec,
str(path)],
diff --git a/packages/meshbay-node/tests/test_stream_capacity.py b/packages/meshbay-node/tests/test_stream_capacity.py
index a35ece8..7ce6bf6 100644
--- a/packages/meshbay-node/tests/test_stream_capacity.py
+++ b/packages/meshbay-node/tests/test_stream_capacity.py
@@ -17,9 +17,10 @@ find.
import asyncio
import pytest
-
from meshbay_node.transport.webrtc_server import (
- MAX_CONCURRENT_TRANSCODES, WebRTCPeerSession, WebRTCTransport,
+ MAX_CONCURRENT_TRANSCODES,
+ WebRTCPeerSession,
+ WebRTCTransport,
)
@@ -35,11 +36,11 @@ def transport(tmp_path):
"""A real WebRTCTransport. Its keys and index are genuine but incidental —
nothing below the capacity code reads them."""
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
-
- from conftest import one_root
from meshbay_common.crypto import generate_gek
from meshbay_node.indexer.group_index import GroupIndex
+ from conftest import one_root
+
sk_node = Ed25519PrivateKey.generate()
gek = generate_gek()
shared = tmp_path / "shared"
diff --git a/packages/meshbay-node/tests/test_stream_capacity_config.py b/packages/meshbay-node/tests/test_stream_capacity_config.py
index 442d3ef..c788ba6 100644
--- a/packages/meshbay-node/tests/test_stream_capacity_config.py
+++ b/packages/meshbay-node/tests/test_stream_capacity_config.py
@@ -23,7 +23,6 @@ import textwrap
from pathlib import Path
import pytest
-
from meshbay_node.config import load_config
from meshbay_node.roots import RootSet
from meshbay_node.transport.webrtc_server import (
diff --git a/packages/meshbay-node/tests/test_stream_seek_audio_alignment.py b/packages/meshbay-node/tests/test_stream_seek_audio_alignment.py
index c1d0c66..5d78c00 100644
--- a/packages/meshbay-node/tests/test_stream_seek_audio_alignment.py
+++ b/packages/meshbay-node/tests/test_stream_seek_audio_alignment.py
@@ -29,7 +29,6 @@ from pathlib import Path
import pytest
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
-
from meshbay_common.crypto import generate_gek
from meshbay_common.webcrypto import chunk_key_aes, decrypt_chunk_aes
from meshbay_node.indexer.group_index import GroupIndex
diff --git a/packages/meshbay-node/tests/test_stream_seek_reports_the_keyframe.py b/packages/meshbay-node/tests/test_stream_seek_reports_the_keyframe.py
index 9aea2b4..c575572 100644
--- a/packages/meshbay-node/tests/test_stream_seek_reports_the_keyframe.py
+++ b/packages/meshbay-node/tests/test_stream_seek_reports_the_keyframe.py
@@ -35,7 +35,6 @@ from pathlib import Path
import pytest
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
-
from meshbay_common.crypto import generate_gek
from meshbay_common.webcrypto import chunk_key_aes, decrypt_chunk_aes
from meshbay_node.indexer.group_index import GroupIndex
diff --git a/packages/meshbay-node/tests/test_stream_subtitle_tracks.py b/packages/meshbay-node/tests/test_stream_subtitle_tracks.py
index d3bcc8b..7a27a37 100644
--- a/packages/meshbay-node/tests/test_stream_subtitle_tracks.py
+++ b/packages/meshbay-node/tests/test_stream_subtitle_tracks.py
@@ -33,7 +33,6 @@ from pathlib import Path
import pytest
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
-
from meshbay_common.crypto import generate_gek
from meshbay_common.webcrypto import chunk_key_aes, decrypt_chunk_aes
from meshbay_node.indexer.group_index import GroupIndex
diff --git a/packages/meshbay-node/tests/test_stream_video_transcode.py b/packages/meshbay-node/tests/test_stream_video_transcode.py
index 316fdce..b60d546 100644
--- a/packages/meshbay-node/tests/test_stream_video_transcode.py
+++ b/packages/meshbay-node/tests/test_stream_video_transcode.py
@@ -32,7 +32,6 @@ from pathlib import Path
import pytest
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
-
from meshbay_common.crypto import generate_gek
from meshbay_common.webcrypto import chunk_key_aes, decrypt_chunk_aes
from meshbay_node import hwaccel
diff --git a/packages/meshbay-node/tests/test_title_parse.py b/packages/meshbay-node/tests/test_title_parse.py
index 2c4573a..95c798b 100644
--- a/packages/meshbay-node/tests/test_title_parse.py
+++ b/packages/meshbay-node/tests/test_title_parse.py
@@ -17,7 +17,6 @@ from meshbay_node.indexer.title_parse import (
year_in,
)
-
# ── §3.3 row: plain, well-formed movie filename ──────────────────────────────
def test_plain_movie_filename_parses_confidently():
diff --git a/packages/meshbay-node/tests/test_tmdb.py b/packages/meshbay-node/tests/test_tmdb.py
index 299ff2a..981357a 100644
--- a/packages/meshbay-node/tests/test_tmdb.py
+++ b/packages/meshbay-node/tests/test_tmdb.py
@@ -2,7 +2,6 @@
import httpx
import pytest
-
from meshbay_node.tmdb import TmdbClient
diff --git a/packages/meshbay-node/tests/test_tmdb_config_policy.py b/packages/meshbay-node/tests/test_tmdb_config_policy.py
index cef4fda..6a51eb0 100644
--- a/packages/meshbay-node/tests/test_tmdb_config_policy.py
+++ b/packages/meshbay-node/tests/test_tmdb_config_policy.py
@@ -21,12 +21,11 @@ wire (see _issue_admin_challenge's docstring).
from pathlib import Path
import pytest
-
+from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from meshbay_common.adminop import OP_TMDB_CONFIG
from meshbay_node.indexer.group_index import GroupIndex
from meshbay_node.roster import Roster
from meshbay_node.transport.webrtc_server import WebRTCPeerSession
-from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from conftest import one_root
diff --git a/packages/meshbay-node/tests/test_tmdb_enabled_policy.py b/packages/meshbay-node/tests/test_tmdb_enabled_policy.py
index 0a748cd..10d1e2b 100644
--- a/packages/meshbay-node/tests/test_tmdb_enabled_policy.py
+++ b/packages/meshbay-node/tests/test_tmdb_enabled_policy.py
@@ -14,12 +14,11 @@ test_tmdb_config_policy.py for those.
from pathlib import Path
import pytest
-
+from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from meshbay_common.adminop import OP_TMDB_ENABLED
from meshbay_node.indexer.group_index import GroupIndex
from meshbay_node.roster import Roster
from meshbay_node.transport.webrtc_server import WebRTCPeerSession
-from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from conftest import one_root
diff --git a/packages/meshbay-node/tests/test_tmdb_language_fallback.py b/packages/meshbay-node/tests/test_tmdb_language_fallback.py
index 52fe4c4..62171c3 100644
--- a/packages/meshbay-node/tests/test_tmdb_language_fallback.py
+++ b/packages/meshbay-node/tests/test_tmdb_language_fallback.py
@@ -9,7 +9,6 @@ field, or silently showing a blank overview/poster.
"""
import pytest
-
from meshbay_node.transport.webrtc_server import WebRTCPeerSession
pytestmark = pytest.mark.asyncio
diff --git a/packages/meshbay-node/tests/test_tmdb_override_policy.py b/packages/meshbay-node/tests/test_tmdb_override_policy.py
index d434e1a..aed3ea2 100644
--- a/packages/meshbay-node/tests/test_tmdb_override_policy.py
+++ b/packages/meshbay-node/tests/test_tmdb_override_policy.py
@@ -14,9 +14,8 @@ from pathlib import Path
import pytest
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
-
from meshbay_common.adminop import OP_TMDB_OVERRIDE
-from meshbay_common.protocol import IndexEntry, MNP
+from meshbay_common.protocol import MNP, IndexEntry
from meshbay_node.indexer.group_index import GroupIndex
from meshbay_node.media_cache import MediaCache
from meshbay_node.transport.webrtc_server import WebRTCPeerSession
diff --git a/packages/meshbay-node/tests/test_tmdb_rematch_policy.py b/packages/meshbay-node/tests/test_tmdb_rematch_policy.py
index 198d75b..4d43552 100644
--- a/packages/meshbay-node/tests/test_tmdb_rematch_policy.py
+++ b/packages/meshbay-node/tests/test_tmdb_rematch_policy.py
@@ -9,7 +9,6 @@ asking for a fresh resolution.
import hashlib
import pytest
-from conftest import one_root
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from meshbay_common.adminop import OP_TMDB_REMATCH
from meshbay_common.protocol import MNP, IndexEntry
@@ -17,6 +16,8 @@ from meshbay_node.indexer.group_index import GroupIndex
from meshbay_node.media_cache import MediaCache
from meshbay_node.transport.webrtc_server import WebRTCPeerSession
+from conftest import one_root
+
pytestmark = pytest.mark.asyncio
diff --git a/packages/meshbay-node/tests/test_tmdb_show_director.py b/packages/meshbay-node/tests/test_tmdb_show_director.py
index cb31c2c..fd324b1 100644
--- a/packages/meshbay-node/tests/test_tmdb_show_director.py
+++ b/packages/meshbay-node/tests/test_tmdb_show_director.py
@@ -12,7 +12,6 @@ the show details. Several creators is ordinary, so they join into one line.
"""
import pytest
-
from meshbay_node.transport.webrtc_server import WebRTCPeerSession
pytestmark = pytest.mark.asyncio
diff --git a/packages/meshbay-node/tests/test_transfer_settings.py b/packages/meshbay-node/tests/test_transfer_settings.py
index bcfa6a8..a2c7fd9 100644
--- a/packages/meshbay-node/tests/test_transfer_settings.py
+++ b/packages/meshbay-node/tests/test_transfer_settings.py
@@ -19,10 +19,12 @@ shape of the bug this whole branch started from (`webrtc._stream_sem`).
"""
import pytest
-
from meshbay_node.roster import Roster
from meshbay_node.transfers import (
- DEFAULT_MAX_PER_MEMBER, DOWNLOAD, UPLOAD, TransferSlots,
+ DEFAULT_MAX_PER_MEMBER,
+ DOWNLOAD,
+ UPLOAD,
+ TransferSlots,
)
diff --git a/packages/meshbay-node/tests/test_transfer_slots.py b/packages/meshbay-node/tests/test_transfer_slots.py
index 7056e93..08e7e70 100644
--- a/packages/meshbay-node/tests/test_transfer_slots.py
+++ b/packages/meshbay-node/tests/test_transfer_slots.py
@@ -21,12 +21,18 @@ by reasoning about it.
import random
import pytest
-
from meshbay_node.transfers import (
- DOWNLOAD, GRANT_DEADLINE_SECS, IDLE_TIMEOUT_SECS, KINDS,
- MAX_MISSED_GRANTS, MAX_QUEUED_PER_MEMBER, REASON_ABANDONED, REASON_IDLE,
- REASON_NOT_TAKEN_UP, TransferSlots,
+ DOWNLOAD,
+ GRANT_DEADLINE_SECS,
+ IDLE_TIMEOUT_SECS,
+ KINDS,
+ MAX_MISSED_GRANTS,
+ MAX_QUEUED_PER_MEMBER,
+ REASON_ABANDONED,
+ REASON_IDLE,
+ REASON_NOT_TAKEN_UP,
UPLOAD,
+ TransferSlots,
)
diff --git a/packages/meshbay-node/tests/test_transfer_slots_wire.py b/packages/meshbay-node/tests/test_transfer_slots_wire.py
index c9fc85a..d96456c 100644
--- a/packages/meshbay-node/tests/test_transfer_slots_wire.py
+++ b/packages/meshbay-node/tests/test_transfer_slots_wire.py
@@ -19,6 +19,9 @@ Three things can only be checked here:
"""
import pytest
+from meshbay_common.protocol import MNP
+from meshbay_node.transfers import DOWNLOAD, UPLOAD
+from meshbay_node.transport.webrtc_server import WebRTCPeerSession
# Every test drives a message handler, and in the node a message handler always
# runs inside the event loop: `_do_transfer_open` starts the sweeper task there.
@@ -26,10 +29,6 @@ import pytest
# on "no current event loop" the moment the sweeper stopped being faked.
pytestmark = pytest.mark.asyncio
-from meshbay_common.protocol import MNP
-from meshbay_node.transfers import DOWNLOAD, UPLOAD
-from meshbay_node.transport.webrtc_server import WebRTCPeerSession
-
class _Session(WebRTCPeerSession):
"""A session with the DataChannel replaced by a list, and nothing else."""
diff --git a/packages/meshbay-node/tests/test_transport_wire_parity.py b/packages/meshbay-node/tests/test_transport_wire_parity.py
index bc6b134..6e74b28 100644
--- a/packages/meshbay-node/tests/test_transport_wire_parity.py
+++ b/packages/meshbay-node/tests/test_transport_wire_parity.py
@@ -16,7 +16,6 @@ again.
import inspect
import pytest
-from conftest import one_root
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from meshbay_common.crypto import generate_gek
from meshbay_common.groupbox import PURPOSE_INDEX, unseal
@@ -25,6 +24,8 @@ from meshbay_node.indexer import DirectoryIndexer
from meshbay_node.transport import quic_server, webrtc_server
from meshbay_node.transport.wire import index_sync_message
+from conftest import one_root
+
@pytest.fixture
def gek():
diff --git a/packages/meshbay-node/tests/test_upload_size_cap.py b/packages/meshbay-node/tests/test_upload_size_cap.py
index dca6e15..cc04eba 100644
--- a/packages/meshbay-node/tests/test_upload_size_cap.py
+++ b/packages/meshbay-node/tests/test_upload_size_cap.py
@@ -17,7 +17,6 @@ import textwrap
from pathlib import Path
import pytest
-
from meshbay_node.config import load_config
from meshbay_node.roots import RootSet
from meshbay_node.transport.webrtc_server import (
diff --git a/packages/meshbay-node/tests/test_video_root_gates_enrichment.py b/packages/meshbay-node/tests/test_video_root_gates_enrichment.py
index d06b3f4..c39876a 100644
--- a/packages/meshbay-node/tests/test_video_root_gates_enrichment.py
+++ b/packages/meshbay-node/tests/test_video_root_gates_enrichment.py
@@ -17,10 +17,9 @@ import asyncio
import pytest
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
-
from meshbay_common.crypto import generate_gek
from meshbay_node import ops
-from meshbay_node.config import Config, HubConfig, NodeConfig, GroupConfig, KeystoreConfig
+from meshbay_node.config import Config, GroupConfig, HubConfig, KeystoreConfig, NodeConfig
from meshbay_node.daemon import NodeDaemon
from meshbay_node.indexer import DirectoryIndexer
from meshbay_node.indexer.enrich import Enricher
diff --git a/packages/meshbay-node/tests/test_webrtc_transport.py b/packages/meshbay-node/tests/test_webrtc_transport.py
index 0839d42..91a6e1c 100644
--- a/packages/meshbay-node/tests/test_webrtc_transport.py
+++ b/packages/meshbay-node/tests/test_webrtc_transport.py
@@ -9,52 +9,58 @@ Uses local loopback (no STUN/ICE needed for localhost).
import asyncio
import base64
-from contextlib import asynccontextmanager
import hashlib
import hmac
import os
import struct
import time
+from contextlib import asynccontextmanager
import jwt
import msgpack
import pytest
+from aiortc import RTCPeerConnection, RTCSessionDescription
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric.ed25519 import (
- Ed25519PrivateKey, Ed25519PublicKey,
+ Ed25519PrivateKey,
+ Ed25519PublicKey,
)
-from aiortc import RTCPeerConnection, RTCSessionDescription
-
from meshbay_common import MNP_VERSION
+from meshbay_common.adminop import (
+ OP_FILE_DELETE,
+ OP_INVITE_CREATE,
+ admin_transcript,
+)
from meshbay_common.crypto import (
generate_gek,
pk_to_b64,
- wrap_gek,
- wrap_gek_aes,
unwrap_gek,
unwrap_gek_aes,
+ wrap_gek,
+ wrap_gek_aes,
)
from meshbay_common.groupbox import PURPOSE_ACK, PURPOSE_INDEX, unseal
-from meshbay_common.webcrypto import chunk_key_aes, decrypt_chunk_aes
-from meshbay_common.protocol import MNP
-TEST_GROUP = "g"
-
from meshbay_common.handshake import (
- NONCE_LEN, ROLE_CLIENT, ROLE_NODE, handshake_transcript,
- make_proof, verify_proof, webrtc_binding,
-)
-from meshbay_common.adminop import (
- OP_FILE_DELETE,
- OP_INVITE_CREATE,
- admin_transcript,
+ NONCE_LEN,
+ ROLE_CLIENT,
+ ROLE_NODE,
+ handshake_transcript,
+ make_proof,
+ verify_proof,
+ webrtc_binding,
)
from meshbay_common.join import ROLE_MEMBER, ROLE_OPERATOR, join_transcript
+from meshbay_common.protocol import MNP
+from meshbay_common.webcrypto import chunk_key_aes, decrypt_chunk_aes
from meshbay_node.bundle_store import BundleStore
-from conftest import one_root
-from meshbay_node.roster import Roster
from meshbay_node.indexer import DirectoryIndexer
+from meshbay_node.roster import Roster
from meshbay_node.transport.webrtc_server import WebRTCTransport
+from conftest import one_root
+
+TEST_GROUP = "g"
+
@pytest.fixture
def sk_node():
diff --git a/packages/meshbay-node/tests/test_windows_root_shapes.py b/packages/meshbay-node/tests/test_windows_root_shapes.py
index 5c5da25..56279b1 100644
--- a/packages/meshbay-node/tests/test_windows_root_shapes.py
+++ b/packages/meshbay-node/tests/test_windows_root_shapes.py
@@ -27,7 +27,6 @@ import tomllib
from pathlib import Path, PureWindowsPath
import pytest
-
from meshbay_node.roots import RootError, RootSet, derive_name
BS = chr(92)
diff --git a/packages/meshbay-node/tests/test_wizard_apps_endpoint.py b/packages/meshbay-node/tests/test_wizard_apps_endpoint.py
index 10ab489..77fa9a6 100644
--- a/packages/meshbay-node/tests/test_wizard_apps_endpoint.py
+++ b/packages/meshbay-node/tests/test_wizard_apps_endpoint.py
@@ -13,12 +13,12 @@ from pathlib import Path
import pytest
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from fastapi.testclient import TestClient
-
-from conftest import one_root
from meshbay_node.indexer.group_index import GroupIndex
from meshbay_node.roster import Roster
from meshbay_node.ui.app import create_ui_app
+from conftest import one_root
+
pytestmark = pytest.mark.asyncio
diff --git a/packages/meshbay-node/tests/transfer_probe.py b/packages/meshbay-node/tests/transfer_probe.py
index 38e6cb6..7aa4902 100755
--- a/packages/meshbay-node/tests/transfer_probe.py
+++ b/packages/meshbay-node/tests/transfer_probe.py
@@ -69,9 +69,8 @@ if not (_QE / "e2e.py").exists():
f"run this from a machine that has one.")
sys.path.insert(0, str(_QE))
-import httpx # noqa: E402
-
-from e2e import Client, env # noqa: E402
+import httpx # noqa: E402
+from e2e import Client, env # noqa: E402
def _line(ok: bool, text: str) -> None:
@@ -272,7 +271,6 @@ async def operator_checks(client, ack, group, node_id) -> int:
and the operator's view because it reported the module defaults.
"""
import asyncio as _a
- import json as _json
import re as _re
failures = 0
@@ -322,7 +320,7 @@ async def operator_checks(client, ack, group, node_id) -> int:
try:
started = await _a.wait_for(
_wait_for_grant(client, tr_waiting), timeout=20)
- except _a.TimeoutError:
+ except TimeoutError:
started = False
_line(started, "raising the cap started the waiting transfer, with no "
"restart and no reconnection")
@@ -371,7 +369,7 @@ async def operator_checks(client, ack, group, node_id) -> int:
_cli("transfers", "per-member", "4", "4", "--group", group["id"])
try:
moved = await _a.wait_for(_wait_for_grant(client, tr_c), timeout=20)
- except _a.TimeoutError:
+ except TimeoutError:
moved = False
_line(moved, "raising the per-member cap started what was waiting on it")
failures += not moved
@@ -526,7 +524,7 @@ async def probe(args) -> int:
try:
seen.append(await alice.recv_type("transfer_state",
timeout=15))
- except asyncio.TimeoutError:
+ except TimeoutError:
break
promoted = [m for m in seen if m.get("state") == "granted"]
ok = bool(promoted)