Merge pull request #266 from zindello/fix/python310-datetime-utc

This commit is contained in:
Lloyd
2026-05-27 07:22:32 +01:00
committed by GitHub
4 changed files with 62 additions and 16 deletions
+6 -13
View File
@@ -4,19 +4,12 @@ import json
import logging
import string
import threading
from datetime import datetime, timedelta
from datetime import datetime, timedelta, timezone
from typing import Any, Callable, Dict, List, Optional
import paho.mqtt.client as mqtt
from nacl.signing import SigningKey
# Try to import datetime.UTC (Python 3.11+) otherwise fallback to timezone.utc
try:
from datetime import UTC
except Exception:
from datetime import timezone
UTC = timezone.utc
from repeater import __version__, config
from repeater.presets import get_preset
@@ -268,7 +261,7 @@ class _BrokerConnection:
def _generate_jwt(self) -> str:
"""Generate MeshCore-style Ed25519 JWT token"""
now = datetime.now(UTC)
now = datetime.now(timezone.utc)
header = {"alg": "Ed25519", "typ": "JWT"}
@@ -448,7 +441,7 @@ class _BrokerConnection:
else:
logger.info(f"No credentials set for {self.broker['name']} (JWT auth disabled and no username/password provided)")
self._connect_time = datetime.now(UTC)
self._connect_time = datetime.now(timezone.utc)
except Exception as e:
logger.error(f"Failed to set JWT credentials for {self.broker['name']}: {e}")
@@ -550,7 +543,7 @@ class _BrokerConnection:
"""Check if connection should be reconnected due to JWT expiry (at 80% of lifetime)"""
if not self._connect_time:
return False
elapsed = (datetime.now(UTC) - self._connect_time).total_seconds()
elapsed = (datetime.now(timezone.utc) - self._connect_time).total_seconds()
expiry_seconds = self.jwt_expiry_minutes * 60
# Stagger refresh by 5% per broker to prevent simultaneous disconnects
# Broker 0: 80%, Broker 1: 85%, Broker 2: 90%, etc.
@@ -906,7 +899,7 @@ class MeshCoreToMqttPusher:
# Packet helpers
# ----------------------------------------------------------------
def _process_packet(self, pkt: dict) -> dict:
return {"timestamp": datetime.now(UTC).isoformat(), "origin_id": self.public_key, **pkt}
return {"timestamp": datetime.now(timezone.utc).isoformat(), "origin_id": self.public_key, **pkt}
def publish_packet(self, pkt: dict, subtopic="packets", retain=False):
return self.publish(subtopic, self._process_packet(pkt), retain)
@@ -941,7 +934,7 @@ class MeshCoreToMqttPusher:
status = {
"status": state,
"timestamp": datetime.now(UTC).isoformat(),
"timestamp": datetime.now(timezone.utc).isoformat(),
"origin": origin or self.node_name,
"origin_id": self.public_key,
"model": "PyMC-Repeater",
+1 -1
View File
@@ -285,7 +285,7 @@ class MeshCLI:
# Display current time
import datetime
dt = datetime.datetime.now(datetime.UTC)
dt = datetime.datetime.now(datetime.timezone.utc)
return f"{dt.hour:02d}:{dt.minute:02d} - {dt.day}/{dt.month}/{dt.year} UTC"
elif command == "clock sync":
# Clock sync happens automatically via sender_timestamp in protocol
+2 -2
View File
@@ -2,7 +2,7 @@ import json
import logging
import os
import time
from datetime import UTC, datetime
from datetime import datetime, timezone
from pathlib import Path
from typing import Callable, Optional
@@ -5250,7 +5250,7 @@ class APIEndpoints:
exported = _sanitize(exported)
meta = {
"exported_at": datetime.now(UTC).isoformat().replace("+00:00", "Z"),
"exported_at": datetime.now(timezone.utc).isoformat().replace("+00:00", "Z"),
"version": __version__,
"config_path": self._config_path,
"includes_secrets": full_backup,
+53
View File
@@ -0,0 +1,53 @@
"""Regression tests guarding against Python 3.10 compatibility breakage.
pyMC Repeater supports Python 3.10+ (LuckFox Pico Ultra ships with 3.10).
These tests scan the source tree statically so regressions are caught in CI
without needing a 3.10 interpreter in the test environment.
"""
import ast
import sys
from pathlib import Path
_REPEATER_ROOT = Path(__file__).parent.parent / "repeater"
def _py_files():
return [p for p in _REPEATER_ROOT.rglob("*.py") if ".pyc" not in str(p)]
def test_minimum_python_version():
"""Fail fast if the test environment itself is below the minimum supported version."""
assert sys.version_info >= (3, 10), (
f"Python 3.10+ required, running {sys.version_info.major}.{sys.version_info.minor}"
)
def test_no_datetime_utc():
"""`datetime.UTC` was added in 3.11 — `timezone.utc` must be used instead."""
violations = []
for path in _py_files():
try:
tree = ast.parse(path.read_text(encoding="utf-8"))
except SyntaxError:
continue
for node in ast.walk(tree):
# catch: from datetime import UTC
if isinstance(node, ast.ImportFrom) and node.module == "datetime":
if any(alias.name == "UTC" for alias in node.names):
rel = path.relative_to(_REPEATER_ROOT.parent)
violations.append(f" {rel}:{node.lineno}")
# catch: datetime.UTC
if (
isinstance(node, ast.Attribute)
and node.attr == "UTC"
and isinstance(node.value, ast.Name)
and node.value.id == "datetime"
):
rel = path.relative_to(_REPEATER_ROOT.parent)
violations.append(f" {rel}:{node.lineno}")
assert not violations, (
"datetime.UTC (Python 3.11+) found in the following files — "
"use timezone.utc instead:\n" + "\n".join(violations)
)