csrd-context 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- csrd/context/__init__.py +51 -0
- csrd/context/_constants.py +6 -0
- csrd/context/_contextvars.py +184 -0
- csrd/context/_fastapi_headers.py +19 -0
- csrd/context/_models.py +34 -0
- csrd/context/middleware/__init__.py +4 -0
- csrd/context/middleware/_logging.py +121 -0
- csrd/context/middleware/_request.py +51 -0
- csrd/context/platform.py +21 -0
- csrd/context/py.typed +0 -0
- csrd_context-0.1.0.dist-info/METADATA +12 -0
- csrd_context-0.1.0.dist-info/RECORD +13 -0
- csrd_context-0.1.0.dist-info/WHEEL +4 -0
csrd/context/__init__.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
from ._contextvars import (
|
|
2
|
+
configure_headers_context_provider,
|
|
3
|
+
get_api_version,
|
|
4
|
+
get_app_id,
|
|
5
|
+
get_headers,
|
|
6
|
+
get_hit_id,
|
|
7
|
+
get_path_params,
|
|
8
|
+
get_query_params,
|
|
9
|
+
reset_api_version_context,
|
|
10
|
+
reset_global_configuration,
|
|
11
|
+
reset_headers_context,
|
|
12
|
+
reset_path_params,
|
|
13
|
+
reset_query_params,
|
|
14
|
+
set_api_version_context,
|
|
15
|
+
set_headers_context,
|
|
16
|
+
set_path_params,
|
|
17
|
+
set_query_params,
|
|
18
|
+
)
|
|
19
|
+
from ._models import PathValue
|
|
20
|
+
from .middleware import HTTPLoggingMiddleware, RequestContextMiddleware
|
|
21
|
+
from .platform import app_id_context, hit_id_context, user_info_context
|
|
22
|
+
|
|
23
|
+
__all__ = (
|
|
24
|
+
"HTTPLoggingMiddleware",
|
|
25
|
+
# Models
|
|
26
|
+
"PathValue",
|
|
27
|
+
# Middleware
|
|
28
|
+
"RequestContextMiddleware",
|
|
29
|
+
"app_id_context",
|
|
30
|
+
# Context accessors
|
|
31
|
+
"configure_headers_context_provider",
|
|
32
|
+
"get_api_version",
|
|
33
|
+
"get_app_id",
|
|
34
|
+
"get_headers",
|
|
35
|
+
"get_hit_id",
|
|
36
|
+
"get_path_params",
|
|
37
|
+
"get_query_params",
|
|
38
|
+
"hit_id_context",
|
|
39
|
+
"reset_api_version_context",
|
|
40
|
+
"reset_global_configuration",
|
|
41
|
+
"reset_headers_context",
|
|
42
|
+
"reset_path_params",
|
|
43
|
+
"reset_query_params",
|
|
44
|
+
"set_api_version_context",
|
|
45
|
+
# Context setters
|
|
46
|
+
"set_headers_context",
|
|
47
|
+
"set_path_params",
|
|
48
|
+
"set_query_params",
|
|
49
|
+
# Platform contextvars
|
|
50
|
+
"user_info_context",
|
|
51
|
+
)
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from collections.abc import Callable
|
|
3
|
+
from contextvars import ContextVar, Token
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from ._constants import APP_ID_HEADER_NAME, HIT_ID_HEADER_NAME
|
|
7
|
+
from ._models import PathValue
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
# Request-scoped context variables and accessors.
|
|
12
|
+
#
|
|
13
|
+
# The ``configure_*`` functions below set module-level state and must be called
|
|
14
|
+
# once during application startup, before any requests are served.
|
|
15
|
+
|
|
16
|
+
_PATH_CONTEXT_KEY = "path_context"
|
|
17
|
+
_QUERY_CONTEXT_KEY = "query_context"
|
|
18
|
+
_API_VERSION_CONTEXT_KEY = "api_version_context"
|
|
19
|
+
|
|
20
|
+
_path_context: ContextVar[PathValue | None] = ContextVar(_PATH_CONTEXT_KEY, default=None)
|
|
21
|
+
_query_context: ContextVar[PathValue | None] = ContextVar(_QUERY_CONTEXT_KEY, default=None)
|
|
22
|
+
_api_version_context: ContextVar[str | None] = ContextVar(_API_VERSION_CONTEXT_KEY, default=None)
|
|
23
|
+
|
|
24
|
+
_headers_getter: Callable[[], Any] | None = None
|
|
25
|
+
_headers_setter: Callable[[Any], Any] | None = None
|
|
26
|
+
_headers_resetter: Callable[[Any], None] | None = None
|
|
27
|
+
_unconfigured_headers_warned = False
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def reset_global_configuration() -> None:
|
|
31
|
+
"""Reset all module-level configuration to defaults.
|
|
32
|
+
|
|
33
|
+
Intended for test teardown to prevent cross-test contamination.
|
|
34
|
+
"""
|
|
35
|
+
global _headers_getter, _headers_setter, _headers_resetter
|
|
36
|
+
global _unconfigured_headers_warned
|
|
37
|
+
_headers_getter = None
|
|
38
|
+
_headers_setter = None
|
|
39
|
+
_headers_resetter = None
|
|
40
|
+
_unconfigured_headers_warned = False
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def configure_headers_context_provider(
|
|
44
|
+
*,
|
|
45
|
+
get_headers: Callable[[], Any],
|
|
46
|
+
set_headers: Callable[[Any], Any],
|
|
47
|
+
reset_headers: Callable[[Any], None],
|
|
48
|
+
) -> None:
|
|
49
|
+
"""Configure framework-provided header context accessors.
|
|
50
|
+
|
|
51
|
+
Must be called during application startup, before serving requests.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
global _headers_getter, _headers_setter, _headers_resetter
|
|
55
|
+
if _headers_getter is not None:
|
|
56
|
+
logger.warning("Overwriting previously configured headers_context_provider")
|
|
57
|
+
_headers_getter = get_headers
|
|
58
|
+
_headers_setter = set_headers
|
|
59
|
+
_headers_resetter = reset_headers
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def set_headers_context(headers: Any) -> Any:
|
|
63
|
+
"""Set current request headers in the configured framework context."""
|
|
64
|
+
if _headers_setter is None:
|
|
65
|
+
raise RuntimeError(
|
|
66
|
+
"Headers context not configured. "
|
|
67
|
+
"Call configure_headers_context_provider() "
|
|
68
|
+
"before using the context system."
|
|
69
|
+
)
|
|
70
|
+
return _headers_setter(headers)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def reset_headers_context(token: Any) -> None:
|
|
74
|
+
"""Reset current request headers from the configured framework context."""
|
|
75
|
+
if token is None:
|
|
76
|
+
return
|
|
77
|
+
if _headers_resetter is None:
|
|
78
|
+
raise RuntimeError(
|
|
79
|
+
"Headers context not configured. "
|
|
80
|
+
"Call configure_headers_context_provider() "
|
|
81
|
+
"before using the context system."
|
|
82
|
+
)
|
|
83
|
+
_headers_resetter(token)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def set_path_params(path_params: PathValue) -> Token[PathValue | None]:
|
|
87
|
+
"""Store path parameters for the current async context; returns a token for reset."""
|
|
88
|
+
return _path_context.set(path_params)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def reset_path_params(token: Token[PathValue | None]) -> None:
|
|
92
|
+
"""Restore path parameters to their previous value."""
|
|
93
|
+
_path_context.reset(token)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def set_query_params(query_params: PathValue) -> Token[PathValue | None]:
|
|
97
|
+
"""Store query parameters for the current async context; returns a token for reset."""
|
|
98
|
+
return _query_context.set(query_params)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def reset_query_params(token: Token[PathValue | None]) -> None:
|
|
102
|
+
"""Restore query parameters to their previous value."""
|
|
103
|
+
_query_context.reset(token)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def set_api_version_context(version: str | None) -> Token[str | None]:
|
|
107
|
+
"""Store the resolved API version for the current async context."""
|
|
108
|
+
return _api_version_context.set(version)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def reset_api_version_context(token: Token[str | None]) -> None:
|
|
112
|
+
"""Restore the API version to its previous value."""
|
|
113
|
+
_api_version_context.reset(token)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def get_path_params() -> PathValue:
|
|
117
|
+
"""Return request path parameters captured in the current context."""
|
|
118
|
+
params = _path_context.get()
|
|
119
|
+
if params is None:
|
|
120
|
+
return PathValue()
|
|
121
|
+
return params
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def get_query_params() -> PathValue:
|
|
125
|
+
"""Return request query parameters captured in the current context."""
|
|
126
|
+
params = _query_context.get()
|
|
127
|
+
if params is None:
|
|
128
|
+
return PathValue()
|
|
129
|
+
return params
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def get_api_version() -> str | None:
|
|
133
|
+
"""Return resolved API version for the current request context."""
|
|
134
|
+
return _api_version_context.get()
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def get_headers() -> Any:
|
|
138
|
+
"""Return current request headers captured during dispatch."""
|
|
139
|
+
global _unconfigured_headers_warned
|
|
140
|
+
if _headers_getter is None:
|
|
141
|
+
if not _unconfigured_headers_warned:
|
|
142
|
+
_unconfigured_headers_warned = True
|
|
143
|
+
logger.warning(
|
|
144
|
+
"Headers context provider not configured. "
|
|
145
|
+
"get_headers(), get_app_id(), and get_hit_id() will return empty values. "
|
|
146
|
+
"Call configure_headers_context_provider() during startup."
|
|
147
|
+
)
|
|
148
|
+
return {}
|
|
149
|
+
headers = _headers_getter()
|
|
150
|
+
if headers is None:
|
|
151
|
+
return {}
|
|
152
|
+
return headers
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def get_app_id() -> str | None:
|
|
156
|
+
"""Return the current request app-id header value."""
|
|
157
|
+
val = get_headers().get(APP_ID_HEADER_NAME, None)
|
|
158
|
+
return str(val) if val is not None else None
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def get_hit_id() -> str | None:
|
|
162
|
+
"""Return the current request hit-id header value."""
|
|
163
|
+
val = get_headers().get(HIT_ID_HEADER_NAME, None)
|
|
164
|
+
return str(val) if val is not None else None
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
__all__ = (
|
|
168
|
+
"configure_headers_context_provider",
|
|
169
|
+
"get_api_version",
|
|
170
|
+
"get_app_id",
|
|
171
|
+
"get_headers",
|
|
172
|
+
"get_hit_id",
|
|
173
|
+
"get_path_params",
|
|
174
|
+
"get_query_params",
|
|
175
|
+
"reset_api_version_context",
|
|
176
|
+
"reset_global_configuration",
|
|
177
|
+
"reset_headers_context",
|
|
178
|
+
"reset_path_params",
|
|
179
|
+
"reset_query_params",
|
|
180
|
+
"set_api_version_context",
|
|
181
|
+
"set_headers_context",
|
|
182
|
+
"set_path_params",
|
|
183
|
+
"set_query_params",
|
|
184
|
+
)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""FastAPI-specific context variable for request headers."""
|
|
2
|
+
|
|
3
|
+
from contextvars import ContextVar
|
|
4
|
+
|
|
5
|
+
from starlette.datastructures import Headers
|
|
6
|
+
|
|
7
|
+
HEADERS_KEY = "request_headers"
|
|
8
|
+
|
|
9
|
+
_EMPTY_HEADERS = Headers()
|
|
10
|
+
|
|
11
|
+
headers_context: ContextVar[Headers] = ContextVar(HEADERS_KEY, default=_EMPTY_HEADERS)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def get_headers() -> Headers:
|
|
15
|
+
"""Return request headers stored in the current context."""
|
|
16
|
+
return headers_context.get()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
__all__ = ("HEADERS_KEY", "get_headers", "headers_context")
|
csrd/context/_models.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class PathValue(dict[str, Any]):
|
|
5
|
+
"""Dictionary wrapper that also supports dot-notation for key access."""
|
|
6
|
+
|
|
7
|
+
def __getattr__(self, item: str) -> Any:
|
|
8
|
+
"""Return dict values via dot-notation, raising AttributeError for missing keys."""
|
|
9
|
+
if item.startswith("__"):
|
|
10
|
+
raise AttributeError(item)
|
|
11
|
+
try:
|
|
12
|
+
return self[item]
|
|
13
|
+
except KeyError:
|
|
14
|
+
raise AttributeError(f"'{type(self).__name__}' has no key '{item}'") from None
|
|
15
|
+
|
|
16
|
+
def __setattr__(self, key: str, value: Any) -> None:
|
|
17
|
+
"""Map attribute assignment to dictionary item assignment."""
|
|
18
|
+
if key.startswith("__"):
|
|
19
|
+
super().__setattr__(key, value)
|
|
20
|
+
return
|
|
21
|
+
self[key] = value
|
|
22
|
+
|
|
23
|
+
def __delattr__(self, item: str) -> None:
|
|
24
|
+
"""Map attribute deletion to dictionary item deletion."""
|
|
25
|
+
if item.startswith("__"):
|
|
26
|
+
super().__delattr__(item)
|
|
27
|
+
return
|
|
28
|
+
try:
|
|
29
|
+
del self[item]
|
|
30
|
+
except KeyError as exc:
|
|
31
|
+
raise AttributeError(item) from exc
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
__all__ = ("PathValue",)
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"""HTTP logging middleware for FastAPI applications.
|
|
2
|
+
|
|
3
|
+
Implemented as a raw ASGI middleware (no ``BaseHTTPMiddleware``) so that
|
|
4
|
+
``StreamingResponse`` and SSE endpoints are not buffered.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
import time
|
|
9
|
+
import uuid
|
|
10
|
+
from collections import defaultdict
|
|
11
|
+
from http import HTTPStatus
|
|
12
|
+
from typing import Any, TypedDict
|
|
13
|
+
|
|
14
|
+
from starlette.requests import Request
|
|
15
|
+
from starlette.routing import Match
|
|
16
|
+
from starlette.types import ASGIApp, Receive, Scope, Send
|
|
17
|
+
|
|
18
|
+
from csrd.models.claims import UserClaims
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
REQUEST_SCOPE_KEY = "__DS__"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class RequestScope(TypedDict, total=False):
|
|
26
|
+
hit_id: str
|
|
27
|
+
app_id: str
|
|
28
|
+
user_info: UserClaims | None
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class HTTPLoggingMiddleware:
|
|
32
|
+
"""Raw ASGI middleware to log HTTP request details including timing,
|
|
33
|
+
user context, and response information.
|
|
34
|
+
|
|
35
|
+
Unlike ``BaseHTTPMiddleware``, this does **not** buffer the response
|
|
36
|
+
body, so ``StreamingResponse`` and SSE endpoints work correctly.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __init__(self, app: ASGIApp) -> None:
|
|
40
|
+
self.app = app
|
|
41
|
+
|
|
42
|
+
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
|
|
43
|
+
if scope["type"] != "http":
|
|
44
|
+
await self.app(scope, receive, send)
|
|
45
|
+
return
|
|
46
|
+
|
|
47
|
+
request = Request(scope)
|
|
48
|
+
scope[REQUEST_SCOPE_KEY] = RequestScope()
|
|
49
|
+
|
|
50
|
+
elapsed = -time.perf_counter()
|
|
51
|
+
|
|
52
|
+
query_params: dict[str, list[str]] = defaultdict(list)
|
|
53
|
+
for key, value in request.query_params.multi_items():
|
|
54
|
+
query_params[key].append(value)
|
|
55
|
+
|
|
56
|
+
extras: dict[str, Any] = {
|
|
57
|
+
"method": request.method,
|
|
58
|
+
"uri": request.url.path,
|
|
59
|
+
"uri_mapping": self._get_route_path(request),
|
|
60
|
+
"query_params": query_params,
|
|
61
|
+
"hit_id": request.headers.get("x-client-hit-id") or str(uuid.uuid4()),
|
|
62
|
+
"app_id": request.headers.get("x-client-app-id", "unknown"),
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
logger.info("http.request.start", extra=extras)
|
|
66
|
+
|
|
67
|
+
# Track response status via a wrapper around send
|
|
68
|
+
status_holder: dict[str, int] = {"status": 0}
|
|
69
|
+
|
|
70
|
+
async def send_wrapper(message: Any) -> None:
|
|
71
|
+
if message["type"] == "http.response.start":
|
|
72
|
+
status_holder["status"] = message["status"]
|
|
73
|
+
await send(message)
|
|
74
|
+
|
|
75
|
+
exc_info = None
|
|
76
|
+
level = logging.INFO
|
|
77
|
+
try:
|
|
78
|
+
await self.app(scope, receive, send_wrapper)
|
|
79
|
+
status = status_holder["status"]
|
|
80
|
+
level = self._get_log_level(status)
|
|
81
|
+
except Exception as exc:
|
|
82
|
+
status = getattr(exc, "status_code", 500)
|
|
83
|
+
extras["error"] = exc.__cause__
|
|
84
|
+
exc_info = exc
|
|
85
|
+
level = logging.ERROR
|
|
86
|
+
raise
|
|
87
|
+
finally:
|
|
88
|
+
ds_scope = scope.get(REQUEST_SCOPE_KEY) or {}
|
|
89
|
+
extras.update(
|
|
90
|
+
hit_id=ds_scope.get("hit_id", "unknown"),
|
|
91
|
+
app_id=ds_scope.get("app_id", "unknown"),
|
|
92
|
+
)
|
|
93
|
+
if user_info := ds_scope.get("user_info"):
|
|
94
|
+
extras.update(
|
|
95
|
+
user_id=user_info.sub,
|
|
96
|
+
user_email=user_info.user_name,
|
|
97
|
+
)
|
|
98
|
+
elapsed += time.perf_counter()
|
|
99
|
+
extras["elapsed_millis"] = int(elapsed * 1000)
|
|
100
|
+
extras["status"] = status
|
|
101
|
+
logger.log(level, "http.request.complete", exc_info=exc_info, extra=extras)
|
|
102
|
+
|
|
103
|
+
@staticmethod
|
|
104
|
+
def _get_log_level(status: int) -> int:
|
|
105
|
+
http_status = HTTPStatus(status)
|
|
106
|
+
if http_status.is_informational or http_status.is_success or http_status.is_redirection:
|
|
107
|
+
return logging.INFO
|
|
108
|
+
if http_status.is_client_error:
|
|
109
|
+
return logging.WARNING
|
|
110
|
+
return logging.ERROR
|
|
111
|
+
|
|
112
|
+
@staticmethod
|
|
113
|
+
def _get_route_path(request: Request) -> str:
|
|
114
|
+
for route in request.app.routes:
|
|
115
|
+
match, _ = route.matches(request.scope)
|
|
116
|
+
if match == Match.FULL:
|
|
117
|
+
return str(route.path)
|
|
118
|
+
return request.url.path
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
__all__ = ("REQUEST_SCOPE_KEY", "HTTPLoggingMiddleware", "RequestScope")
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Request context middleware for setting up headers context.
|
|
2
|
+
|
|
3
|
+
Implemented as a raw ASGI middleware (no ``BaseHTTPMiddleware``) so that
|
|
4
|
+
``StreamingResponse`` and SSE endpoints are not buffered.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from starlette.requests import Request
|
|
8
|
+
from starlette.types import ASGIApp, Receive, Scope, Send
|
|
9
|
+
|
|
10
|
+
from .._contextvars import (
|
|
11
|
+
configure_headers_context_provider,
|
|
12
|
+
reset_headers_context,
|
|
13
|
+
set_headers_context,
|
|
14
|
+
)
|
|
15
|
+
from .._fastapi_headers import headers_context
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _setup_fastapi_headers_provider() -> None:
|
|
19
|
+
"""Wire the FastAPI headers ContextVar as the headers provider."""
|
|
20
|
+
configure_headers_context_provider(
|
|
21
|
+
get_headers=headers_context.get,
|
|
22
|
+
set_headers=headers_context.set,
|
|
23
|
+
reset_headers=headers_context.reset,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class RequestContextMiddleware:
|
|
28
|
+
"""Raw ASGI middleware that captures request headers into context variables.
|
|
29
|
+
|
|
30
|
+
Unlike ``BaseHTTPMiddleware``, this does **not** buffer the response body,
|
|
31
|
+
so ``StreamingResponse`` and SSE endpoints work correctly.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(self, app: ASGIApp, **kwargs: object) -> None:
|
|
35
|
+
self.app = app
|
|
36
|
+
_setup_fastapi_headers_provider()
|
|
37
|
+
|
|
38
|
+
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
|
|
39
|
+
if scope["type"] != "http":
|
|
40
|
+
await self.app(scope, receive, send)
|
|
41
|
+
return
|
|
42
|
+
|
|
43
|
+
request = Request(scope)
|
|
44
|
+
token = set_headers_context(request.headers)
|
|
45
|
+
try:
|
|
46
|
+
await self.app(scope, receive, send)
|
|
47
|
+
finally:
|
|
48
|
+
reset_headers_context(token)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
__all__ = ("RequestContextMiddleware",)
|
csrd/context/platform.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Platform-level context variables for user info, hit-id, and app-id."""
|
|
2
|
+
|
|
3
|
+
from contextvars import ContextVar
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
__all__ = (
|
|
7
|
+
"APP_ID_KEY",
|
|
8
|
+
"HIT_ID_KEY",
|
|
9
|
+
"USER_INFO_KEY",
|
|
10
|
+
"app_id_context",
|
|
11
|
+
"hit_id_context",
|
|
12
|
+
"user_info_context",
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
USER_INFO_KEY = "user_info"
|
|
16
|
+
HIT_ID_KEY = "hit_id"
|
|
17
|
+
APP_ID_KEY = "app_id"
|
|
18
|
+
|
|
19
|
+
user_info_context: ContextVar[Any | None] = ContextVar(USER_INFO_KEY, default=None)
|
|
20
|
+
hit_id_context: ContextVar[str] = ContextVar(HIT_ID_KEY, default="unknown")
|
|
21
|
+
app_id_context: ContextVar[str] = ContextVar(APP_ID_KEY, default="unknown")
|
csrd/context/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: csrd-context
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Request context, header utilities, and logging middleware for FastAPI
|
|
5
|
+
Project-URL: Repository, https://github.com/csrd-api/fastapi-common
|
|
6
|
+
Project-URL: Documentation, https://github.com/csrd-api/fastapi-common/tree/main/packages/context
|
|
7
|
+
Project-URL: Changelog, https://github.com/csrd-api/fastapi-common/blob/main/CHANGELOG.md
|
|
8
|
+
License: MIT
|
|
9
|
+
Requires-Python: >=3.12
|
|
10
|
+
Requires-Dist: csrd-models
|
|
11
|
+
Requires-Dist: fastapi<1,>=0.115
|
|
12
|
+
Requires-Dist: starlette>=0.36
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
csrd/context/__init__.py,sha256=pKZhboGarvcEV2D_pdEtDNP6JMSCFYVKBM7KFiAh8QY,1276
|
|
2
|
+
csrd/context/_constants.py,sha256=8pgg9TI3EOODbYnkKXSMvxaMOO0n4nsKvyphdXR0gak,195
|
|
3
|
+
csrd/context/_contextvars.py,sha256=sRxi-0EwbUk8IwvdKYTCauLyHOt-4Han16_aE6VXlBg,5984
|
|
4
|
+
csrd/context/_fastapi_headers.py,sha256=aJVhMHTjFPpaWU9q1CcwCkT8_jWCSTvdEZNhxCGlbtE,483
|
|
5
|
+
csrd/context/_models.py,sha256=okTHUDODPi6GlLkmHHCHaEeNipT-Ab4TAdDkf7xZB70,1106
|
|
6
|
+
csrd/context/platform.py,sha256=XMQRT7uvsQdRwR6PRyWbEvXbobAPdnG7E9TvjjXCDFQ,584
|
|
7
|
+
csrd/context/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
csrd/context/middleware/__init__.py,sha256=kd3UP5e-lhEe825FDbaWwtHIGm3R3iakmX7JsvB7864,226
|
|
9
|
+
csrd/context/middleware/_logging.py,sha256=ONKzJcdbJk8zmgnw7FsIcfb5B1M3FH2qLTfCGA378Gs,4051
|
|
10
|
+
csrd/context/middleware/_request.py,sha256=wvJsL1xa7WXkvsVi2IVxLZRJ71NcrTA7_JRBcpv1bwI,1592
|
|
11
|
+
csrd_context-0.1.0.dist-info/METADATA,sha256=g9FMm81C4hEu4PcFq-9q8qEBS-B-rPVfN4KAy9BiOpI,519
|
|
12
|
+
csrd_context-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
13
|
+
csrd_context-0.1.0.dist-info/RECORD,,
|