Skip to content

Commit f7786c3

Browse files
committed
docs: add missing docstrings to reach 80% coverage threshold
1 parent e9105b3 commit f7786c3

File tree

8 files changed

+150
-1
lines changed

8 files changed

+150
-1
lines changed

sdks/implementations/python/src/stack_auth/_app.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,15 @@ def __init__(
125125
base_url: str = DEFAULT_BASE_URL,
126126
token_store: TokenStoreInit | None = None,
127127
) -> None:
128+
"""Initialize the synchronous Stack Auth application client.
129+
130+
Args:
131+
project_id: The Stack Auth project identifier.
132+
secret_server_key: Server-side secret key for authentication.
133+
publishable_client_key: Optional publishable client key.
134+
base_url: Stack Auth API base URL.
135+
token_store: Optional token storage initializer for user sessions.
136+
"""
128137
self._project_id = project_id
129138
self._client = SyncAPIClient(
130139
project_id=project_id,
@@ -150,9 +159,11 @@ def close(self) -> None:
150159
self._client.close()
151160

152161
def __enter__(self) -> StackServerApp:
162+
"""Enter the context manager."""
153163
return self
154164

155165
def __exit__(self, *_: Any) -> None:
166+
"""Exit the context manager and close the client."""
156167
self.close()
157168

158169
# -- partial user (local JWT decode) -------------------------------------
@@ -1446,6 +1457,15 @@ def __init__(
14461457
base_url: str = DEFAULT_BASE_URL,
14471458
token_store: TokenStoreInit | None = None,
14481459
) -> None:
1460+
"""Initialize the asynchronous Stack Auth application client.
1461+
1462+
Args:
1463+
project_id: The Stack Auth project identifier.
1464+
secret_server_key: Server-side secret key for authentication.
1465+
publishable_client_key: Optional publishable client key.
1466+
base_url: Stack Auth API base URL.
1467+
token_store: Optional token storage initializer for user sessions.
1468+
"""
14491469
self._project_id = project_id
14501470
self._client = AsyncAPIClient(
14511471
project_id=project_id,
@@ -1471,9 +1491,11 @@ async def aclose(self) -> None:
14711491
await self._client.aclose()
14721492

14731493
async def __aenter__(self) -> AsyncStackServerApp:
1494+
"""Enter the async context manager."""
14741495
return self
14751496

14761497
async def __aexit__(self, *_: Any) -> None:
1498+
"""Exit the async context manager and close the client."""
14771499
await self.aclose()
14781500

14791501
# -- partial user (local JWT decode) -------------------------------------

sdks/implementations/python/src/stack_auth/_client.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,14 @@ def __init__(
4040
secret_server_key: str,
4141
publishable_client_key: str | None = None,
4242
) -> None:
43+
"""Initialize the API client.
44+
45+
Args:
46+
base_url: Stack Auth API base URL.
47+
project_id: The Stack Auth project identifier.
48+
secret_server_key: Server-side secret key for authentication.
49+
publishable_client_key: Optional publishable client key.
50+
"""
4351
self._base_url = base_url.rstrip("/")
4452
self._project_id = project_id
4553
self._secret_server_key = secret_server_key
@@ -51,6 +59,7 @@ def __init__(
5159
# ------------------------------------------------------------------
5260

5361
def _build_headers(self) -> dict[str, str]:
62+
"""Build HTTP headers required for every Stack Auth API request."""
5463
headers = {
5564
"x-stack-project-id": self._project_id,
5665
"x-stack-access-type": "server",
@@ -64,6 +73,7 @@ def _build_headers(self) -> dict[str, str]:
6473
return headers
6574

6675
def _build_url(self, path: str) -> str:
76+
"""Construct the full API URL for the given endpoint path."""
6777
return f"{self._base_url}/api/{API_VERSION}{path}"
6878

6979
# ------------------------------------------------------------------
@@ -114,10 +124,12 @@ def _parse_response(self, response: httpx.Response) -> tuple[int, dict[str, Any]
114124
# ------------------------------------------------------------------
115125

116126
def _should_retry(self, method: str, attempt: int) -> bool:
127+
"""Return True if the request method is idempotent and retries remain."""
117128
return method.upper() in self.IDEMPOTENT_METHODS and attempt < self.MAX_RETRIES
118129

119130
@staticmethod
120131
def _get_retry_delay(attempt: int, response: httpx.Response | None = None) -> float:
132+
"""Calculate retry delay using Retry-After header or exponential backoff."""
121133
if response is not None and response.status_code == 429:
122134
retry_after = response.headers.get("Retry-After")
123135
if retry_after is not None:
@@ -142,6 +154,7 @@ class SyncAPIClient(BaseAPIClient[httpx.Client]):
142154
"""Synchronous HTTP client using :class:`httpx.Client`."""
143155

144156
def _get_client(self) -> httpx.Client:
157+
"""Return the underlying httpx.Client, creating it lazily if needed."""
145158
if self._client is None:
146159
self._client = httpx.Client(timeout=httpx.Timeout(30.0))
147160
return self._client
@@ -154,6 +167,20 @@ def request(
154167
body: dict[str, Any] | None = None,
155168
params: dict[str, Any] | None = None,
156169
) -> dict[str, Any] | None:
170+
"""Send a synchronous HTTP request with retry and error handling.
171+
172+
Args:
173+
method: HTTP method (GET, POST, PUT, PATCH, DELETE).
174+
path: API endpoint path (appended to the base URL).
175+
body: Optional JSON body for the request.
176+
params: Optional query parameters.
177+
178+
Returns:
179+
Parsed JSON response dict, or None for empty responses.
180+
181+
Raises:
182+
StackAuthError: On known API errors or non-2xx responses.
183+
"""
157184
url = self._build_url(path)
158185
headers = self._build_headers()
159186

@@ -213,21 +240,25 @@ def request(
213240
# ------------------------------------------------------------------
214241

215242
def close(self) -> None:
243+
"""Close the underlying HTTP client and release resources."""
216244
if self._client is not None:
217245
self._client.close()
218246
self._client = None
219247

220248
def __enter__(self) -> SyncAPIClient:
249+
"""Enter the context manager."""
221250
return self
222251

223252
def __exit__(self, *_: Any) -> None:
253+
"""Exit the context manager and close the client."""
224254
self.close()
225255

226256

227257
class AsyncAPIClient(BaseAPIClient[httpx.AsyncClient]):
228258
"""Asynchronous HTTP client using :class:`httpx.AsyncClient`."""
229259

230260
def _get_client(self) -> httpx.AsyncClient:
261+
"""Return the underlying httpx.AsyncClient, creating it lazily if needed."""
231262
if self._client is None:
232263
self._client = httpx.AsyncClient(timeout=httpx.Timeout(30.0))
233264
return self._client
@@ -240,6 +271,20 @@ async def request(
240271
body: dict[str, Any] | None = None,
241272
params: dict[str, Any] | None = None,
242273
) -> dict[str, Any] | None:
274+
"""Send an asynchronous HTTP request with retry and error handling.
275+
276+
Args:
277+
method: HTTP method (GET, POST, PUT, PATCH, DELETE).
278+
path: API endpoint path (appended to the base URL).
279+
body: Optional JSON body for the request.
280+
params: Optional query parameters.
281+
282+
Returns:
283+
Parsed JSON response dict, or None for empty responses.
284+
285+
Raises:
286+
StackAuthError: On known API errors or non-2xx responses.
287+
"""
243288
url = self._build_url(path)
244289
headers = self._build_headers()
245290

@@ -296,12 +341,15 @@ async def request(
296341
# ------------------------------------------------------------------
297342

298343
async def aclose(self) -> None:
344+
"""Close the underlying async HTTP client and release resources."""
299345
if self._client is not None:
300346
await self._client.aclose()
301347
self._client = None
302348

303349
async def __aenter__(self) -> AsyncAPIClient:
350+
"""Enter the async context manager."""
304351
return self
305352

306353
async def __aexit__(self, *_: Any) -> None:
354+
"""Exit the async context manager and close the client."""
307355
await self.aclose()

sdks/implementations/python/src/stack_auth/_jwt.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ class AsyncJWKSFetcher:
3232
"""
3333

3434
def __init__(self, jwks_url: str, http_client: httpx.AsyncClient) -> None:
35+
"""Initialize the async JWKS fetcher.
36+
37+
Args:
38+
jwks_url: The URL of the JWKS endpoint.
39+
http_client: An httpx.AsyncClient for making HTTP requests.
40+
"""
3541
self._jwks_url = jwks_url
3642
self._http_client = http_client
3743
self._cache: dict[str, Any] | None = None
@@ -84,6 +90,12 @@ class SyncJWKSFetcher:
8490
"""
8591

8692
def __init__(self, jwks_url: str, http_client: httpx.Client) -> None:
93+
"""Initialize the sync JWKS fetcher.
94+
95+
Args:
96+
jwks_url: The URL of the JWKS endpoint.
97+
http_client: An httpx.Client for making HTTP requests.
98+
"""
8799
self._jwks_url = jwks_url
88100
self._http_client = http_client
89101
self._cache: dict[str, Any] | None = None

sdks/implementations/python/src/stack_auth/_token_store.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ class TokenStore(ABC):
3838
"""
3939

4040
def __init__(self) -> None:
41+
"""Initialize the token store with sync and async CAS locks."""
4142
self._sync_lock = threading.Lock()
4243
self._async_lock = asyncio.Lock()
4344

@@ -70,14 +71,17 @@ class MemoryTokenStore(TokenStore):
7071
"""In-memory token store. Shared per project_id via the module registry."""
7172

7273
def __init__(self) -> None:
74+
"""Initialize with empty access and refresh tokens."""
7375
super().__init__()
7476
self._access_token: str | None = None
7577
self._refresh_token: str | None = None
7678

7779
def get_stored_access_token(self) -> str | None:
80+
"""Return the stored access token, or None if not set."""
7881
return self._access_token
7982

8083
def get_stored_refresh_token(self) -> str | None:
84+
"""Return the stored refresh token, or None if not set."""
8185
return self._refresh_token
8286

8387
def compare_and_set(
@@ -86,6 +90,7 @@ def compare_and_set(
8690
new_refresh_token: str | None,
8791
new_access_token: str | None,
8892
) -> None:
93+
"""Atomically update tokens if the current refresh token matches."""
8994
if self._refresh_token == compare_refresh_token:
9095
self._refresh_token = new_refresh_token
9196
self._access_token = new_access_token
@@ -102,14 +107,22 @@ class ExplicitTokenStore(TokenStore):
102107
"""
103108

104109
def __init__(self, access_token: str | None = None, refresh_token: str | None = None) -> None:
110+
"""Initialize with explicit token values.
111+
112+
Args:
113+
access_token: Initial access token, or None.
114+
refresh_token: Initial refresh token, or None.
115+
"""
105116
super().__init__()
106117
self._access_token: str | None = access_token
107118
self._refresh_token: str | None = refresh_token
108119

109120
def get_stored_access_token(self) -> str | None:
121+
"""Return the stored access token, or None if not set."""
110122
return self._access_token
111123

112124
def get_stored_refresh_token(self) -> str | None:
125+
"""Return the stored refresh token, or None if not set."""
113126
return self._refresh_token
114127

115128
def compare_and_set(
@@ -118,6 +131,7 @@ def compare_and_set(
118131
new_refresh_token: str | None,
119132
new_access_token: str | None,
120133
) -> None:
134+
"""Atomically update tokens if the current refresh token matches."""
121135
if self._refresh_token == compare_refresh_token:
122136
self._refresh_token = new_refresh_token
123137
self._access_token = new_access_token
@@ -134,6 +148,11 @@ class RequestTokenStore(TokenStore):
134148
"""
135149

136150
def __init__(self, request: RequestLike) -> None:
151+
"""Initialize by extracting tokens from the request's x-stack-auth header.
152+
153+
Args:
154+
request: A request-like object whose headers may contain x-stack-auth.
155+
"""
137156
super().__init__()
138157
self._access_token: str | None = None
139158
self._refresh_token: str | None = None
@@ -147,9 +166,11 @@ def __init__(self, request: RequestLike) -> None:
147166
pass
148167

149168
def get_stored_access_token(self) -> str | None:
169+
"""Return the stored access token, or None if not set."""
150170
return self._access_token
151171

152172
def get_stored_refresh_token(self) -> str | None:
173+
"""Return the stored refresh token, or None if not set."""
153174
return self._refresh_token
154175

155176
def compare_and_set(
@@ -158,6 +179,7 @@ def compare_and_set(
158179
new_refresh_token: str | None,
159180
new_access_token: str | None,
160181
) -> None:
182+
"""Atomically update tokens if the current refresh token matches."""
161183
if self._refresh_token == compare_refresh_token:
162184
self._refresh_token = new_refresh_token
163185
self._access_token = new_access_token

sdks/implementations/python/src/stack_auth/_types.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,9 @@
55

66
@runtime_checkable
77
class RequestLike(Protocol):
8+
"""Protocol for objects that expose HTTP headers (e.g. Django/Flask/Starlette requests)."""
9+
810
@property
9-
def headers(self) -> Mapping[str, str]: ...
11+
def headers(self) -> Mapping[str, str]:
12+
"""Return the request headers as a string mapping."""
13+
...

sdks/implementations/python/src/stack_auth/errors.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,13 @@ class StackAuthError(Exception):
1515
"""
1616

1717
def __init__(self, code: str, message: str, details: dict[str, Any] | None = None) -> None:
18+
"""Initialize a StackAuthError.
19+
20+
Args:
21+
code: The error code string from the Stack Auth API.
22+
message: Human-readable error description.
23+
details: Optional dictionary with additional error context.
24+
"""
1825
self.code = code
1926
self.message = message
2027
self.details = details

sdks/implementations/python/src/stack_auth/models/data_vault.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ class DataVaultStore:
2222
"""
2323

2424
def __init__(self, store_id: str, *, _client: Any) -> None:
25+
"""Initialize a synchronous data vault store.
26+
27+
Args:
28+
store_id: The data vault store identifier.
29+
_client: The internal HTTP client used for API requests.
30+
"""
2531
self.id = store_id
2632
self._client = _client
2733
self._base_path = f"/data-vault/stores/{store_id}/items"
@@ -69,6 +75,12 @@ class AsyncDataVaultStore:
6975
"""
7076

7177
def __init__(self, store_id: str, *, _client: Any) -> None:
78+
"""Initialize an asynchronous data vault store.
79+
80+
Args:
81+
store_id: The data vault store identifier.
82+
_client: The internal async HTTP client used for API requests.
83+
"""
7284
self.id = store_id
7385
self._client = _client
7486
self._base_path = f"/data-vault/stores/{store_id}/items"

0 commit comments

Comments
 (0)