foundry-mcp 0.8.22__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.

Potentially problematic release.


This version of foundry-mcp might be problematic. Click here for more details.

Files changed (153) hide show
  1. foundry_mcp/__init__.py +13 -0
  2. foundry_mcp/cli/__init__.py +67 -0
  3. foundry_mcp/cli/__main__.py +9 -0
  4. foundry_mcp/cli/agent.py +96 -0
  5. foundry_mcp/cli/commands/__init__.py +37 -0
  6. foundry_mcp/cli/commands/cache.py +137 -0
  7. foundry_mcp/cli/commands/dashboard.py +148 -0
  8. foundry_mcp/cli/commands/dev.py +446 -0
  9. foundry_mcp/cli/commands/journal.py +377 -0
  10. foundry_mcp/cli/commands/lifecycle.py +274 -0
  11. foundry_mcp/cli/commands/modify.py +824 -0
  12. foundry_mcp/cli/commands/plan.py +640 -0
  13. foundry_mcp/cli/commands/pr.py +393 -0
  14. foundry_mcp/cli/commands/review.py +667 -0
  15. foundry_mcp/cli/commands/session.py +472 -0
  16. foundry_mcp/cli/commands/specs.py +686 -0
  17. foundry_mcp/cli/commands/tasks.py +807 -0
  18. foundry_mcp/cli/commands/testing.py +676 -0
  19. foundry_mcp/cli/commands/validate.py +982 -0
  20. foundry_mcp/cli/config.py +98 -0
  21. foundry_mcp/cli/context.py +298 -0
  22. foundry_mcp/cli/logging.py +212 -0
  23. foundry_mcp/cli/main.py +44 -0
  24. foundry_mcp/cli/output.py +122 -0
  25. foundry_mcp/cli/registry.py +110 -0
  26. foundry_mcp/cli/resilience.py +178 -0
  27. foundry_mcp/cli/transcript.py +217 -0
  28. foundry_mcp/config.py +1454 -0
  29. foundry_mcp/core/__init__.py +144 -0
  30. foundry_mcp/core/ai_consultation.py +1773 -0
  31. foundry_mcp/core/batch_operations.py +1202 -0
  32. foundry_mcp/core/cache.py +195 -0
  33. foundry_mcp/core/capabilities.py +446 -0
  34. foundry_mcp/core/concurrency.py +898 -0
  35. foundry_mcp/core/context.py +540 -0
  36. foundry_mcp/core/discovery.py +1603 -0
  37. foundry_mcp/core/error_collection.py +728 -0
  38. foundry_mcp/core/error_store.py +592 -0
  39. foundry_mcp/core/health.py +749 -0
  40. foundry_mcp/core/intake.py +933 -0
  41. foundry_mcp/core/journal.py +700 -0
  42. foundry_mcp/core/lifecycle.py +412 -0
  43. foundry_mcp/core/llm_config.py +1376 -0
  44. foundry_mcp/core/llm_patterns.py +510 -0
  45. foundry_mcp/core/llm_provider.py +1569 -0
  46. foundry_mcp/core/logging_config.py +374 -0
  47. foundry_mcp/core/metrics_persistence.py +584 -0
  48. foundry_mcp/core/metrics_registry.py +327 -0
  49. foundry_mcp/core/metrics_store.py +641 -0
  50. foundry_mcp/core/modifications.py +224 -0
  51. foundry_mcp/core/naming.py +146 -0
  52. foundry_mcp/core/observability.py +1216 -0
  53. foundry_mcp/core/otel.py +452 -0
  54. foundry_mcp/core/otel_stubs.py +264 -0
  55. foundry_mcp/core/pagination.py +255 -0
  56. foundry_mcp/core/progress.py +387 -0
  57. foundry_mcp/core/prometheus.py +564 -0
  58. foundry_mcp/core/prompts/__init__.py +464 -0
  59. foundry_mcp/core/prompts/fidelity_review.py +691 -0
  60. foundry_mcp/core/prompts/markdown_plan_review.py +515 -0
  61. foundry_mcp/core/prompts/plan_review.py +627 -0
  62. foundry_mcp/core/providers/__init__.py +237 -0
  63. foundry_mcp/core/providers/base.py +515 -0
  64. foundry_mcp/core/providers/claude.py +472 -0
  65. foundry_mcp/core/providers/codex.py +637 -0
  66. foundry_mcp/core/providers/cursor_agent.py +630 -0
  67. foundry_mcp/core/providers/detectors.py +515 -0
  68. foundry_mcp/core/providers/gemini.py +426 -0
  69. foundry_mcp/core/providers/opencode.py +718 -0
  70. foundry_mcp/core/providers/opencode_wrapper.js +308 -0
  71. foundry_mcp/core/providers/package-lock.json +24 -0
  72. foundry_mcp/core/providers/package.json +25 -0
  73. foundry_mcp/core/providers/registry.py +607 -0
  74. foundry_mcp/core/providers/test_provider.py +171 -0
  75. foundry_mcp/core/providers/validation.py +857 -0
  76. foundry_mcp/core/rate_limit.py +427 -0
  77. foundry_mcp/core/research/__init__.py +68 -0
  78. foundry_mcp/core/research/memory.py +528 -0
  79. foundry_mcp/core/research/models.py +1234 -0
  80. foundry_mcp/core/research/providers/__init__.py +40 -0
  81. foundry_mcp/core/research/providers/base.py +242 -0
  82. foundry_mcp/core/research/providers/google.py +507 -0
  83. foundry_mcp/core/research/providers/perplexity.py +442 -0
  84. foundry_mcp/core/research/providers/semantic_scholar.py +544 -0
  85. foundry_mcp/core/research/providers/tavily.py +383 -0
  86. foundry_mcp/core/research/workflows/__init__.py +25 -0
  87. foundry_mcp/core/research/workflows/base.py +298 -0
  88. foundry_mcp/core/research/workflows/chat.py +271 -0
  89. foundry_mcp/core/research/workflows/consensus.py +539 -0
  90. foundry_mcp/core/research/workflows/deep_research.py +4142 -0
  91. foundry_mcp/core/research/workflows/ideate.py +682 -0
  92. foundry_mcp/core/research/workflows/thinkdeep.py +405 -0
  93. foundry_mcp/core/resilience.py +600 -0
  94. foundry_mcp/core/responses.py +1624 -0
  95. foundry_mcp/core/review.py +366 -0
  96. foundry_mcp/core/security.py +438 -0
  97. foundry_mcp/core/spec.py +4119 -0
  98. foundry_mcp/core/task.py +2463 -0
  99. foundry_mcp/core/testing.py +839 -0
  100. foundry_mcp/core/validation.py +2357 -0
  101. foundry_mcp/dashboard/__init__.py +32 -0
  102. foundry_mcp/dashboard/app.py +119 -0
  103. foundry_mcp/dashboard/components/__init__.py +17 -0
  104. foundry_mcp/dashboard/components/cards.py +88 -0
  105. foundry_mcp/dashboard/components/charts.py +177 -0
  106. foundry_mcp/dashboard/components/filters.py +136 -0
  107. foundry_mcp/dashboard/components/tables.py +195 -0
  108. foundry_mcp/dashboard/data/__init__.py +11 -0
  109. foundry_mcp/dashboard/data/stores.py +433 -0
  110. foundry_mcp/dashboard/launcher.py +300 -0
  111. foundry_mcp/dashboard/views/__init__.py +12 -0
  112. foundry_mcp/dashboard/views/errors.py +217 -0
  113. foundry_mcp/dashboard/views/metrics.py +164 -0
  114. foundry_mcp/dashboard/views/overview.py +96 -0
  115. foundry_mcp/dashboard/views/providers.py +83 -0
  116. foundry_mcp/dashboard/views/sdd_workflow.py +255 -0
  117. foundry_mcp/dashboard/views/tool_usage.py +139 -0
  118. foundry_mcp/prompts/__init__.py +9 -0
  119. foundry_mcp/prompts/workflows.py +525 -0
  120. foundry_mcp/resources/__init__.py +9 -0
  121. foundry_mcp/resources/specs.py +591 -0
  122. foundry_mcp/schemas/__init__.py +38 -0
  123. foundry_mcp/schemas/intake-schema.json +89 -0
  124. foundry_mcp/schemas/sdd-spec-schema.json +414 -0
  125. foundry_mcp/server.py +150 -0
  126. foundry_mcp/tools/__init__.py +10 -0
  127. foundry_mcp/tools/unified/__init__.py +92 -0
  128. foundry_mcp/tools/unified/authoring.py +3620 -0
  129. foundry_mcp/tools/unified/context_helpers.py +98 -0
  130. foundry_mcp/tools/unified/documentation_helpers.py +268 -0
  131. foundry_mcp/tools/unified/environment.py +1341 -0
  132. foundry_mcp/tools/unified/error.py +479 -0
  133. foundry_mcp/tools/unified/health.py +225 -0
  134. foundry_mcp/tools/unified/journal.py +841 -0
  135. foundry_mcp/tools/unified/lifecycle.py +640 -0
  136. foundry_mcp/tools/unified/metrics.py +777 -0
  137. foundry_mcp/tools/unified/plan.py +876 -0
  138. foundry_mcp/tools/unified/pr.py +294 -0
  139. foundry_mcp/tools/unified/provider.py +589 -0
  140. foundry_mcp/tools/unified/research.py +1283 -0
  141. foundry_mcp/tools/unified/review.py +1042 -0
  142. foundry_mcp/tools/unified/review_helpers.py +314 -0
  143. foundry_mcp/tools/unified/router.py +102 -0
  144. foundry_mcp/tools/unified/server.py +565 -0
  145. foundry_mcp/tools/unified/spec.py +1283 -0
  146. foundry_mcp/tools/unified/task.py +3846 -0
  147. foundry_mcp/tools/unified/test.py +431 -0
  148. foundry_mcp/tools/unified/verification.py +520 -0
  149. foundry_mcp-0.8.22.dist-info/METADATA +344 -0
  150. foundry_mcp-0.8.22.dist-info/RECORD +153 -0
  151. foundry_mcp-0.8.22.dist-info/WHEEL +4 -0
  152. foundry_mcp-0.8.22.dist-info/entry_points.txt +3 -0
  153. foundry_mcp-0.8.22.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,540 @@
