Coverage for src / lexigram / contracts / auth / repositories.py: 100%
18 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-08-19 05:41 +0800
« prev ^ index » next coverage.py v7.13.5, created at 2026-08-19 05:41 +0800
1"""RepositoryProtocol protocols for auth-domain persistence.
3Zero-dependency contracts. Any package may depend on these without coupling
4to a concrete database driver or to each other.
5"""
7from __future__ import annotations
9from typing import TYPE_CHECKING, Any, Protocol, runtime_checkable
11if TYPE_CHECKING:
12 from datetime import datetime
14__all__ = [
15 "APIKeyRepositoryProtocol",
16 "SessionRepositoryProtocol",
17]
20@runtime_checkable
21class APIKeyRepositoryProtocol(Protocol):
22 """Persistence contract for API key records.
24 Implementations are responsible for all SQL/NoSQL I/O. Business-logic
25 callers (e.g. ``APIKeyManager``) depend only on this protocol, keeping
26 them decoupled from the underlying data store.
27 """
29 async def insert(self, payload: dict[str, Any]) -> str:
30 """Persist a new API key record and return the generated key_id.
32 Args:
33 payload: Field/value mapping for the new row (name, key_hash,
34 prefix, user_id, scopes, expires_at, …).
36 Returns:
37 The opaque identifier assigned to the record by the store.
38 """
39 ...
41 async def find_by_prefix(self, prefix: str) -> list[dict[str, Any]]:
42 """Return all active (non-revoked) keys whose display prefix matches.
44 Args:
45 prefix: The short displayable prefix used for fast pre-filtering.
47 Returns:
48 List of raw row dicts; empty list when nothing matches.
49 """
50 ...
52 async def update_last_used(self, key_id: str, ip_address: str | None) -> None:
53 """Refresh the last-used timestamp and originating IP for a key.
55 Args:
56 key_id: Identifier of the key to update.
57 ip_address: Caller IP, or ``None`` when unavailable.
58 """
59 ...
61 async def revoke(self, key_id: str) -> None:
62 """Mark a key as permanently revoked.
64 Args:
65 key_id: Identifier of the key to revoke.
66 """
67 ...
69 async def find_by_user(self, user_id: str) -> list[dict[str, Any]]:
70 """Return all active (non-revoked) keys for a user.
72 Args:
73 user_id: Owner identifier.
75 Returns:
76 List of raw row dicts; empty list when nothing matches.
77 """
78 ...
81@runtime_checkable
82class SessionRepositoryProtocol(Protocol):
83 """Persistence contract for user session records.
85 Decouples session-management business logic from the underlying store,
86 allowing SQL, Redis, or any other backend to be injected without
87 altering the service layer.
88 """
90 async def insert(self, payload: dict[str, Any]) -> None:
91 """Persist a new session record.
93 Args:
94 payload: Field/value mapping for the new row (session_id,
95 admin_id, device_id, ip_address, user_agent, fingerprint,
96 expires_at, …).
97 """
98 ...
100 async def find_active(self, session_id: str) -> dict[str, Any] | None:
101 """Return the row for an active session, or ``None`` if absent/inactive.
103 Args:
104 session_id: The opaque session identifier.
106 Returns:
107 Raw row dict on success, ``None`` otherwise.
108 """
109 ...
111 async def find_active_by_user(
112 self,
113 user_id: str,
114 cutoff: datetime,
115 ) -> list[dict[str, Any]]:
116 """Return all non-expired, active sessions for a user.
118 Args:
119 user_id: The user to query for.
120 cutoff: Sessions expiring *at or before* this time are excluded.
122 Returns:
123 List of raw row dicts ordered by ``last_active_at`` descending.
124 """
125 ...
127 async def revoke(self, session_id: str) -> None:
128 """Deactivate a single session.
130 Args:
131 session_id: Session to revoke.
132 """
133 ...
135 async def revoke_all(self, user_id: str) -> None:
136 """Deactivate every active session owned by a user.
138 Args:
139 user_id: Owner whose sessions are to be revoked.
140 """
141 ...
143 async def update_activity(self, session_id: str, now: datetime) -> None:
144 """Refresh the ``last_active_at`` timestamp for an active session.
146 Args:
147 session_id: Session to touch.
148 now: Current UTC timestamp to write.
149 """
150 ...