mirror of
https://github.com/pyMC-dev/pyMC_Repeater.git
synced 2026-06-11 08:44:46 +02:00
Merge pull request #266 from zindello/fix/python310-datetime-utc
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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,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,
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
Reference in New Issue
Block a user