mirror of
https://github.com/kuhyx/diet-guard.git
synced 2026-11-10 18:07:20 +01:00
Grade against 3.14 too, not just run on it
`requires-python` and the CI pin already said 3.14, but ruff, mypy and pylint were still grading against 3.10 -- three tools judging an interpreter nothing runs any more, and ruff withholding the pyupgrade fixes that only become safe at the newer target. `pin_ci_python.py` rewrites exactly these three knobs. The fixes ruff then had to make are what the stale target was hiding: 18 files on `datetime.timezone.utc` instead of `datetime.UTC` (UP017), plus ruff's stable formatter applying PEP 758 -- `except (A, B):` becomes `except A, B:`. That second one makes the source 3.14-only at the syntax level, which is exactly what `requires-python = ">=3.14"` already promised; kuhy chose it deliberately over holding ruff a release back (2026-08-29). ruff, ruff-format, mypy --strict and pylint all clean at the new target. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SfnJmxxsZ534AKk16rC2Gr
This commit is contained in:
parent
3c314ecf9a
commit
42d15b6a91
@ -2,7 +2,7 @@ name: diet_guard_app
|
||||
description: "Companion phone app for diet_guard: log meals on the go."
|
||||
publish_to: 'none'
|
||||
|
||||
version: 1.0.74+1
|
||||
version: 1.0.75+1
|
||||
|
||||
environment:
|
||||
sdk: ^3.12.2
|
||||
|
||||
@ -19,7 +19,7 @@ keeps up to date on every edit.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timezone
|
||||
from datetime import UTC, datetime
|
||||
import json
|
||||
import logging
|
||||
|
||||
@ -49,7 +49,7 @@ def _now_local() -> datetime:
|
||||
imported: ``_state`` imports :func:`daily_budget` from this module, so
|
||||
importing back from ``_state`` here would be circular.
|
||||
"""
|
||||
return datetime.now(tz=timezone.utc).astimezone()
|
||||
return datetime.now(tz=UTC).astimezone()
|
||||
|
||||
|
||||
# A medically sane lower bound. Even an aggressive deficit must not compute
|
||||
@ -207,7 +207,7 @@ def read_raw_record() -> dict[str, object] | None:
|
||||
try:
|
||||
with BUDGET_FILE.open() as handle:
|
||||
record = json.load(handle)
|
||||
except (OSError, json.JSONDecodeError):
|
||||
except OSError, json.JSONDecodeError:
|
||||
return None
|
||||
if not isinstance(record, dict):
|
||||
return None
|
||||
|
||||
@ -28,7 +28,7 @@ propagating -- it relays them untouched. No coordinated release is needed.
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime, timezone
|
||||
from datetime import UTC, datetime
|
||||
import json
|
||||
import logging
|
||||
|
||||
@ -200,7 +200,7 @@ def read_raw_history() -> dict[str, object] | None:
|
||||
try:
|
||||
with BUDGET_HISTORY_FILE.open() as handle:
|
||||
document = json.load(handle)
|
||||
except (OSError, json.JSONDecodeError):
|
||||
except OSError, json.JSONDecodeError:
|
||||
return None
|
||||
if not isinstance(document, dict):
|
||||
return None
|
||||
@ -229,5 +229,5 @@ def load_entries() -> tuple[BudgetEntry, ...]:
|
||||
|
||||
def record_budget_change(kcal: int, *, when: datetime | None = None) -> None:
|
||||
"""Record a budget edit, effective from the day it was made."""
|
||||
moment = when if when is not None else datetime.now(tz=timezone.utc).astimezone()
|
||||
moment = when if when is not None else datetime.now(tz=UTC).astimezone()
|
||||
write_raw_history(history_to_json(upsert(load_entries(), kcal=kcal, when=moment)))
|
||||
|
||||
@ -56,7 +56,7 @@ def _read_bank() -> dict[str, BankRecord]:
|
||||
try:
|
||||
with FOOD_BANK_FILE.open() as handle:
|
||||
data = json.load(handle)
|
||||
except (OSError, json.JSONDecodeError):
|
||||
except OSError, json.JSONDecodeError:
|
||||
_quarantine_corrupt_bank()
|
||||
return {}
|
||||
if not isinstance(data, dict):
|
||||
|
||||
@ -19,7 +19,7 @@ next meal.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timezone
|
||||
from datetime import UTC, datetime
|
||||
import json
|
||||
import logging
|
||||
|
||||
@ -46,7 +46,7 @@ def read_manual_bank() -> dict[str, ManualRecord]:
|
||||
try:
|
||||
with MANUAL_BANK_FILE.open() as handle:
|
||||
bank = json.load(handle)
|
||||
except (OSError, json.JSONDecodeError):
|
||||
except OSError, json.JSONDecodeError:
|
||||
_logger.warning("Manual food bank %s is unreadable", MANUAL_BANK_FILE)
|
||||
return {}
|
||||
if not isinstance(bank, dict):
|
||||
@ -77,7 +77,7 @@ def add_manual_entry(name: str, record: ManualRecord) -> None:
|
||||
bank = read_manual_bank()
|
||||
stamped = dict(record)
|
||||
stamped["t"] = (
|
||||
datetime.now(tz=timezone.utc)
|
||||
datetime.now(tz=UTC)
|
||||
.astimezone()
|
||||
.isoformat(
|
||||
timespec="seconds",
|
||||
|
||||
@ -195,7 +195,7 @@ class _GateMealFlow(_PullFlows):
|
||||
consumed = today_total_kcal()
|
||||
try:
|
||||
budget = daily_budget()
|
||||
except (BudgetError, OSError):
|
||||
except BudgetError, OSError:
|
||||
return f"{consumed:g} kcal today"
|
||||
return (
|
||||
f"{consumed:g} / {budget:g} kcal · {round(budget - consumed, 1):g} left"
|
||||
|
||||
@ -97,7 +97,7 @@ class _GateNutrition(_GateCore):
|
||||
this_kcal = nutrition.kcal if nutrition is not None else 0.0
|
||||
try:
|
||||
budget = daily_budget()
|
||||
except (BudgetError, OSError):
|
||||
except BudgetError, OSError:
|
||||
tail = f" · this item {this_kcal:g} kcal" if this_kcal else ""
|
||||
self._vars.projection.set(f"Consumed {consumed:g} kcal today{tail}")
|
||||
return
|
||||
|
||||
@ -98,7 +98,7 @@ def load_session_cookie() -> str | None:
|
||||
return None
|
||||
try:
|
||||
cached = json.loads(KUCHNIA_SESSION_FILE.read_text(encoding="utf-8"))
|
||||
except (OSError, json.JSONDecodeError):
|
||||
except OSError, json.JSONDecodeError:
|
||||
_logger.warning("Catering session cache %s is unreadable", KUCHNIA_SESSION_FILE)
|
||||
return None
|
||||
value = cached.get(SESSION_COOKIE) if isinstance(cached, dict) else None
|
||||
|
||||
@ -51,7 +51,7 @@ def read_synced_credential() -> tuple[str, str, str] | None:
|
||||
return None
|
||||
try:
|
||||
raw = json.loads(KUCHNIA_SYNCED_CREDENTIAL_FILE.read_text(encoding="utf-8"))
|
||||
except (OSError, json.JSONDecodeError):
|
||||
except OSError, json.JSONDecodeError:
|
||||
_logger.warning(
|
||||
"Synced catering credential %s is unreadable",
|
||||
KUCHNIA_SYNCED_CREDENTIAL_FILE,
|
||||
|
||||
@ -29,7 +29,7 @@ KEEP IN SYNC WITH ``app/lib/services/meal_schedule_service.dart``.
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime, timezone
|
||||
from datetime import UTC, datetime
|
||||
import json
|
||||
import logging
|
||||
|
||||
@ -187,7 +187,7 @@ def read_raw_history() -> dict[str, object] | None:
|
||||
try:
|
||||
with MEAL_SCHEDULE_FILE.open() as handle:
|
||||
document = json.load(handle)
|
||||
except (OSError, json.JSONDecodeError):
|
||||
except OSError, json.JSONDecodeError:
|
||||
_logger.debug("unreadable meal schedule; using the default")
|
||||
return None
|
||||
if not isinstance(document, dict):
|
||||
@ -215,7 +215,7 @@ def current_schedule() -> MealSchedule:
|
||||
through; the slot arithmetic itself stays a pure function of its
|
||||
arguments.
|
||||
"""
|
||||
today = datetime.now(tz=timezone.utc).astimezone().date().isoformat()
|
||||
today = datetime.now(tz=UTC).astimezone().date().isoformat()
|
||||
return schedule_for_day(load_entries(), today)
|
||||
|
||||
|
||||
@ -227,6 +227,6 @@ def record_schedule_change(
|
||||
Seeds the default at the epoch first, so past days keep the four-meal
|
||||
schedule they were actually judged against.
|
||||
"""
|
||||
moment = when if when is not None else datetime.now(tz=timezone.utc).astimezone()
|
||||
moment = when if when is not None else datetime.now(tz=UTC).astimezone()
|
||||
entries = seed_default(load_entries())
|
||||
write_raw_history(history_to_json(upsert(entries, schedule, moment)))
|
||||
|
||||
@ -95,7 +95,7 @@ def _record_ids(client: RemoteStore, device_id: str) -> set[str]:
|
||||
return set()
|
||||
try:
|
||||
parsed = json.loads(text)
|
||||
except (ValueError, TypeError):
|
||||
except ValueError, TypeError:
|
||||
return set()
|
||||
return set(parsed) if isinstance(parsed, dict) else set()
|
||||
|
||||
|
||||
@ -10,7 +10,7 @@ instead of silently losing the data it just wrote.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timezone
|
||||
from datetime import UTC, datetime
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
@ -36,7 +36,7 @@ DayLog = dict[str, list[dict[str, object]]]
|
||||
|
||||
def now_local() -> datetime:
|
||||
"""Return the current time as a timezone-aware local datetime."""
|
||||
return datetime.now(tz=timezone.utc).astimezone()
|
||||
return datetime.now(tz=UTC).astimezone()
|
||||
|
||||
|
||||
def _today() -> str:
|
||||
@ -76,7 +76,7 @@ def _read_raw_log() -> DayLog:
|
||||
try:
|
||||
with FOOD_LOG_FILE.open() as handle:
|
||||
data = json.load(handle)
|
||||
except (OSError, json.JSONDecodeError):
|
||||
except OSError, json.JSONDecodeError:
|
||||
_logger.warning("Cannot read food log %s", FOOD_LOG_FILE)
|
||||
return {}
|
||||
if not isinstance(data, dict):
|
||||
|
||||
@ -79,7 +79,7 @@ def _remote_revisions(client: RemoteStore) -> dict[str, str]:
|
||||
return {}
|
||||
try:
|
||||
return get_string_map(_REVS_DIR)
|
||||
except (GitHubSyncError, RemoteSyncError):
|
||||
except GitHubSyncError, RemoteSyncError:
|
||||
# A revision map that cannot be read is not worth failing a sync
|
||||
# over; without it every peer is simply fetched, as before.
|
||||
return {}
|
||||
@ -122,7 +122,7 @@ def _pull_remote_logs(
|
||||
continue
|
||||
try:
|
||||
remote_logs.append(parse_remote_log(text))
|
||||
except (TypeError, KeyError, ValueError, json.JSONDecodeError):
|
||||
except TypeError, KeyError, ValueError, json.JSONDecodeError:
|
||||
# Deliberately not recorded as seen: a corrupt push must be
|
||||
# retried next tick, not remembered as merged.
|
||||
_logger.warning("Unparsable log pushed by device %r, skipping", device_id)
|
||||
|
||||
@ -96,7 +96,7 @@ def _sync_food_bank(client: GitHubSyncClient, device_ids: Sequence[str]) -> None
|
||||
continue
|
||||
try:
|
||||
merged = merge_logs(merged, parse_remote_food_bank(text))
|
||||
except (TypeError, KeyError, ValueError, json.JSONDecodeError):
|
||||
except TypeError, KeyError, ValueError, json.JSONDecodeError:
|
||||
_logger.warning(
|
||||
"Unparsable food bank pushed by device %r, skipping",
|
||||
device_id,
|
||||
@ -159,7 +159,7 @@ def _sync_manual_bank(client: GitHubSyncClient, device_ids: Sequence[str]) -> No
|
||||
continue
|
||||
try:
|
||||
merged = merge_logs(merged, parse_remote_manual_bank(text))
|
||||
except (TypeError, KeyError, ValueError, json.JSONDecodeError):
|
||||
except TypeError, KeyError, ValueError, json.JSONDecodeError:
|
||||
_logger.warning(
|
||||
"Unparsable curated food bank pushed by device %r, skipping",
|
||||
device_id,
|
||||
@ -201,7 +201,7 @@ def _sync_budget(client: GitHubSyncClient, device_ids: Sequence[str]) -> None:
|
||||
continue
|
||||
try:
|
||||
merged = merge_logs(merged, parse_remote_budget(text))
|
||||
except (TypeError, KeyError, ValueError, json.JSONDecodeError):
|
||||
except TypeError, KeyError, ValueError, json.JSONDecodeError:
|
||||
_logger.warning(
|
||||
"Unparsable budget pushed by device %r, skipping",
|
||||
device_id,
|
||||
|
||||
@ -15,7 +15,7 @@ menu with the PC switched off; see :mod:`diet_guard.sync_merge._kuchnia`.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timezone
|
||||
from datetime import UTC, datetime
|
||||
import json
|
||||
import logging
|
||||
from typing import TYPE_CHECKING
|
||||
@ -92,7 +92,7 @@ def _bootstrap_credential_log() -> Log:
|
||||
username, password = _kuchnia_config.read_credentials()
|
||||
except KuchniaError:
|
||||
return {}
|
||||
edited = datetime.fromtimestamp(path.stat().st_mtime, tz=timezone.utc)
|
||||
edited = datetime.fromtimestamp(path.stat().st_mtime, tz=UTC)
|
||||
return credential_to_log(
|
||||
username, password, edited.astimezone().isoformat(timespec="seconds")
|
||||
)
|
||||
@ -121,7 +121,7 @@ def _sync_kuchnia_credential(
|
||||
continue
|
||||
try:
|
||||
merged = merge_logs(merged, parse_remote_credential(text))
|
||||
except (TypeError, KeyError, ValueError, json.JSONDecodeError):
|
||||
except TypeError, KeyError, ValueError, json.JSONDecodeError:
|
||||
_logger.warning(
|
||||
"Unparsable catering credential pushed by device %r, skipping",
|
||||
device_id,
|
||||
|
||||
@ -121,7 +121,7 @@ def _merge_peer_logs(
|
||||
continue
|
||||
try:
|
||||
merged = merge_logs(merged, parse_remote_log(text))
|
||||
except (TypeError, KeyError, ValueError, json.JSONDecodeError):
|
||||
except TypeError, KeyError, ValueError, json.JSONDecodeError:
|
||||
# A corrupt push must be retried next tick, not remembered as
|
||||
# merged -- so deliberately not recorded in ``seen``.
|
||||
_logger.warning("Unparsable log pushed by device %r, skipping", device_id)
|
||||
|
||||
@ -37,7 +37,7 @@ def _resolved(path: pathlib.Path) -> pathlib.Path:
|
||||
"""Resolve ``path`` as far as the filesystem allows, never raising."""
|
||||
try:
|
||||
return path.expanduser().resolve()
|
||||
except (OSError, RuntimeError, ValueError):
|
||||
except OSError, RuntimeError, ValueError:
|
||||
return path
|
||||
|
||||
|
||||
|
||||
@ -11,7 +11,7 @@ stamp -- those foods were never eaten, so they are not derivable from any log.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timezone
|
||||
from datetime import UTC, datetime
|
||||
import json
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
@ -113,7 +113,7 @@ def log_to_manual_bank(log: Log) -> dict[str, dict[str, object]]:
|
||||
continue
|
||||
stored = dict(body)
|
||||
if hlc is not None:
|
||||
winning = datetime.fromtimestamp(hlc.wall_time_ms / 1000, tz=timezone.utc)
|
||||
winning = datetime.fromtimestamp(hlc.wall_time_ms / 1000, tz=UTC)
|
||||
stored["t"] = winning.astimezone().isoformat(timespec="seconds")
|
||||
bank[name] = stored
|
||||
return bank
|
||||
|
||||
@ -14,7 +14,7 @@ brand-new sync payload, so every device pushing it already speaks this format.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timezone
|
||||
from datetime import UTC, datetime
|
||||
import json
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
@ -139,7 +139,7 @@ def log_to_history(log: Log) -> tuple[BudgetEntry, ...]:
|
||||
continue
|
||||
if isinstance(value, bool) or not isinstance(value, int):
|
||||
continue
|
||||
edited = datetime.fromtimestamp(hlc.wall_time_ms / 1000, tz=timezone.utc)
|
||||
edited = datetime.fromtimestamp(hlc.wall_time_ms / 1000, tz=UTC)
|
||||
entries.append(
|
||||
BudgetEntry(
|
||||
effective_from=name[len(_HISTORY_FIELD_PREFIX) :],
|
||||
@ -163,7 +163,7 @@ def log_to_budget(log: Log) -> dict[str, object] | None:
|
||||
value, hlc = record.fields.get("value", ({}, None))
|
||||
result: dict[str, object] = dict(value) if isinstance(value, dict) else {}
|
||||
if hlc is not None:
|
||||
winning_time = datetime.fromtimestamp(hlc.wall_time_ms / 1000, tz=timezone.utc)
|
||||
winning_time = datetime.fromtimestamp(hlc.wall_time_ms / 1000, tz=UTC)
|
||||
result["t"] = winning_time.astimezone().isoformat(timespec="seconds")
|
||||
weight, _ = record.fields.get(_WEIGHT_FIELD, (None, None))
|
||||
if isinstance(weight, (int, float)) and not isinstance(weight, bool):
|
||||
|
||||
@ -10,9 +10,9 @@ would silently change merge outcomes rather than raising.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timezone
|
||||
from datetime import UTC, datetime
|
||||
|
||||
_EPOCH = datetime(1970, 1, 1, tzinfo=timezone.utc)
|
||||
_EPOCH = datetime(1970, 1, 1, tzinfo=UTC)
|
||||
|
||||
|
||||
def _wall_time_ms(stamp: str) -> int:
|
||||
|
||||
@ -28,7 +28,7 @@ KEEP IN SYNC WITH ``app/lib/services/sync_merge_kuchnia.dart``.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timezone
|
||||
from datetime import UTC, datetime
|
||||
import json
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
@ -125,7 +125,7 @@ def log_to_credential(log: Log) -> tuple[str, str, str] | None:
|
||||
return None
|
||||
# `password_hlc` cannot be None here: a field's value and its clock arrive
|
||||
# as one tuple, so reaching this line at all means the field was present.
|
||||
stamp = datetime.fromtimestamp(password_hlc.wall_time_ms / 1000, tz=timezone.utc)
|
||||
stamp = datetime.fromtimestamp(password_hlc.wall_time_ms / 1000, tz=UTC)
|
||||
return username, password, stamp.astimezone().isoformat(timespec="seconds")
|
||||
|
||||
|
||||
|
||||
@ -18,7 +18,7 @@ malformed one is skipped exactly as a non-int ``hist:`` value is.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timezone
|
||||
from datetime import UTC, datetime
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from crdt_sync import Hlc
|
||||
@ -97,7 +97,7 @@ def log_to_schedule_history(log: Log) -> tuple[ScheduleEntry, ...]:
|
||||
isinstance(first, int) and isinstance(last, int) and isinstance(count, int)
|
||||
) or any(isinstance(part, bool) for part in (first, last, count)):
|
||||
continue
|
||||
edited = datetime.fromtimestamp(hlc.wall_time_ms / 1000, tz=timezone.utc)
|
||||
edited = datetime.fromtimestamp(hlc.wall_time_ms / 1000, tz=UTC)
|
||||
entries.append(
|
||||
ScheduleEntry(
|
||||
effective_from=name[len(SCHEDULE_FIELD_PREFIX) :],
|
||||
|
||||
@ -6,7 +6,7 @@ fixture, so every read/write here is isolated from real user data.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timezone
|
||||
from datetime import UTC, datetime
|
||||
import json
|
||||
|
||||
from diet_guard import _budget, _budget_derived, _budget_history
|
||||
@ -64,7 +64,7 @@ class TestFileIO:
|
||||
def test_record_budget_change_defaults_to_now(self) -> None:
|
||||
record_budget_change(1900)
|
||||
entries = load_entries()
|
||||
today = datetime.now(tz=timezone.utc).astimezone().date().isoformat()
|
||||
today = datetime.now(tz=UTC).astimezone().date().isoformat()
|
||||
assert entries[-1].effective_from == today
|
||||
assert entries[-1].kcal == 1900
|
||||
|
||||
@ -121,7 +121,7 @@ class TestWriteBudgetIntegration:
|
||||
assert schedule.for_day("2026-06-01") == 2200
|
||||
assert (
|
||||
schedule.for_day(
|
||||
datetime.now(tz=timezone.utc).astimezone().date().isoformat(),
|
||||
datetime.now(tz=UTC).astimezone().date().isoformat(),
|
||||
)
|
||||
== 2000
|
||||
)
|
||||
@ -140,7 +140,7 @@ class TestWriteBudgetIntegration:
|
||||
_budget.write_budget(2000)
|
||||
entries = load_entries()
|
||||
assert [e.effective_from for e in entries] == [
|
||||
datetime.now(tz=timezone.utc).astimezone().date().isoformat(),
|
||||
datetime.now(tz=UTC).astimezone().date().isoformat(),
|
||||
]
|
||||
|
||||
def test_the_history_file_is_plain_readable_json(self) -> None:
|
||||
|
||||
@ -4,7 +4,7 @@ alongside its source module (see _cli_gate.py's module docstring).
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timezone
|
||||
from datetime import UTC, datetime
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from diet_guard import _cli_gate
|
||||
@ -214,7 +214,7 @@ class TestGateReadsFreshState:
|
||||
|
||||
def test_written_slot_clears_the_gate_immediately(self) -> None:
|
||||
"""At 09:00 only the 08:00 slot is due; logging it flips the decision."""
|
||||
now = datetime(2026, 1, 1, 9, 0, tzinfo=timezone.utc)
|
||||
now = datetime(2026, 1, 1, 9, 0, tzinfo=UTC)
|
||||
assert gate_is_due(now) is True
|
||||
log_meal("oatmeal", self._nutrition(), slot=8)
|
||||
assert gate_is_due(now) is False
|
||||
|
||||
@ -6,7 +6,7 @@ here the logged set is mocked and ``now`` is injected to drive each decision.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timezone
|
||||
from datetime import UTC, datetime
|
||||
from unittest.mock import patch
|
||||
|
||||
from diet_guard._gate import due_slots, gate_is_due, gate_message
|
||||
@ -14,7 +14,7 @@ from diet_guard._gate import due_slots, gate_is_due, gate_message
|
||||
|
||||
def _at(hour: int) -> datetime:
|
||||
"""Return a fixed local datetime at ``hour``."""
|
||||
return datetime(2026, 1, 1, hour, 0, tzinfo=timezone.utc)
|
||||
return datetime(2026, 1, 1, hour, 0, tzinfo=UTC)
|
||||
|
||||
|
||||
def _logged(slots: set[int]) -> object:
|
||||
|
||||
@ -7,7 +7,7 @@ here.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timezone
|
||||
from datetime import UTC, datetime
|
||||
from typing import TYPE_CHECKING
|
||||
from unittest.mock import patch
|
||||
|
||||
@ -190,7 +190,7 @@ class TestPersistence:
|
||||
def test_record_defaults_to_now(self) -> None:
|
||||
"""Omitting ``when`` stamps the edit with the current time."""
|
||||
store.record_schedule_change(MealSchedule(7, 19, 3))
|
||||
today = datetime.now(tz=timezone.utc).astimezone().date().isoformat()
|
||||
today = datetime.now(tz=UTC).astimezone().date().isoformat()
|
||||
assert store.schedule_for_day(store.load_entries(), today) == MealSchedule(
|
||||
7, 19, 3
|
||||
)
|
||||
|
||||
@ -8,7 +8,7 @@ still pin the behaviour these functions had when those hours were hardcoded.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timezone
|
||||
from datetime import UTC, datetime
|
||||
|
||||
from diet_guard._meal_schedule import DEFAULT_SCHEDULE, MealSchedule
|
||||
from diet_guard._slots import (
|
||||
@ -24,7 +24,7 @@ from diet_guard._slots import (
|
||||
|
||||
def _at(hour: int) -> datetime:
|
||||
"""Return a fixed local datetime at ``hour`` (date is irrelevant here)."""
|
||||
return datetime(2026, 1, 1, hour, 0, tzinfo=timezone.utc)
|
||||
return datetime(2026, 1, 1, hour, 0, tzinfo=UTC)
|
||||
|
||||
|
||||
class TestDaySlots:
|
||||
|
||||
@ -23,7 +23,7 @@ mcp = ["mcp==2.1.1"]
|
||||
include = ["diet_guard*"]
|
||||
|
||||
[tool.ruff]
|
||||
target-version = "py310"
|
||||
target-version = "py314"
|
||||
include = ["*.py", "**/*.py"]
|
||||
exclude = [".git", ".venv", "__pycache__", "build", "dist", ".eggs"]
|
||||
|
||||
@ -66,7 +66,7 @@ line-ending = "auto"
|
||||
docstring-code-format = true
|
||||
|
||||
[tool.mypy]
|
||||
python_version = "3.10"
|
||||
python_version = "3.14"
|
||||
strict = true
|
||||
warn_return_any = true
|
||||
warn_unused_configs = true
|
||||
@ -100,7 +100,7 @@ init-hook = "import sys; sys.path.insert(0, '.')"
|
||||
analyse-fallback-blocks = true
|
||||
persistent = true
|
||||
jobs = 0
|
||||
py-version = "3.10"
|
||||
py-version = "3.14"
|
||||
# "tests" is a basename match: test suites intentionally use patterns
|
||||
# (protected-access, magic-value comparisons) that don't apply to source
|
||||
# code, matching testsAndMisc's pylint scope (tests are linted by ruff, not
|
||||
|
||||
@ -30,7 +30,7 @@ Usage:
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from datetime import datetime, timezone
|
||||
from datetime import UTC, datetime
|
||||
import json
|
||||
from pathlib import Path
|
||||
import sys
|
||||
@ -192,7 +192,7 @@ def main() -> int:
|
||||
out.write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"captured_at": datetime.now(tz=timezone.utc).isoformat(),
|
||||
"captured_at": datetime.now(tz=UTC).isoformat(),
|
||||
"login_base": base,
|
||||
"xsrf_cookie_present": xsrf is not None,
|
||||
"steps": captured,
|
||||
|
||||
@ -64,7 +64,7 @@ def load_session_cookie() -> str | None:
|
||||
return None
|
||||
try:
|
||||
cached = json.loads(SESSION_FILE.read_text(encoding="utf-8"))
|
||||
except (OSError, json.JSONDecodeError):
|
||||
except OSError, json.JSONDecodeError:
|
||||
return None
|
||||
value = cached.get(SESSION_COOKIE) if isinstance(cached, dict) else None
|
||||
return value if isinstance(value, str) else None
|
||||
|
||||
@ -26,7 +26,7 @@ Usage:
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from datetime import datetime, timezone
|
||||
from datetime import UTC, datetime
|
||||
import json
|
||||
from pathlib import Path
|
||||
import sys
|
||||
@ -69,7 +69,7 @@ def pick_deliveries(capture: dict[str, Any]) -> list[dict[str, Any]]:
|
||||
"""
|
||||
order = capture["steps"]["order"]["body"]
|
||||
deliveries = sorted(order["deliveries"], key=lambda item: str(item["date"]))
|
||||
today = datetime.now(tz=timezone.utc).astimezone().date().isoformat()
|
||||
today = datetime.now(tz=UTC).astimezone().date().isoformat()
|
||||
past = [item for item in deliveries if str(item["date"]) < today]
|
||||
current = [item for item in deliveries if str(item["date"]) == today]
|
||||
future = [item for item in deliveries if str(item["date"]) > today]
|
||||
|
||||
Loading…
Reference in New Issue
Block a user