sync: auto-sync from HOWARD-HOME at 2026-07-05 20:23:39

Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-05 20:23:39
This commit is contained in:
2026-07-05 20:24:08 -07:00
parent 976bfe7957
commit a8e76ba215
10 changed files with 292 additions and 108 deletions

View File

@@ -24,6 +24,7 @@ import subprocess
import sys
import time
import urllib.error
import urllib.parse
import urllib.request
from pathlib import Path
from typing import Any, Optional
@@ -93,18 +94,26 @@ def vault_get_field(entry: str, field: str) -> str:
# --- minimal JSON HTTP --------------------------------------------------------
def http_json(method: str, url: str, *, headers: Optional[dict] = None,
body: Optional[dict] = None, timeout: float = HTTP_TIMEOUT,
body: Optional[dict] = None, form: Optional[dict] = None,
timeout: float = HTTP_TIMEOUT,
basic: Optional[tuple[str, str]] = None) -> tuple[int, Any]:
"""Do an HTTP request and parse a JSON response. Returns (status, parsed).
4xx/5xx are returned (not raised) so callers can inspect the error body.
Transport failures raise BackupError.
`body` sends a JSON payload; `form` sends application/x-www-form-urlencoded
(list values expand via doseq, e.g. repeated fields the Seafile admin share
endpoint reads with getlist). Pass at most one. 4xx/5xx are returned (not
raised) so callers can inspect the error body; transport failures raise.
"""
hdrs = dict(headers or {})
data = None
if body is not None and form is not None:
raise BackupError("http_json: pass body OR form, not both")
if body is not None:
data = json.dumps(body).encode("utf-8")
hdrs.setdefault("Content-Type", "application/json")
elif form is not None:
data = urllib.parse.urlencode(form, doseq=True).encode("utf-8")
hdrs.setdefault("Content-Type", "application/x-www-form-urlencoded")
if basic is not None:
import base64
tok = base64.b64encode(f"{basic[0]}:{basic[1]}".encode()).decode("ascii")