fix(config): add type ignore for optionxform and handle None values in path inference

- Added a type ignore comment for the assignment of optionxform in config_schema.py to suppress type checker warnings.
- Updated path inference logic to return None when bot latitude or longitude is not set, ensuring safer handling of missing values.
- Enhanced the decoding of path nodes to check for None before appending repeater details, preventing potential errors.
This commit is contained in:
agessaman
2026-07-12 16:31:05 -07:00
parent 2324f23832
commit ffee28c0ff
6 changed files with 37 additions and 33 deletions
+1 -1
View File
@@ -278,7 +278,7 @@ def load_documented_keys_from_example(example_path: Path) -> dict[str, dict[str,
# Active keys via ConfigParser
try:
cfg = configparser.ConfigParser(allow_no_value=True)
cfg.optionxform = str
cfg.optionxform = str # type: ignore[assignment]
cfg.read(str(example_path), encoding="utf-8")
for section in cfg.sections():
try:
+29 -24
View File
@@ -316,6 +316,9 @@ def get_node_location(node_id: str, cfg: PathInferenceConfig, *, db_manager, log
def select_by_simple_proximity(repeaters_with_location, cfg: PathInferenceConfig, *, logger):
"""Select the closest repeater to the bot, with a strong recency bias."""
if cfg.bot_latitude is None or cfg.bot_longitude is None:
return None, 0.0
scored_repeaters = calculate_recency_weighted_scores(repeaters_with_location, cfg)
scored_repeaters = [(r, score) for r, score in scored_repeaters if score >= MIN_RECENCY_THRESHOLD]
@@ -1118,18 +1121,19 @@ def decode_path_nodes(
if selection.status == 'resolved':
selected_repeater = selection.repeater
decoded_path.append({
'node_id': node_id,
'name': selected_repeater['name'],
'public_key': selected_repeater['public_key'],
'device_type': selected_repeater['device_type'],
'role': selected_repeater.get('role', 'repeater'),
'found': True,
'geographic_guess': selection.confidence < 0.8,
'collision': True,
'matches': selection.matches,
**_loc(selected_repeater),
})
if selected_repeater is not None:
decoded_path.append({
'node_id': node_id,
'name': selected_repeater['name'],
'public_key': selected_repeater['public_key'],
'device_type': selected_repeater['device_type'],
'role': selected_repeater.get('role', 'repeater'),
'found': True,
'geographic_guess': selection.confidence < 0.8,
'collision': True,
'matches': selection.matches,
**_loc(selected_repeater),
})
elif selection.status == 'collision':
fallback = selection.recent_repeaters[0]
decoded_path.append({
@@ -1146,18 +1150,19 @@ def decode_path_nodes(
})
elif selection.status == 'single':
repeater = selection.repeater
decoded_path.append({
'node_id': node_id,
'name': repeater['name'],
'public_key': repeater['public_key'],
'device_type': repeater['device_type'],
'role': repeater.get('role', 'repeater'),
'found': True,
'geographic_guess': False,
'collision': False,
'matches': 1,
**_loc(repeater),
})
if repeater is not None:
decoded_path.append({
'node_id': node_id,
'name': repeater['name'],
'public_key': repeater['public_key'],
'device_type': repeater['device_type'],
'role': repeater.get('role', 'repeater'),
'found': True,
'geographic_guess': False,
'collision': False,
'matches': 1,
**_loc(repeater),
})
else:
decoded_path.append({
'node_id': node_id,
+4 -4
View File
@@ -95,15 +95,15 @@ def validate_field(field: dict, raw: Any) -> tuple[bool, Any, Optional[str]]:
if ftype in ("int", "float"):
try:
coerced = int(raw) if ftype == "int" else float(raw)
num = int(raw) if ftype == "int" else float(raw)
except (ValueError, TypeError):
return False, None, f"{label} must be a number"
lo, hi = field.get("min"), field.get("max")
if lo is not None and coerced < lo:
if lo is not None and num < lo:
return False, None, f"{label} must be ≥ {lo}"
if hi is not None and coerced > hi:
if hi is not None and num > hi:
return False, None, f"{label} must be ≤ {hi}"
return True, coerced, None
return True, num, None
if ftype == "enum":
allowed = {str(o.get("value")) for o in field.get("options", [])}
+2 -2
View File
@@ -53,7 +53,7 @@ class SettingsStore(ABC):
def write_sections(
self,
updates: dict[str, dict[str, str]],
deletes: Optional[dict[str, list[str]]] = None,
deletes: Optional[dict[str, list[str] | set[str]]] = None,
) -> dict:
"""Persist multiple sections at once, with optional key deletions.
@@ -100,7 +100,7 @@ class ConfigIniStore(SettingsStore):
def write_sections(
self,
updates: dict[str, dict[str, str]],
deletes: Optional[dict[str, list[str]]] = None,
deletes: Optional[dict[str, list[str] | set[str]]] = None,
) -> dict:
# Mirror writes into the in-memory config so this process stays current.
for section, values in (updates or {}).items():
-1
View File
@@ -1,7 +1,6 @@
#!/usr/bin/env python3
"""Unit tests for lazy NWS alert coverage handling (international / HTTP 400)."""
import asyncio
import configparser
from unittest.mock import AsyncMock, Mock
+1 -1
View File
@@ -3,9 +3,9 @@
import pytest
from modules.models import MeshMessage
from modules.commands.alternatives.wx_international import GlobalWxCommand
from modules.commands.wx_command import WxCommand
from modules.models import MeshMessage
@pytest.fixture