US-004: rezolva_rar_env (cerere>default cont>ancora globala) + MediuIndisponibil + cod RAR_MEDIU_INDISPONIBIL. US-005: camp rar_env pe POST /v1/prezentari + /valideaza (Literal), echo in SubmissionResult/ValidareResult/GET, build_key + INSERT env-aware. US-006: AccountSessions re-cheiat (account_id, rar_env); RarClient base_url per env; creds din slotul env; purge + recover_orphans scoped pe env (E1/1a, 1b/E6); claim_one propaga rar_env (1c/E8); keepalive pe ancora globala (M2). US-009: selector mediu la import (>=2 medii), eticheta la 1, banner la 0; commit seteaza rar_env pe submissions. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
157 lines
5.7 KiB
Python
157 lines
5.7 KiB
Python
"""Medii RAR per cont (PRD 5.20): disponibilitate + default efectiv.
|
|
|
|
Sursa UNICA de adevar pentru REQ-DISP / REQ-DEFAULT: vizibilitatea selector/toggle
|
|
in UI, validarea tintei in API si decizia worker-ului citesc TOATE de aici, ca sa
|
|
decida identic.
|
|
|
|
Un mediu ('test'|'prod') e *disponibil* pentru un cont daca e activat (bifa) SI are
|
|
credentiale (slot per-mediu non-gol). Din disponibilitate decurge tot UX-ul:
|
|
- 0 medii -> nicio tinta; trimiterea web e blocata, API cade pe ancora globala.
|
|
- 1 mediu -> tinta implicita (acel mediu), fara selector.
|
|
- 2 medii -> selector la import + toggle in statusbar + alegere in API.
|
|
|
|
Functii PURE (fara DB) peste un rand de cont (sqlite3.Row sau dict). Helperele cu
|
|
`conn` incarca randul si deleaga.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import sqlite3
|
|
from typing import Any
|
|
|
|
VALID_ENVS: tuple[str, str] = ("test", "prod")
|
|
|
|
|
|
def _field(account: Any, key: str, default: Any = None) -> Any:
|
|
"""Citire toleranta a unui camp de cont (dict sau sqlite3.Row, camp posibil absent)."""
|
|
if account is None:
|
|
return default
|
|
if isinstance(account, dict):
|
|
return account.get(key, default)
|
|
try:
|
|
return account[key] # sqlite3.Row
|
|
except (IndexError, KeyError):
|
|
return default
|
|
|
|
|
|
def _are_creds(account: Any, env: str) -> bool:
|
|
creds = _field(account, f"rar_creds_{env}_enc", None)
|
|
return bool(creds and str(creds).strip())
|
|
|
|
|
|
def _enabled(account: Any, env: str) -> bool:
|
|
return int(_field(account, f"rar_{env}_enabled", 0) or 0) == 1
|
|
|
|
|
|
def medii_disponibile(account: Any) -> list[str]:
|
|
"""Subset din ('test','prod') = activat AND creds prezente. Ordine stabila test<prod."""
|
|
return [env for env in VALID_ENVS if _enabled(account, env) and _are_creds(account, env)]
|
|
|
|
|
|
def rar_env_efectiv(account: Any) -> str | None:
|
|
"""Mediul tinta implicit al contului (REQ-DEFAULT).
|
|
|
|
Mereu unul din mediile disponibile: default-ul contului daca inca e disponibil,
|
|
altfel singurul disponibil; daca 0 disponibile -> None (nicio tinta).
|
|
"""
|
|
disp = medii_disponibile(account)
|
|
if not disp:
|
|
return None
|
|
default = _field(account, "rar_env_default", "prod")
|
|
if default in disp:
|
|
return default
|
|
return disp[0]
|
|
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
# Helpere cu conexiune #
|
|
# --------------------------------------------------------------------------- #
|
|
|
|
_ACCOUNT_ENV_COLS = (
|
|
"id, rar_test_enabled, rar_prod_enabled, "
|
|
"rar_creds_test_enc, rar_creds_prod_enc, rar_env_default"
|
|
)
|
|
|
|
|
|
def load_account_env(conn: sqlite3.Connection, account_id: int) -> sqlite3.Row | None:
|
|
"""Randul de cont cu exact coloanele de mediu (pentru medii_disponibile/rar_env_efectiv)."""
|
|
from .mapping import account_or_default
|
|
|
|
return conn.execute(
|
|
f"SELECT {_ACCOUNT_ENV_COLS} FROM accounts WHERE id=?",
|
|
(account_or_default(account_id),),
|
|
).fetchone()
|
|
|
|
|
|
def medii_disponibile_cont(conn: sqlite3.Connection, account_id: int) -> list[str]:
|
|
return medii_disponibile(load_account_env(conn, account_id))
|
|
|
|
|
|
def rar_env_efectiv_cont(conn: sqlite3.Connection, account_id: int) -> str | None:
|
|
return rar_env_efectiv(load_account_env(conn, account_id))
|
|
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
# Exceptie si rezolvator de mediu tinta (US-004, dependent de US-002) #
|
|
# --------------------------------------------------------------------------- #
|
|
|
|
class MediuIndisponibil(Exception):
|
|
"""Mediul RAR cerut e valid dar nu e disponibil pentru contul dat.
|
|
|
|
Atribute
|
|
--------
|
|
env: mediul cerut (ex. 'test')
|
|
disponibile: lista mediilor disponibile pentru cont in momentul erorii
|
|
"""
|
|
|
|
def __init__(self, env: str, disponibile: list[str]) -> None:
|
|
self.env = env
|
|
self.disponibile = disponibile
|
|
super().__init__(
|
|
f"mediu indisponibil: {env!r} (disponibile: {disponibile!r})"
|
|
)
|
|
|
|
|
|
def rezolva_rar_env(
|
|
conn: sqlite3.Connection,
|
|
account_id: int,
|
|
cerut: str | None = None,
|
|
) -> str:
|
|
"""Determina mediul RAR tinta pentru un submission la ingestie.
|
|
|
|
Precedenta stricta (de la cea mai mare la cea mai mica):
|
|
1. `cerut` explicit si disponibil -> intoarce `cerut`.
|
|
2. `cerut` explicit dar indisponibil -> ridica MediuIndisponibil.
|
|
3. `cerut` invalid (nu in VALID_ENVS) -> ridica ValueError (fara fallback silentios).
|
|
4. `cerut` None -> incearca rar_env_efectiv_cont (default-ul contului).
|
|
5. Daca contul nu are niciun mediu disponibil (rar_env_efectiv_cont == None)
|
|
-> cade pe ancora globala get_settings().rar_env, normalizata la VALID_ENVS.
|
|
Acest fallback e intentionat (PRD 5.20 §2 Non-Goals): AUTOPASS_RAR_ENV ramane
|
|
ancora de migrare si fallback pentru actiuni fara cont (keepalive, canal API cu
|
|
creds efemere pe conturi nou-create fara medii configurate).
|
|
|
|
Ridica
|
|
------
|
|
ValueError -- `cerut` nu e in VALID_ENVS
|
|
MediuIndisponibil -- `cerut` e valid dar nu e disponibil pentru cont
|
|
"""
|
|
if cerut is not None:
|
|
if cerut not in VALID_ENVS:
|
|
raise ValueError(f"mediu invalid: {cerut!r}")
|
|
disp = medii_disponibile_cont(conn, account_id)
|
|
if cerut not in disp:
|
|
raise MediuIndisponibil(cerut, disp)
|
|
return cerut
|
|
|
|
# cerut e None: incearca default-ul contului
|
|
efectiv = rar_env_efectiv_cont(conn, account_id)
|
|
if efectiv is not None:
|
|
return efectiv
|
|
|
|
# Ancora globala: 0 medii disponibile pe cont -> fallback la AUTOPASS_RAR_ENV.
|
|
from .config import get_settings
|
|
global_env = get_settings().rar_env
|
|
if global_env in VALID_ENVS:
|
|
return global_env
|
|
return "test" # rar_env invalid in config -> cel mai sigur default
|