mirror of
https://github.com/agessaman/meshcore-bot.git
synced 2026-07-30 07:19:48 +00:00
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:
@@ -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
@@ -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,
|
||||
|
||||
@@ -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", [])}
|
||||
|
||||
@@ -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,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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user