Merge pull request #3 from ipnet-mesh/claude/fix-linting-errors-01GKk8AffQQFixCbjZ4egqn2

Fix flake8 and mypy linting errors
This commit is contained in:
JingleManSweep
2025-12-03 14:28:33 +00:00
committed by GitHub
35 changed files with 71 additions and 80 deletions

View File

@@ -1,6 +1,6 @@
[flake8]
max-line-length = 88
extend-ignore = E203, E501, W503
extend-ignore = B008, E203, E402, E501, W503
exclude =
.git,
__pycache__,

View File

@@ -21,6 +21,7 @@ repos:
rev: 7.0.0
hooks:
- id: flake8
args: ["--config=.flake8"]
additional_dependencies:
- flake8-bugbear
- flake8-comprehensions
@@ -29,6 +30,7 @@ repos:
rev: v1.9.0
hooks:
- id: mypy
exclude: ^alembic/
additional_dependencies:
- pydantic>=2.0.0
- pydantic-settings>=2.0.0

View File

@@ -106,9 +106,25 @@ plugins = ["pydantic.mypy"]
module = [
"paho.*",
"uvicorn.*",
"alembic.*",
]
ignore_missing_imports = true
[[tool.mypy.overrides]]
module = [
"tests.*",
"conftest",
]
disallow_untyped_defs = false
disallow_incomplete_defs = false
[[tool.mypy.overrides]]
module = [
"alembic.env",
"alembic.versions.*",
]
ignore_errors = true
[tool.pytest.ini_options]
minversion = "7.0"
asyncio_mode = "auto"

View File

@@ -71,7 +71,7 @@ def db() -> None:
def db_upgrade(revision: str, database_url: str | None) -> None:
"""Upgrade database to a later version."""
import os
from alembic import command
from alembic import command # type: ignore[attr-defined]
from alembic.config import Config
click.echo(f"Upgrading database to revision: {revision}")
@@ -101,7 +101,7 @@ def db_upgrade(revision: str, database_url: str | None) -> None:
def db_downgrade(revision: str, database_url: str | None) -> None:
"""Revert database to a previous version."""
import os
from alembic import command
from alembic import command # type: ignore[attr-defined]
from alembic.config import Config
click.echo(f"Downgrading database to revision: {revision}")
@@ -130,7 +130,7 @@ def db_downgrade(revision: str, database_url: str | None) -> None:
)
def db_revision(message: str, autogenerate: bool) -> None:
"""Create a new database migration."""
from alembic import command
from alembic import command # type: ignore[attr-defined]
from alembic.config import Config
click.echo(f"Creating new revision: {message}")
@@ -151,7 +151,7 @@ def db_revision(message: str, autogenerate: bool) -> None:
def db_current(database_url: str | None) -> None:
"""Show current database revision."""
import os
from alembic import command
from alembic import command # type: ignore[attr-defined]
from alembic.config import Config
alembic_cfg = Config("alembic.ini")
@@ -164,7 +164,7 @@ def db_current(database_url: str | None) -> None:
@db.command("history")
def db_history() -> None:
"""Show database migration history."""
from alembic import command
from alembic import command # type: ignore[attr-defined]
from alembic.config import Config
alembic_cfg = Config("alembic.ini")

View File