1
+ """Unified context management for request correlation and distributed tracing.
2
+
3
+ This module provides a single source of truth for request context propagation
4
+ across the foundry-mcp codebase, including:
5
+
6
+ - Correlation ID generation and propagation
7
+ - W3C Trace Context (traceparent/tracestate) support
8
+ - Thread-safe context variables via contextvars
9
+ - Both async and sync context managers
10
+
11
+ Usage:
12
+ from foundry_mcp.core.context import (
13
+ request_context,
14
+ sync_request_context,
15
+ get_correlation_id,
16
+ get_current_context,
17
+ generate_correlation_id,
18
+ )
19
+
20
+ # Async usage
21
+ async with request_context(client_id="user123") as ctx:
22
+ print(ctx.correlation_id) # e.g., "req_a1b2c3d4e5f6"
23
+
24
+ # Sync usage
25
+ with sync_request_context() as ctx:
26
+ print(ctx.correlation_id)
27
+
28
+ # Manual ID generation
29
+ corr_id = generate_correlation_id(prefix="task") # "task_a1b2c3d4e5f6"
30
+ """
31
+
32
+ from __future__ import annotations
33
+
34
+ import re
35
+ import secrets
36
+ import time
37
+ from contextlib import contextmanager
38
+ from contextvars import ContextVar
39
+ from dataclasses import dataclass, field
40
+ from typing import Any, Dict, Generator, Optional
41
+
42
+ __all__ = [
43
+ # Context variables (for advanced use)
44
+ "correlation_id_var",
45
+ "client_id_var",
46
+ "start_time_var",
47
+ "trace_context_var",
48
+ # Dataclasses
49
+ "W3CTraceContext",
50
+ "RequestContext",
51
+ # ID generation
52
+ "generate_correlation_id",
53
+ # Context managers
54
+ "request_context",
55
+ "sync_request_context",
56
+ # Accessors
57
+ "get_correlation_id",
58
+ "get_client_id",
59
+ "get_start_time",
60
+ "get_trace_context",
61
+ "get_current_context",
62
+ # W3C helpers
63
+ "parse_traceparent",
64
+ "format_traceparent",
65
+ ]
66
+
67
+ # -----------------------------------------------------------------------------
68
+ # Context Variables
69
+ # -----------------------------------------------------------------------------
70
+
71
+ correlation_id_var: ContextVar[str] = ContextVar("correlation_id", default="")
72
+ """Request correlation ID for tracing requests across components."""
73
+
74
+ client_id_var: ContextVar[str] = ContextVar("client_id", default="anonymous")
75
+ """Identifier for the client making the request."""
76
+
77
+ start_time_var: ContextVar[float] = ContextVar("start_time", default=0.0)
78
+ """Request start time as Unix timestamp."""
79
+
80
+ trace_context_var: ContextVar[Optional["W3CTraceContext"]] = ContextVar(
81
+ "trace_context", default=None
82
+ )
83
+ """W3C Trace Context for distributed tracing integration."""
84
+
85
+
86
+ # -----------------------------------------------------------------------------
87
+ # W3C Trace Context Support
88
+ # -----------------------------------------------------------------------------
89
+
90
+ # W3C traceparent format: version-trace_id-parent_id-flags
91
+ # Example: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01
92
+ _TRACEPARENT_REGEX = re.compile(
93
+ r"^(?P<version>[0-9a-f]{2})-"
94
+ r"(?P<trace_id>[0-9a-f]{32})-"
95
+ r"(?P<parent_id>[0-9a-f]{16})-"
96
+ r"(?P<flags>[0-9a-f]{2})$"
97
+ )
98
+
99
+
100
+ @dataclass(frozen=True)
101
+ class W3CTraceContext:
102
+ """W3C Trace Context representation for distributed tracing.
103
+
104
+ Implements the W3C Trace Context specification:
105
+ https://www.w3.org/TR/trace-context/
106
+
107
+ Attributes:
108
+ version: Trace context version (currently "00")
109
+ trace_id: 32-character hex trace identifier
110
+ parent_id: 16-character hex span/parent identifier
111
+ flags: 2-character hex flags (01 = sampled)
112
+ tracestate: Optional vendor-specific trace state
113
+ """
114
+
115
+ version: str = "00"
116
+ trace_id: str = ""
117
+ parent_id: str = ""
118
+ flags: str = "00"
119
+ tracestate: Optional[str] = None
120
+
121
+ @classmethod
122
+ def parse(
123
+ cls,
124
+ traceparent: Optional[str] = None,
125
+ tracestate: Optional[str] = None,
126
+ ) -> Optional["W3CTraceContext"]:
127
+ """Parse W3C traceparent and tracestate headers.
128
+
129
+ Args:
130
+ traceparent: The traceparent header value
131
+ tracestate: Optional tracestate header value
132
+
133
+ Returns:
134
+ Parsed W3CTraceContext or None if parsing fails
135
+ """
136
+ if not traceparent:
137
+ return None
138
+
139
+ match = _TRACEPARENT_REGEX.match(traceparent.lower().strip())
140
+ if not match:
141
+ return None
142
+
143
+ return cls(
144
+ version=match.group("version"),
145
+ trace_id=match.group("trace_id"),
146
+ parent_id=match.group("parent_id"),
147
+ flags=match.group("flags"),
148
+ tracestate=tracestate,
149
+ )
150
+
151
+ @classmethod
152
+ def generate(cls, sampled: bool = True) -> "W3CTraceContext":
153
+ """Generate a new W3C Trace Context.
154
+
155
+ Args:
156
+ sampled: Whether this trace should be sampled
157
+
158
+ Returns:
159
+ New W3CTraceContext with generated IDs
160
+ """
161
+ return cls(
162
+ version="00",
163
+ trace_id=secrets.token_hex(16), # 32 hex chars
164
+ parent_id=secrets.token_hex(8), # 16 hex chars
165
+ flags="01" if sampled else "00",
166
+ )
167
+
168
+ @property
169
+ def is_sampled(self) -> bool:
170
+ """Check if trace is sampled based on flags."""
171
+ try:
172
+ return (int(self.flags, 16) & 0x01) == 0x01
173
+ except ValueError:
174
+ return False
175
+
176
+ @property
177
+ def traceparent(self) -> str:
178
+ """Format as W3C traceparent header value."""
179
+ return f"{self.version}-{self.trace_id}-{self.parent_id}-{self.flags}"
180
+
181
+ def with_new_parent(self, sampled: Optional[bool] = None) -> "W3CTraceContext":
182
+ """Create child context with new parent_id, preserving trace_id.
183
+
184
+ Args:
185
+ sampled: Override sampling decision (None = inherit)
186
+
187
+ Returns:
188
+ New W3CTraceContext with same trace_id but new parent_id
189
+ """
190
+ flags = self.flags
191
+ if sampled is not None:
192
+ flags = "01" if sampled else "00"
193
+
194
+ return W3CTraceContext(
195
+ version=self.version,
196
+ trace_id=self.trace_id,
197
+ parent_id=secrets.token_hex(8),
198
+ flags=flags,
199
+ tracestate=self.tracestate,
200
+ )
201
+
202
+ def to_dict(self) -> Dict[str, Any]:
203
+ """Convert to dictionary for serialization."""
204
+ result = {
205
+ "version": self.version,
206
+ "trace_id": self.trace_id,
207
+ "parent_id": self.parent_id,
208
+ "flags": self.flags,
209
+ "sampled": self.is_sampled,
210
+ }
211
+ if self.tracestate:
212
+ result["tracestate"] = self.tracestate
213
+ return result
214
+
215
+
216
+ def parse_traceparent(header: Optional[str]) -> Optional[W3CTraceContext]:
217
+ """Parse a traceparent header into W3CTraceContext.
218
+
219
+ Args:
220
+ header: The traceparent header value
221
+
222
+ Returns:
223
+ Parsed context or None
224
+ """
225
+ return W3CTraceContext.parse(header)
226
+
227
+
228
+ def format_traceparent(ctx: W3CTraceContext) -> str:
229
+ """Format W3CTraceContext as traceparent header.
230
+
231
+ Args:
232
+ ctx: The trace context
233
+
234
+ Returns:
235
+ Formatted traceparent header value
236
+ """
237
+ return ctx.traceparent
238
+
239
+
240
+ # -----------------------------------------------------------------------------
241
+ # Correlation ID Generation
242
+ # -----------------------------------------------------------------------------
243
+
244
+
245
+ def generate_correlation_id(prefix: str = "req") -> str:
246
+ """Generate a unique correlation ID with optional prefix.
247
+
248
+ Format: {prefix}_{12_hex_chars}
249
+ Example: "req_a1b2c3d4e5f6"
250
+
251
+ Args:
252
+ prefix: ID prefix (default: "req")
253
+
254
+ Returns:
255
+ Unique correlation ID string
256
+ """
257
+ return f"{prefix}_{secrets.token_hex(6)}"
258
+
259
+
260
+ # -----------------------------------------------------------------------------
261
+ # Request Context
262
+ # -----------------------------------------------------------------------------
263
+
264
+
265
+ @dataclass
266
+ class RequestContext:
267
+ """Immutable snapshot of the current request context.
268
+
269
+ This dataclass captures all context variables at a point in time,
270
+ providing convenient access to timing information and serialization.
271
+
272
+ Attributes:
273
+ correlation_id: Unique request identifier
274
+ client_id: Client/user identifier
275
+ start_time: Request start timestamp
276
+ trace_context: Optional W3C distributed tracing context
277
+ """
278
+
279
+ correlation_id: str = ""
280
+ client_id: str = "anonymous"
281
+ start_time: float = field(default_factory=time.time)
282
+ trace_context: Optional[W3CTraceContext] = None
283
+
284
+ @property
285
+ def elapsed_seconds(self) -> float:
286
+ """Calculate elapsed time since request start in seconds."""
287
+ if self.start_time <= 0:
288
+ return 0.0
289
+ return time.time() - self.start_time
290
+
291
+ @property
292
+ def elapsed_ms(self) -> float:
293
+ """Calculate elapsed time since request start in milliseconds."""
294
+ return self.elapsed_seconds * 1000
295
+
296
+ def to_dict(self) -> Dict[str, Any]:
297
+ """Convert context to dictionary for logging/serialization.
298
+
299
+ Returns:
300
+ Dictionary with all context fields
301
+ """
302
+ result: Dict[str, Any] = {
303
+ "correlation_id": self.correlation_id,
304
+ "client_id": self.client_id,
305
+ "start_time": self.start_time,
306
+ "elapsed_ms": round(self.elapsed_ms, 2),
307
+ }
308
+ if self.trace_context:
309
+ result["trace"] = self.trace_context.to_dict()
310
+ return result
311
+
312
+
313
+ # -----------------------------------------------------------------------------
314
+ # Context Managers
315
+ # -----------------------------------------------------------------------------
316
+
317
+
318
+ @contextmanager
319
+ def sync_request_context(
320
+ *,
321
+ correlation_id: Optional[str] = None,
322
+ client_id: Optional[str] = None,
323
+ traceparent: Optional[str] = None,
324
+ tracestate: Optional[str] = None,
325
+ ) -> Generator[RequestContext, None, None]:
326
+ """Synchronous context manager for request context.
327
+
328
+ Sets up context variables for the duration of the with block,
329
+ automatically cleaning up on exit.
330
+
331
+ Args:
332
+ correlation_id: Request ID (auto-generated if None)
333
+ client_id: Client identifier (default: "anonymous")
334
+ traceparent: W3C traceparent header for distributed tracing
335
+ tracestate: W3C tracestate header
336
+
337
+ Yields:
338
+ RequestContext snapshot
339
+
340
+ Example:
341
+ with sync_request_context(client_id="user123") as ctx:
342
+ logger.info(f"Processing request {ctx.correlation_id}")
343
+ """
344
+ # Generate or use provided correlation ID
345
+ corr_id = correlation_id or generate_correlation_id()
346
+ client = client_id or "anonymous"
347
+ start = time.time()
348
+
349
+ # Parse trace context if provided
350
+ trace_ctx = W3CTraceContext.parse(traceparent, tracestate)
351
+
352
+ # Set context variables
353
+ token_corr = correlation_id_var.set(corr_id)
354
+ token_client = client_id_var.set(client)
355
+ token_start = start_time_var.set(start)
356
+ token_trace = trace_context_var.set(trace_ctx)
357
+
358
+ try:
359
+ yield RequestContext(
360
+ correlation_id=corr_id,
361
+ client_id=client,
362
+ start_time=start,
363
+ trace_context=trace_ctx,
364
+ )
365
+ finally:
366
+ # Reset context variables
367
+ correlation_id_var.reset(token_corr)
368
+ client_id_var.reset(token_client)
369
+ start_time_var.reset(token_start)
370
+ trace_context_var.reset(token_trace)
371
+
372
+
373
+ async def request_context(
374
+ *,
375
+ correlation_id: Optional[str] = None,
376
+ client_id: Optional[str] = None,
377
+ traceparent: Optional[str] = None,
378
+ tracestate: Optional[str] = None,
379
+ ) -> RequestContext:
380
+ """Async context manager for request context.
381
+
382
+ This is an async generator that can be used with `async with`.
383
+ Sets up context variables for the duration of the async with block.
384
+
385
+ Args:
386
+ correlation_id: Request ID (auto-generated if None)
387
+ client_id: Client identifier (default: "anonymous")
388
+ traceparent: W3C traceparent header for distributed tracing
389
+ tracestate: W3C tracestate header
390
+
391
+ Returns:
392
+ RequestContext snapshot (use with async context manager)
393
+
394
+ Example:
395
+ async with request_context(client_id="user123") as ctx:
396
+ logger.info(f"Processing request {ctx.correlation_id}")
397
+ """
398
+ # This uses the sync implementation since contextvars work across
399
+ # sync/async boundaries in Python 3.7+
400
+ with sync_request_context(
401
+ correlation_id=correlation_id,
402
+ client_id=client_id,
403
+ traceparent=traceparent,
404
+ tracestate=tracestate,
405
+ ) as ctx:
406
+ return ctx
407
+
408
+
409
+ # Make request_context work as both async and sync context manager
410
+ class _AsyncContextManager:
411
+ """Wrapper to make request_context work as async context manager."""
412
+
413
+ def __init__(
414
+ self,
415
+ correlation_id: Optional[str] = None,
416
+ client_id: Optional[str] = None,
417
+ traceparent: Optional[str] = None,
418
+ tracestate: Optional[str] = None,
419
+ ):
420
+ self.correlation_id = correlation_id
421
+ self.client_id = client_id
422
+ self.traceparent = traceparent
423
+ self.tracestate = tracestate
424
+ self._sync_cm: Optional[Generator[RequestContext, None, None]] = None
425
+ self._ctx: Optional[RequestContext] = None
426
+
427
+ async def __aenter__(self) -> RequestContext:
428
+ self._sync_cm = sync_request_context(
429
+ correlation_id=self.correlation_id,
430
+ client_id=self.client_id,
431
+ traceparent=self.traceparent,
432
+ tracestate=self.tracestate,
433
+ )
434
+ self._ctx = self._sync_cm.__enter__()
435
+ return self._ctx
436
+
437
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
438
+ if self._sync_cm:
439
+ self._sync_cm.__exit__(exc_type, exc_val, exc_tb)
440
+ return False
441
+
442
+
443
+ def async_request_context(
444
+ *,
445
+ correlation_id: Optional[str] = None,
446
+ client_id: Optional[str] = None,
447
+ traceparent: Optional[str] = None,
448
+ tracestate: Optional[str] = None,
449
+ ) -> _AsyncContextManager:
450
+ """Create an async context manager for request context.
451
+
452
+ Args:
453
+ correlation_id: Request ID (auto-generated if None)
454
+ client_id: Client identifier (default: "anonymous")
455
+ traceparent: W3C traceparent header for distributed tracing
456
+ tracestate: W3C tracestate header
457
+
458
+ Returns:
459
+ Async context manager yielding RequestContext
460
+
461
+ Example:
462
+ async with async_request_context(client_id="user123") as ctx:
463
+ await some_async_operation()
464
+ logger.info(f"Completed request {ctx.correlation_id}")
465
+ """
466
+ return _AsyncContextManager(
467
+ correlation_id=correlation_id,
468
+ client_id=client_id,
469
+ traceparent=traceparent,
470
+ tracestate=tracestate,
471
+ )
472
+
473
+
474
+ # -----------------------------------------------------------------------------
475
+ # Context Accessors
476
+ # -----------------------------------------------------------------------------
477
+
478
+
479
+ def get_correlation_id() -> str:
480
+ """Get the current correlation ID from context.
481
+
482
+ Returns:
483
+ Current correlation ID or empty string if not set
484
+ """
485
+ return correlation_id_var.get()
486
+
487
+
488
+ def get_client_id() -> str:
489
+ """Get the current client ID from context.
490
+
491
+ Returns:
492
+ Current client ID or "anonymous" if not set
493
+ """
494
+ return client_id_var.get()
495
+
496
+
497
+ def get_start_time() -> float:
498
+ """Get the request start time from context.
499
+
500
+ Returns:
501
+ Start time as Unix timestamp or 0.0 if not set
502
+ """
503
+ return start_time_var.get()
504
+
505
+
506
+ def get_trace_context() -> Optional[W3CTraceContext]:
507
+ """Get the W3C trace context if set.
508
+
509
+ Returns:
510
+ Current W3CTraceContext or None
511
+ """
512
+ return trace_context_var.get()
513
+
514
+
515
+ def get_current_context() -> RequestContext:
516
+ """Get a snapshot of all current context values.
517
+
518
+ Returns:
519
+ RequestContext with current values from context variables
520
+ """
521
+ return RequestContext(
522
+ correlation_id=get_correlation_id(),
523
+ client_id=get_client_id(),
524
+ start_time=get_start_time(),
525
+ trace_context=get_trace_context(),
526
+ )
527
+
528
+
529
+ # -----------------------------------------------------------------------------
530
+ # Backward Compatibility Aliases
531
+ # -----------------------------------------------------------------------------
532
+
533
+ # For backward compatibility with concurrency.py
534
+ request_id = correlation_id_var
535
+ """Alias for correlation_id_var (backward compatibility)."""
536
+
537
+
538
+ def get_request_id() -> str:
539
+ """Alias for get_correlation_id() (backward compatibility)."""
540
+ return get_correlation_id()