ruff and docs

This commit is contained in:
Joel Krauska
2025-11-03 20:05:04 -08:00
parent fe59b42a53
commit 64261d3bc4
8 changed files with 113 additions and 43 deletions
+1
View File
@@ -89,6 +89,7 @@ def run_migrations_online() -> None:
loop = asyncio.get_running_loop()
# Event loop is already running, schedule and run the coroutine
import concurrent.futures
with concurrent.futures.ThreadPoolExecutor() as pool:
pool.submit(lambda: asyncio.run(run_async_migrations())).result()
except RuntimeError:
@@ -6,7 +6,7 @@ Create Date: 2025-10-26 20:59:04.347066
"""
from typing import Sequence, Union
from collections.abc import Sequence
import sqlalchemy as sa
@@ -14,9 +14,9 @@ from alembic import op
# revision identifiers, used by Alembic.
revision: str = '1717fa5c6545'
down_revision: Union[str, None] = 'add_time_us_cols'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
down_revision: str | None = 'add_time_us_cols'
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
+21 -9
View File
@@ -5,17 +5,18 @@ Revises: c88468b7ab0b
Create Date: 2025-11-03 14:10:00.000000
"""
from typing import Sequence, Union
from alembic import op
from collections.abc import Sequence
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision: str = 'add_time_us_cols'
down_revision: Union[str, None] = 'c88468b7ab0b'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
down_revision: str | None = 'c88468b7ab0b'
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
@@ -28,22 +29,33 @@ def upgrade() -> None:
if 'import_time_us' not in packet_columns:
with op.batch_alter_table('packet', schema=None) as batch_op:
batch_op.add_column(sa.Column('import_time_us', sa.BigInteger(), nullable=True))
op.create_index('idx_packet_import_time_us', 'packet', [sa.text('import_time_us DESC')], unique=False)
op.create_index('idx_packet_from_node_time_us', 'packet', ['from_node_id', sa.text('import_time_us DESC')], unique=False)
op.create_index(
'idx_packet_import_time_us', 'packet', [sa.text('import_time_us DESC')], unique=False
)
op.create_index(
'idx_packet_from_node_time_us',
'packet',
['from_node_id', sa.text('import_time_us DESC')],
unique=False,
)
# Add import_time_us to packet_seen table
packet_seen_columns = [col['name'] for col in inspector.get_columns('packet_seen')]
if 'import_time_us' not in packet_seen_columns:
with op.batch_alter_table('packet_seen', schema=None) as batch_op:
batch_op.add_column(sa.Column('import_time_us', sa.BigInteger(), nullable=True))
op.create_index('idx_packet_seen_import_time_us', 'packet_seen', ['import_time_us'], unique=False)
op.create_index(
'idx_packet_seen_import_time_us', 'packet_seen', ['import_time_us'], unique=False
)
# Add import_time_us to traceroute table
traceroute_columns = [col['name'] for col in inspector.get_columns('traceroute')]
if 'import_time_us' not in traceroute_columns:
with op.batch_alter_table('traceroute', schema=None) as batch_op:
batch_op.add_column(sa.Column('import_time_us', sa.BigInteger(), nullable=True))
op.create_index('idx_traceroute_import_time_us', 'traceroute', ['import_time_us'], unique=False)
op.create_index(
'idx_traceroute_import_time_us', 'traceroute', ['import_time_us'], unique=False
)
def downgrade() -> None:
@@ -6,7 +6,7 @@ Create Date: 2025-10-26 20:56:50.285200
"""
from typing import Sequence, Union
from collections.abc import Sequence
import sqlalchemy as sa
@@ -14,9 +14,9 @@ from alembic import op
# revision identifiers, used by Alembic.
revision: str = 'c88468b7ab0b'
down_revision: Union[str, None] = None
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
down_revision: str | None = None
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
@@ -28,7 +28,8 @@ def upgrade() -> None:
# Create node table if it doesn't exist
if 'node' not in existing_tables:
op.create_table('node',
op.create_table(
'node',
sa.Column('id', sa.String(), nullable=False),
sa.Column('node_id', sa.BigInteger(), nullable=True),
sa.Column('long_name', sa.String(), nullable=True),
@@ -41,13 +42,14 @@ def upgrade() -> None:
sa.Column('channel', sa.String(), nullable=True),
sa.Column('last_update', sa.DateTime(), nullable=True),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('node_id')
sa.UniqueConstraint('node_id'),
)
op.create_index('idx_node_node_id', 'node', ['node_id'], unique=False)
# Create packet table if it doesn't exist
if 'packet' not in existing_tables:
op.create_table('packet',
op.create_table(
'packet',
sa.Column('id', sa.BigInteger(), nullable=False),
sa.Column('portnum', sa.Integer(), nullable=True),
sa.Column('from_node_id', sa.BigInteger(), nullable=True),
@@ -56,18 +58,33 @@ def upgrade() -> None:
sa.Column('import_time', sa.DateTime(), nullable=True),
sa.Column('import_time_us', sa.BigInteger(), nullable=True),
sa.Column('channel', sa.String(), nullable=True),
sa.PrimaryKeyConstraint('id')
sa.PrimaryKeyConstraint('id'),
)
op.create_index('idx_packet_from_node_id', 'packet', ['from_node_id'], unique=False)
op.create_index('idx_packet_to_node_id', 'packet', ['to_node_id'], unique=False)
op.create_index('idx_packet_import_time', 'packet', [sa.text('import_time DESC')], unique=False)
op.create_index('idx_packet_import_time_us', 'packet', [sa.text('import_time_us DESC')], unique=False)
op.create_index('idx_packet_from_node_time', 'packet', ['from_node_id', sa.text('import_time DESC')], unique=False)
op.create_index('idx_packet_from_node_time_us', 'packet', ['from_node_id', sa.text('import_time_us DESC')], unique=False)
op.create_index(
'idx_packet_import_time', 'packet', [sa.text('import_time DESC')], unique=False
)
op.create_index(
'idx_packet_import_time_us', 'packet', [sa.text('import_time_us DESC')], unique=False
)
op.create_index(
'idx_packet_from_node_time',
'packet',
['from_node_id', sa.text('import_time DESC')],
unique=False,
)
op.create_index(
'idx_packet_from_node_time_us',
'packet',
['from_node_id', sa.text('import_time_us DESC')],
unique=False,
)
# Create packet_seen table if it doesn't exist
if 'packet_seen' not in existing_tables:
op.create_table('packet_seen',
op.create_table(
'packet_seen',
sa.Column('packet_id', sa.BigInteger(), nullable=False),
sa.Column('node_id', sa.BigInteger(), nullable=False),
sa.Column('rx_time', sa.BigInteger(), nullable=False),
@@ -79,16 +96,22 @@ def upgrade() -> None:
sa.Column('topic', sa.String(), nullable=True),
sa.Column('import_time', sa.DateTime(), nullable=True),
sa.Column('import_time_us', sa.BigInteger(), nullable=True),
sa.ForeignKeyConstraint(['packet_id'], ['packet.id'], ),
sa.PrimaryKeyConstraint('packet_id', 'node_id', 'rx_time')
sa.ForeignKeyConstraint(
['packet_id'],
['packet.id'],
),
sa.PrimaryKeyConstraint('packet_id', 'node_id', 'rx_time'),
)
op.create_index('idx_packet_seen_node_id', 'packet_seen', ['node_id'], unique=False)
op.create_index('idx_packet_seen_packet_id', 'packet_seen', ['packet_id'], unique=False)
op.create_index('idx_packet_seen_import_time_us', 'packet_seen', ['import_time_us'], unique=False)
op.create_index(
'idx_packet_seen_import_time_us', 'packet_seen', ['import_time_us'], unique=False
)
# Create traceroute table if it doesn't exist
if 'traceroute' not in existing_tables:
op.create_table('traceroute',
op.create_table(
'traceroute',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('packet_id', sa.BigInteger(), nullable=True),
sa.Column('gateway_node_id', sa.BigInteger(), nullable=True),
@@ -96,11 +119,16 @@ def upgrade() -> None:
sa.Column('route', sa.LargeBinary(), nullable=True),
sa.Column('import_time', sa.DateTime(), nullable=True),
sa.Column('import_time_us', sa.BigInteger(), nullable=True),
sa.ForeignKeyConstraint(['packet_id'], ['packet.id'], ),
sa.PrimaryKeyConstraint('id')
sa.ForeignKeyConstraint(
['packet_id'],
['packet.id'],
),
sa.PrimaryKeyConstraint('id'),
)
op.create_index('idx_traceroute_import_time', 'traceroute', ['import_time'], unique=False)
op.create_index('idx_traceroute_import_time_us', 'traceroute', ['import_time_us'], unique=False)
op.create_index(
'idx_traceroute_import_time_us', 'traceroute', ['import_time_us'], unique=False
)
# ### end Alembic commands ###
+4 -2
View File
@@ -298,9 +298,11 @@ Health check endpoint for monitoring, load balancers, and orchestration systems.
{
"status": "healthy",
"timestamp": "2025-11-03T14:30:00.123456Z",
"version": "2.0.8",
"version": "3.0.0",
"git_revision": "6416978",
"database": "connected"
"database": "connected",
"database_size": "853.03 MB",
"database_size_bytes": 894468096
}
```
+1
View File
@@ -1,4 +1,5 @@
"""Version information for MeshView."""
import subprocess
from pathlib import Path
-7
View File
@@ -1,12 +1,10 @@
import asyncio
import datetime
import json
import logging
import os
import pathlib
import re
import ssl
import subprocess
import traceback
from collections import Counter, defaultdict
from dataclasses import dataclass
@@ -19,16 +17,11 @@ from google.protobuf.message import Message
from jinja2 import Environment, PackageLoader, Undefined, select_autoescape
from markupsafe import Markup
from pandas import DataFrame
from sqlalchemy import text
from meshtastic.protobuf.portnums_pb2 import PortNum
from meshview import config, database, decode_payload, migrations, models, store
from meshview.__version__ import (
__version__,
__version_string__,
_git_revision,
_git_revision_short,
get_version_info,
)
from meshview.web_api import api
+34 -1
View File
@@ -1,9 +1,9 @@
"""API endpoints for MeshView."""
import datetime
import json
import logging
import os
import re
from aiohttp import web
from sqlalchemy import text
@@ -43,6 +43,7 @@ async def api_channels(request: web.Request):
except Exception as e:
return web.json_response({"channels": [], "error": str(e)})
@routes.get("/api/chat")
async def api_chat(request):
try:
@@ -130,6 +131,7 @@ async def api_chat(request):
{"error": "Failed to fetch chat data", "details": str(e)}, status=500
)
@routes.get("/api/nodes")
async def api_nodes(request):
try:
@@ -175,6 +177,7 @@ async def api_nodes(request):
logger.error(f"Error in /api/nodes: {e}")
return web.json_response({"error": "Failed to fetch nodes"}, status=500)
@routes.get("/api/packets")
async def api_packets(request):
try:
@@ -215,6 +218,7 @@ async def api_packets(request):
logger.error(f"Error in /api/packets: {e}")
return web.json_response({"error": "Failed to fetch packets"}, status=500)
@routes.get("/api/stats")
async def api_stats(request):
"""
@@ -267,6 +271,7 @@ async def api_stats(request):
return web.json_response(stats)
@routes.get("/api/edges")
async def api_edges(request):
since = datetime.datetime.now() - datetime.timedelta(hours=48)
@@ -309,6 +314,7 @@ async def api_edges(request):
return web.json_response({"edges": edges_list})
@routes.get("/api/config")
async def api_config(request):
try:
@@ -411,6 +417,7 @@ async def api_config(request):
except Exception as e:
return web.json_response({"error": str(e)}, status=500)
@routes.get("/api/lang")
async def api_lang(request):
# Language from ?lang=xx, fallback to config, then to "en"
@@ -437,6 +444,7 @@ async def api_lang(request):
# if no section requested → return full translation file
return web.json_response(translations)
@routes.get("/health")
async def health_check(request):
"""Health check endpoint for monitoring and load balancers."""
@@ -458,8 +466,33 @@ async def health_check(request):
health_status["status"] = "unhealthy"
return web.json_response(health_status, status=503)
# Get database file size
try:
db_url = CONFIG.get("database", {}).get("connection_string", "")
# Extract file path from SQLite connection string (e.g., "sqlite+aiosqlite:///packets.db")
if "sqlite" in db_url.lower():
db_path = db_url.split("///")[-1].split("?")[0]
if os.path.exists(db_path):
db_size_bytes = os.path.getsize(db_path)
# Convert to human-readable format
if db_size_bytes < 1024:
health_status["database_size"] = f"{db_size_bytes} B"
elif db_size_bytes < 1024 * 1024:
health_status["database_size"] = f"{db_size_bytes / 1024:.2f} KB"
elif db_size_bytes < 1024 * 1024 * 1024:
health_status["database_size"] = f"{db_size_bytes / (1024 * 1024):.2f} MB"
else:
health_status["database_size"] = (
f"{db_size_bytes / (1024 * 1024 * 1024):.2f} GB"
)
health_status["database_size_bytes"] = db_size_bytes
except Exception as e:
logger.warning(f"Failed to get database size: {e}")
# Don't fail health check if we can't get size
return web.json_response(health_status)
@routes.get("/version")
async def version_endpoint(request):
"""Return version information including semver and git revision."""