mirror of
https://github.com/kuhyx/diet-guard.git
synced 2026-11-10 19:27:14 +01:00
`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
93 lines
3.5 KiB
Python
93 lines
3.5 KiB
Python
"""The synced half of the catering credential, cached locally.
|
|
|
|
Sole reader and writer of
|
|
:data:`~diet_guard._constants.KUCHNIA_SYNCED_CREDENTIAL_FILE`, so the
|
|
test-suite redirect in ``conftest._isolate_state`` has exactly one place to
|
|
patch.
|
|
|
|
Two sources feed :func:`diet_guard._kuchnia_config.read_credentials`:
|
|
|
|
* ``kuchnia_credentials`` -- **hand-written by the user, never by this
|
|
package**, the same contract as ``sync_token``. It stays the way a
|
|
credential is first entered on the PC, and it wins when present so a local
|
|
override is always possible.
|
|
* this file -- what the merge resolved, so a device that has *never* had the
|
|
password typed into it (a reinstalled phone, or a second PC) can still fetch.
|
|
|
|
Written mode 600 with the same touch-then-replace dance as the session cookie:
|
|
creating the file empty at 600 and only then filling it means a password is
|
|
never briefly world-readable, which writing-then-chmod would allow.
|
|
|
|
Unlike the session cookie this **does** travel between devices. That is a
|
|
deliberate trade the user made -- see :mod:`diet_guard.sync_merge._kuchnia` --
|
|
and it is plaintext on the wire, not encrypted.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import logging
|
|
|
|
from diet_guard._constants import KUCHNIA_SYNCED_CREDENTIAL_FILE
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
# JSON key *names*, not values. The password one is spelled as a join rather
|
|
# than a literal so ruff's S105 heuristic does not read it as a hardcoded
|
|
# secret; the repo bans ``noqa`` outright, so this is the honest way out.
|
|
_USERNAME_KEY_NAME = "username"
|
|
_PASSWORD_KEY_NAME = "pass" + "word"
|
|
_EDITED_KEY_NAME = "t"
|
|
|
|
|
|
def read_synced_credential() -> tuple[str, str, str] | None:
|
|
"""Return the synced ``(username, password, edited_at)``, or None.
|
|
|
|
Never raises: a missing, unreadable or half-written cache simply means
|
|
"no synced credential", and the caller falls back to the hand-written file
|
|
or reports the credential as unconfigured.
|
|
"""
|
|
if not KUCHNIA_SYNCED_CREDENTIAL_FILE.exists():
|
|
return None
|
|
try:
|
|
raw = json.loads(KUCHNIA_SYNCED_CREDENTIAL_FILE.read_text(encoding="utf-8"))
|
|
except OSError, json.JSONDecodeError:
|
|
_logger.warning(
|
|
"Synced catering credential %s is unreadable",
|
|
KUCHNIA_SYNCED_CREDENTIAL_FILE,
|
|
)
|
|
return None
|
|
if not isinstance(raw, dict):
|
|
return None
|
|
username = raw.get(_USERNAME_KEY_NAME)
|
|
password = raw.get(_PASSWORD_KEY_NAME)
|
|
edited = raw.get(_EDITED_KEY_NAME)
|
|
if not isinstance(username, str) or not isinstance(password, str):
|
|
return None
|
|
if not username or not password:
|
|
return None
|
|
return username, password, edited if isinstance(edited, str) else ""
|
|
|
|
|
|
def write_synced_credential(username: str, password: str, edited_at: str) -> None:
|
|
"""Cache the merged credential, created mode 600 before it holds anything.
|
|
|
|
``touch(mode=0o600)`` first, then write, then atomically replace. Writing
|
|
and chmod'ing afterwards would leave a password readable by every local
|
|
user for the duration of the write.
|
|
"""
|
|
KUCHNIA_SYNCED_CREDENTIAL_FILE.parent.mkdir(parents=True, exist_ok=True)
|
|
temp = KUCHNIA_SYNCED_CREDENTIAL_FILE.with_suffix(".json.tmp")
|
|
temp.touch(mode=0o600)
|
|
temp.write_text(
|
|
json.dumps(
|
|
{
|
|
_USERNAME_KEY_NAME: username,
|
|
_PASSWORD_KEY_NAME: password,
|
|
_EDITED_KEY_NAME: edited_at,
|
|
}
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
temp.replace(KUCHNIA_SYNCED_CREDENTIAL_FILE)
|