@@ -6,6 +6,7 @@ from typing import AsyncGenerator
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from sqlalchemy import text
from meshcore_hub import __version__
from meshcore_hub.common.database import DatabaseManager
@@ -115,7 +116,7 @@ def create_app(
try:
db = get_db_manager()
with db.session_scope() as session:
session.execute("SELECT 1")
session.execute(text("SELECT 1"))
return {"status": "ready", "database": "connected"}
except Exception as e:
return {"status": "not_ready", "database": str(e)}

View File

@@ -62,7 +62,7 @@ def _handle_message(
"""
text = payload.get("text")
if not text:
logger.warning(f"Message missing text content")
logger.warning("Message missing text content")
return
now = datetime.now(timezone.utc)

View File

@@ -3,7 +3,7 @@
from datetime import datetime
from typing import Optional
from sqlalchemy import BigInteger, DateTime, ForeignKey, Index, Integer, String
from sqlalchemy import BigInteger, DateTime, ForeignKey, Index, Integer
from sqlalchemy.dialects.sqlite import JSON
from sqlalchemy.orm import Mapped, mapped_column

View File

@@ -124,7 +124,7 @@ class MQTTClient:
self.config = config
self.topic_builder = TopicBuilder(config.prefix)
self._client = mqtt.Client(
callback_api_version=CallbackAPIVersion.VERSION2,
callback_api_version=CallbackAPIVersion.VERSION2, # type: ignore[call-arg]
client_id=config.client_id,
clean_session=config.clean_session,
)
@@ -211,7 +211,7 @@ class MQTTClient:
pattern_parts = pattern.split("/")
topic_parts = topic.split("/")
for i, (p, t) in enumerate(zip(pattern_parts, topic_parts)):
for _i, (p, t) in enumerate(zip(pattern_parts, topic_parts)):
if p == "#":
return True
if p != "+" and p != t:

View File

@@ -2,7 +2,6 @@
import click
from meshcore_hub.common.config import InterfaceMode
from meshcore_hub.common.logging import configure_logging

View File

@@ -2,7 +2,6 @@
import asyncio
import logging
import time
from abc import ABC, abstractmethod
from dataclasses import dataclass
from enum import Enum
@@ -261,9 +260,9 @@ class MeshCoreDevice(BaseMeshCoreDevice):
"""
super().__init__(config)
self._running = False
self._mc = None
self._loop = None
self._subscriptions = []
self._mc: Any = None
self._loop: Any = None
self._subscriptions: list[Any] = []
def connect(self) -> bool:
"""Connect to the MeshCore device."""
@@ -309,7 +308,7 @@ class MeshCoreDevice(BaseMeshCoreDevice):
if self_info:
self._public_key = self_info.get("public_key")
if self._public_key:
logger.info(f"Retrieved device public key from self_info")
logger.info("Retrieved device public key from self_info")
else:
logger.warning(
"Device self_info missing public_key field. "
@@ -352,7 +351,7 @@ class MeshCoreDevice(BaseMeshCoreDevice):
for mc_event_type, our_event_type in event_map.items():
async def callback(event, et=our_event_type):
async def callback(event: Any, et: EventType = our_event_type) -> None:
# Convert event to dict and dispatch
# Use event.payload for the full data (text, etc.)
# event.attributes only contains filtering fields
@@ -399,7 +398,7 @@ class MeshCoreDevice(BaseMeshCoreDevice):
try:
async def _send():
async def _send() -> None:
await self._mc.commands.send_msg(destination, text)
self._loop.run_until_complete(_send())
@@ -422,7 +421,7 @@ class MeshCoreDevice(BaseMeshCoreDevice):
try:
async def _send():
async def _send() -> None:
await self._mc.commands.send_chan_msg(channel_idx, text)
self._loop.run_until_complete(_send())
@@ -440,7 +439,7 @@ class MeshCoreDevice(BaseMeshCoreDevice):
try:
async def _send():
async def _send() -> None:
await self._mc.commands.send_advert(flood=flood)
self._loop.run_until_complete(_send())
@@ -458,7 +457,7 @@ class MeshCoreDevice(BaseMeshCoreDevice):
try:
async def _request():
async def _request() -> None:
await self._mc.commands.send_statusreq(target)
self._loop.run_until_complete(_request())
@@ -476,7 +475,7 @@ class MeshCoreDevice(BaseMeshCoreDevice):
try:
async def _request():
async def _request() -> None:
await self._mc.commands.send_telemetry_req(target)
self._loop.run_until_complete(_request())
@@ -494,7 +493,7 @@ class MeshCoreDevice(BaseMeshCoreDevice):
try:
async def _set_time():
async def _set_time() -> None:
await self._mc.commands.set_time(timestamp)
self._loop.run_until_complete(_set_time())
@@ -512,7 +511,7 @@ class MeshCoreDevice(BaseMeshCoreDevice):
try:
async def _start_fetching():
async def _start_fetching() -> None:
await self._mc.start_auto_message_fetching()
self._loop.run_until_complete(_start_fetching())
@@ -531,7 +530,7 @@ class MeshCoreDevice(BaseMeshCoreDevice):
self._setup_event_subscriptions()
# Run the async event loop
async def _run_loop():
async def _run_loop() -> None:
while self._running and self._connected:
await asyncio.sleep(0.1)

View File

@@ -158,7 +158,6 @@ class MockMeshCoreDevice(BaseMeshCoreDevice):
logger.warning("Simulated send failure")
return False
ts = timestamp or int(time.time())
logger.info(f"Mock: Sending message to {destination[:12]}...: {text[:20]}...")
# Simulate send confirmation after delay
@@ -199,7 +198,6 @@ class MockMeshCoreDevice(BaseMeshCoreDevice):
logger.warning("Simulated send failure")
return False
ts = timestamp or int(time.time())
logger.info(f"Mock: Sending message to channel {channel_idx}: {text[:20]}...")
return True

View File

@@ -15,7 +15,6 @@ from typing import Any, Optional
from meshcore_hub.common.mqtt import MQTTClient, MQTTConfig
from meshcore_hub.interface.device import (
BaseMeshCoreDevice,
DeviceConfig,
EventType,
create_device,
)

View File

@@ -6,7 +6,6 @@ In SENDER mode, the interface:
3. Executes received commands on the device
"""
import json
import logging
import signal
import threading
@@ -16,7 +15,6 @@ from typing import Any, Optional
from meshcore_hub.common.mqtt import MQTTClient, MQTTConfig
from meshcore_hub.interface.device import (
BaseMeshCoreDevice,
DeviceConfig,
create_device,
)

View File

@@ -7,7 +7,6 @@ from typing import AsyncGenerator
import httpx
from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
@@ -132,7 +131,8 @@ def create_app(
def get_templates(request: Request) -> Jinja2Templates:
"""Get templates from app state."""
return request.app.state.templates
templates: Jinja2Templates = request.app.state.templates
return templates
def get_network_context(request: Request) -> dict:

View File

@@ -13,7 +13,7 @@ logger = logging.getLogger(__name__)
router = APIRouter()
def load_members(members_file: str | None) -> list[dict]:
def load_members(members_file: str | None) -> list[dict[str, str]]:
"""Load members from JSON file.
Args:
@@ -32,9 +32,11 @@ def load_members(members_file: str | None) -> list[dict]:
data = json.load(f)
# Handle both list and dict with "members" key
if isinstance(data, list):
return data
return list(data)
elif isinstance(data, dict) and "members" in data:
return data["members"]
members = data["members"]
if isinstance(members, list):
return list(members)
else:
logger.warning(f"Members file not found: {members_file}")
except Exception as e:

View File

@@ -29,7 +29,7 @@ async def messages_list(
offset = (page - 1) * limit
# Build query params
params = {"limit": limit, "offset": offset}
params: dict[str, int | str] = {"limit": limit, "offset": offset}
if message_type:
params["message_type"] = message_type
if channel_idx is not None:

View File

@@ -28,7 +28,7 @@ async def nodes_list(
offset = (page - 1) * limit
# Build query params
params = {"limit": limit, "offset": offset}
params: dict[str, int | str] = {"limit": limit, "offset": offset}
if search:
params["search"] = search
if adv_type:

View File

@@ -1,7 +1,5 @@
"""Tests for advertisement API routes."""
import pytest
class TestListAdvertisements:
"""Tests for GET /advertisements endpoint."""

View File

@@ -1,7 +1,5 @@
"""Tests for API authentication."""
import pytest
class TestAuthenticationFlow:
"""Tests for authentication behavior."""

View File

@@ -1,7 +1,5 @@
"""Tests for command API routes."""
import pytest
class TestSendMessage:
"""Tests for POST /commands/send-message endpoint."""

View File

@@ -1,7 +1,5 @@
"""Tests for dashboard API routes."""
import pytest
class TestDashboardStats:
"""Tests for GET /dashboard/stats endpoint."""

View File

@@ -1,7 +1,5 @@
"""Tests for message API routes."""
import pytest
class TestListMessages:
"""Tests for GET /messages endpoint."""

View File

@@ -1,7 +1,5 @@
"""Tests for node API routes."""
import pytest
class TestListNodes:
"""Tests for GET /nodes endpoint."""

View File

@@ -1,7 +1,5 @@
"""Tests for telemetry API routes."""
import pytest
class TestListTelemetry:
"""Tests for GET /telemetry endpoint."""

View File

@@ -1,7 +1,5 @@
"""Tests for trace path API routes."""
import pytest
class TestListTracePaths:
"""Tests for GET /trace-paths endpoint."""

View File

@@ -1,11 +1,8 @@
"""Fixtures for collector component tests."""
import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from meshcore_hub.common.database import DatabaseManager
from meshcore_hub.common.models import Base
@pytest.fixture

View File

@@ -1,6 +1,5 @@
"""Tests for advertisement handler."""
import pytest
from sqlalchemy import select
from meshcore_hub.common.models import Advertisement, Node

View File

@@ -1,6 +1,5 @@
"""Tests for message handlers."""
import pytest
from sqlalchemy import select
from meshcore_hub.common.models import Message, Node

View File

@@ -1,6 +1,5 @@
"""Tests for telemetry handler."""
import pytest
from sqlalchemy import select
from meshcore_hub.common.models import Node, Telemetry

View File

@@ -155,6 +155,7 @@ class TestTelemetryModel:
db_session.commit()
assert telemetry.id is not None
assert telemetry.parsed_data is not None
assert telemetry.parsed_data["temperature"] == 22.5
@@ -175,4 +176,5 @@ class TestEventLogModel:
assert event.id is not None
assert event.event_type == "BATTERY"
assert event.payload is not None
assert event.payload["battery_percentage"] == 75

View File

@@ -1,8 +1,10 @@
"""Fixtures for interface component tests."""
from collections.abc import Generator
import pytest
from meshcore_hub.interface.device import DeviceConfig, EventType
from meshcore_hub.interface.device import DeviceConfig
from meshcore_hub.interface.mock_device import MockDeviceConfig, MockMeshCoreDevice
@@ -27,7 +29,9 @@ def mock_device_config() -> MockDeviceConfig:
@pytest.fixture
def mock_device(device_config, mock_device_config) -> MockMeshCoreDevice:
def mock_device(
device_config: DeviceConfig, mock_device_config: MockDeviceConfig
) -> Generator[MockMeshCoreDevice, None, None]:
"""Create a mock device instance for testing."""
device = MockMeshCoreDevice(device_config, mock_device_config)
yield device

View File

@@ -1,7 +1,5 @@
"""Tests for device abstraction."""
import pytest
from meshcore_hub.interface.device import (
DeviceConfig,
EventType,

View File

@@ -1,8 +1,6 @@
"""Tests for mock device implementation."""
import pytest
import time
import threading
from meshcore_hub.interface.device import EventType
from meshcore_hub.interface.mock_device import (

View File

@@ -3,8 +3,7 @@
import pytest
from unittest.mock import MagicMock, patch
from meshcore_hub.interface.device import DeviceConfig, EventType
from meshcore_hub.interface.mock_device import MockDeviceConfig, MockMeshCoreDevice
from meshcore_hub.interface.device import EventType
from meshcore_hub.interface.receiver import Receiver, create_receiver
@@ -69,7 +68,7 @@ class TestCreateReceiver:
def test_creates_receiver_with_mock_device(self):
"""Test creating receiver with mock device."""
with patch("meshcore_hub.interface.receiver.MQTTClient") as MockMQTT:
with patch("meshcore_hub.interface.receiver.MQTTClient"):
receiver = create_receiver(mock=True)
assert receiver is not None
@@ -78,8 +77,8 @@ class TestCreateReceiver:
def test_creates_receiver_with_custom_mqtt_config(self):
"""Test creating receiver with custom MQTT configuration."""
with patch("meshcore_hub.interface.receiver.MQTTClient") as MockMQTT:
receiver = create_receiver(
with patch("meshcore_hub.interface.receiver.MQTTClient") as mock_mqtt:
create_receiver(
mock=True,
mqtt_host="mqtt.example.com",
mqtt_port=8883,
@@ -87,8 +86,8 @@ class TestCreateReceiver:
)
# Verify MQTT client was created with correct config
MockMQTT.assert_called_once()
config = MockMQTT.call_args[0][0]
mock_mqtt.assert_called_once()
config = mock_mqtt.call_args[0][0]
assert config.host == "mqtt.example.com"
assert config.port == 8883
assert config.prefix == "custom"

View File

@@ -3,8 +3,6 @@
import pytest
from unittest.mock import MagicMock, patch
from meshcore_hub.interface.device import DeviceConfig, EventType
from meshcore_hub.interface.mock_device import MockDeviceConfig, MockMeshCoreDevice
from meshcore_hub.interface.sender import Sender, create_sender
@@ -107,7 +105,7 @@ class TestCreateSender:
def test_creates_sender_with_mock_device(self):
"""Test creating sender with mock device."""
with patch("meshcore_hub.interface.sender.MQTTClient") as MockMQTT:
with patch("meshcore_hub.interface.sender.MQTTClient"):
sender = create_sender(mock=True)
assert sender is not None
@@ -116,16 +114,16 @@ class TestCreateSender:
def test_creates_sender_with_custom_mqtt_config(self):
"""Test creating sender with custom MQTT configuration."""
with patch("meshcore_hub.interface.sender.MQTTClient") as MockMQTT:
sender = create_sender(
with patch("meshcore_hub.interface.sender.MQTTClient") as mock_mqtt:
create_sender(
mock=True,
mqtt_host="mqtt.example.com",
mqtt_port=8883,
mqtt_prefix="custom",
)
MockMQTT.assert_called_once()
config = MockMQTT.call_args[0][0]
mock_mqtt.assert_called_once()
config = mock_mqtt.call_args[0][0]
assert config.host == "mqtt.example.com"
assert config.port == 8883
assert config.prefix == "custom"