revenium-python-sdk 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.
- revenium_middleware/__init__.py +184 -0
- revenium_middleware/_core/__init__.py +65 -0
- revenium_middleware/_core/config.py +165 -0
- revenium_middleware/_core/context.py +109 -0
- revenium_middleware/_core/decorators.py +202 -0
- revenium_middleware/_core/metering.py +207 -0
- revenium_middleware/_core/prompt_extraction.py +55 -0
- revenium_middleware/_core/subscriber.py +51 -0
- revenium_middleware/_core/trace_fields.py +265 -0
- revenium_middleware/anthropic/__init__.py +108 -0
- revenium_middleware/anthropic/bedrock_adapter.py +753 -0
- revenium_middleware/anthropic/config.py +29 -0
- revenium_middleware/anthropic/middleware.py +1070 -0
- revenium_middleware/anthropic/prompt_extractor.py +178 -0
- revenium_middleware/anthropic/provider.py +141 -0
- revenium_middleware/anthropic/summary_printer.py +286 -0
- revenium_middleware/anthropic/trace_fields.py +158 -0
- revenium_middleware/google/__init__.py +114 -0
- revenium_middleware/google/common/__init__.py +127 -0
- revenium_middleware/google/common/exceptions.py +137 -0
- revenium_middleware/google/common/protocols.py +192 -0
- revenium_middleware/google/common/summary_printer.py +271 -0
- revenium_middleware/google/common/trace_fields.py +205 -0
- revenium_middleware/google/common/types.py +208 -0
- revenium_middleware/google/common/utils.py +1111 -0
- revenium_middleware/google/config.py +64 -0
- revenium_middleware/google/google_ai/__init__.py +53 -0
- revenium_middleware/google/google_ai/middleware.py +667 -0
- revenium_middleware/google/google_ai/provider.py +135 -0
- revenium_middleware/google/prompt_extractor.py +396 -0
- revenium_middleware/google/vertex_ai/__init__.py +56 -0
- revenium_middleware/google/vertex_ai/middleware.py +1162 -0
- revenium_middleware/google/vertex_ai/provider.py +99 -0
- revenium_middleware/litellm/__init__.py +25 -0
- revenium_middleware/litellm/client/__init__.py +81 -0
- revenium_middleware/litellm/client/config.py +53 -0
- revenium_middleware/litellm/client/context.py +198 -0
- revenium_middleware/litellm/client/decorators.py +912 -0
- revenium_middleware/litellm/client/hooks.py +192 -0
- revenium_middleware/litellm/client/integrations/__init__.py +26 -0
- revenium_middleware/litellm/client/integrations/crewai.py +446 -0
- revenium_middleware/litellm/client/middleware.py +321 -0
- revenium_middleware/litellm/client/summary_printer.py +314 -0
- revenium_middleware/litellm/client/trace_fields.py +51 -0
- revenium_middleware/litellm/client/validation.py +207 -0
- revenium_middleware/litellm/proxy/__init__.py +25 -0
- revenium_middleware/litellm/proxy/middleware.py +217 -0
- revenium_middleware/ollama/__init__.py +28 -0
- revenium_middleware/ollama/middleware.py +569 -0
- revenium_middleware/ollama/trace_fields.py +63 -0
- revenium_middleware/openai/__init__.py +23 -0
- revenium_middleware/openai/azure_config.py +169 -0
- revenium_middleware/openai/azure_model_resolver.py +219 -0
- revenium_middleware/openai/config.py +45 -0
- revenium_middleware/openai/exceptions.py +115 -0
- revenium_middleware/openai/langchain/__init__.py +114 -0
- revenium_middleware/openai/langchain/_utils.py +129 -0
- revenium_middleware/openai/langchain/unified_handler.py +526 -0
- revenium_middleware/openai/middleware.py +1451 -0
- revenium_middleware/openai/prompt_extractor.py +173 -0
- revenium_middleware/openai/provider.py +170 -0
- revenium_middleware/openai/summary_printer.py +292 -0
- revenium_middleware/openai/trace_fields.py +98 -0
- revenium_middleware/perplexity/__init__.py +97 -0
- revenium_middleware/perplexity/middleware.py +379 -0
- revenium_middleware/perplexity/perplexity_sdk.py +256 -0
- revenium_middleware/perplexity/provider.py +84 -0
- revenium_middleware/perplexity/trace_fields.py +25 -0
- revenium_python_sdk-0.1.0.dist-info/METADATA +252 -0
- revenium_python_sdk-0.1.0.dist-info/RECORD +73 -0
- revenium_python_sdk-0.1.0.dist-info/WHEEL +5 -0
- revenium_python_sdk-0.1.0.dist-info/licenses/LICENSE +21 -0
- revenium_python_sdk-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Provider detection and configuration for Vertex AI SDK support.
|
|
3
|
+
|
|
4
|
+
This module handles provider detection for the native Vertex AI SDK (vertexai package).
|
|
5
|
+
All Vertex AI SDK usage reports as "Google" provider for unified analytics.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import logging
|
|
10
|
+
from ..common import ProviderMetadata
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger("revenium_middleware.extension")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def detect_provider() -> str:
|
|
16
|
+
"""
|
|
17
|
+
Detect Vertex AI provider configuration.
|
|
18
|
+
|
|
19
|
+
For the native Vertex AI SDK, we always return "vertex_ai" since
|
|
20
|
+
this module only handles the vertexai package.
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
Always returns "vertex_ai" for this module
|
|
24
|
+
"""
|
|
25
|
+
logger.debug("Vertex AI SDK provider detected")
|
|
26
|
+
return "vertex_ai"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def get_provider_metadata() -> ProviderMetadata:
|
|
30
|
+
"""
|
|
31
|
+
Get provider metadata for Vertex AI SDK usage records.
|
|
32
|
+
|
|
33
|
+
Returns standardized "Google" provider name for unified analytics.
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
ProviderMetadata with "Google" provider name
|
|
37
|
+
"""
|
|
38
|
+
return ProviderMetadata.for_vertex_ai_sdk()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def validate_vertex_ai_configuration() -> bool:
|
|
42
|
+
"""
|
|
43
|
+
Validate that Vertex AI is properly configured.
|
|
44
|
+
|
|
45
|
+
Checks for required environment variables and configuration.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
True if Vertex AI is properly configured, False otherwise
|
|
49
|
+
"""
|
|
50
|
+
# Check for required Vertex AI configuration
|
|
51
|
+
project_id = os.getenv("GOOGLE_CLOUD_PROJECT")
|
|
52
|
+
location = os.getenv("GOOGLE_CLOUD_LOCATION")
|
|
53
|
+
|
|
54
|
+
if not project_id:
|
|
55
|
+
logger.warning(
|
|
56
|
+
"GOOGLE_CLOUD_PROJECT environment variable not set for Vertex AI"
|
|
57
|
+
)
|
|
58
|
+
return False
|
|
59
|
+
|
|
60
|
+
if not location:
|
|
61
|
+
logger.warning(
|
|
62
|
+
"GOOGLE_CLOUD_LOCATION environment variable not set for Vertex AI"
|
|
63
|
+
)
|
|
64
|
+
return False
|
|
65
|
+
|
|
66
|
+
logger.debug(
|
|
67
|
+
f"Vertex AI configuration validated: project={project_id}, location={location}"
|
|
68
|
+
)
|
|
69
|
+
return True
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def get_vertex_ai_config() -> dict:
|
|
73
|
+
"""
|
|
74
|
+
Get Vertex AI configuration from environment variables.
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
Dictionary with Vertex AI configuration
|
|
78
|
+
"""
|
|
79
|
+
return {
|
|
80
|
+
"project_id": os.getenv("GOOGLE_CLOUD_PROJECT"),
|
|
81
|
+
"location": os.getenv("GOOGLE_CLOUD_LOCATION", "us-central1"),
|
|
82
|
+
"credentials_path": os.getenv("GOOGLE_APPLICATION_CREDENTIALS"),
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def is_vertex_ai_available() -> bool:
|
|
87
|
+
"""
|
|
88
|
+
Check if Vertex AI SDK is available and properly configured.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
True if Vertex AI SDK is available and configured, False otherwise
|
|
92
|
+
"""
|
|
93
|
+
try:
|
|
94
|
+
import vertexai
|
|
95
|
+
|
|
96
|
+
return validate_vertex_ai_configuration()
|
|
97
|
+
except ImportError:
|
|
98
|
+
logger.debug("Vertex AI SDK not available")
|
|
99
|
+
return False
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Revenium Middleware for LiteLLM.
|
|
3
|
+
|
|
4
|
+
Provides both client-side (wrapt-based) and proxy-side (custom logger) middleware
|
|
5
|
+
for metering LiteLLM usage.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
# Conditionally import subpackages
|
|
13
|
+
try:
|
|
14
|
+
import litellm as _litellm # noqa: F401
|
|
15
|
+
from . import client
|
|
16
|
+
from . import proxy
|
|
17
|
+
except ImportError:
|
|
18
|
+
logger.debug("LiteLLM SDK (litellm) not available, middleware not loaded")
|
|
19
|
+
client = None # type: ignore
|
|
20
|
+
proxy = None # type: ignore
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
"client",
|
|
24
|
+
"proxy",
|
|
25
|
+
]
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Revenium LiteLLM Client Middleware
|
|
3
|
+
|
|
4
|
+
When you install and import this library, it will automatically hook
|
|
5
|
+
litellm.completion using wrapt, and log token usage after each request.
|
|
6
|
+
|
|
7
|
+
New in v0.2.0:
|
|
8
|
+
- Context-based metadata injection via metadata_context
|
|
9
|
+
- Type-safe validation with UsageMetadata (requires pydantic)
|
|
10
|
+
- Decorator-based metadata injection (8 decorators available)
|
|
11
|
+
- Framework integrations (CrewAI integration available)
|
|
12
|
+
|
|
13
|
+
Basic Usage:
|
|
14
|
+
>>> import revenium_middleware.litellm.client.middleware
|
|
15
|
+
>>> import litellm
|
|
16
|
+
>>> response = litellm.completion(
|
|
17
|
+
... model="gpt-4",
|
|
18
|
+
... messages=[{"role": "user", "content": "Hello"}],
|
|
19
|
+
... usage_metadata={"agent": "my-agent"}
|
|
20
|
+
... )
|
|
21
|
+
|
|
22
|
+
Context-Based Usage:
|
|
23
|
+
>>> from revenium_middleware.litellm.client import metadata_context
|
|
24
|
+
>>> with metadata_context.set(agent="Lead Analyst", task_type="research"):
|
|
25
|
+
... response = litellm.completion(...) # Metadata auto-injected
|
|
26
|
+
"""
|
|
27
|
+
import logging
|
|
28
|
+
|
|
29
|
+
logger = logging.getLogger(__name__)
|
|
30
|
+
|
|
31
|
+
# Import components that don't require litellm SDK
|
|
32
|
+
from .context import metadata_context, MetadataContext
|
|
33
|
+
from .validation import UsageMetadata, Subscriber, SubscriberCredential, PYDANTIC_AVAILABLE
|
|
34
|
+
from .decorators import (
|
|
35
|
+
track_agent,
|
|
36
|
+
track_task,
|
|
37
|
+
track_trace,
|
|
38
|
+
track_organization,
|
|
39
|
+
track_subscription,
|
|
40
|
+
track_product,
|
|
41
|
+
track_subscriber,
|
|
42
|
+
track_quality
|
|
43
|
+
)
|
|
44
|
+
from .hooks import (
|
|
45
|
+
register_metadata_hook,
|
|
46
|
+
unregister_metadata_hook,
|
|
47
|
+
clear_metadata_hooks,
|
|
48
|
+
get_registered_hooks
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
# Conditionally import middleware (requires litellm SDK)
|
|
52
|
+
try:
|
|
53
|
+
import litellm as _litellm # noqa: F401
|
|
54
|
+
from .middleware import completion_wrapper
|
|
55
|
+
except ImportError:
|
|
56
|
+
logger.debug("LiteLLM SDK (litellm) not available, middleware not loaded")
|
|
57
|
+
completion_wrapper = None # type: ignore
|
|
58
|
+
|
|
59
|
+
__version__ = "0.2.0"
|
|
60
|
+
|
|
61
|
+
__all__ = [
|
|
62
|
+
'completion_wrapper',
|
|
63
|
+
'metadata_context',
|
|
64
|
+
'MetadataContext',
|
|
65
|
+
'UsageMetadata',
|
|
66
|
+
'Subscriber',
|
|
67
|
+
'SubscriberCredential',
|
|
68
|
+
'PYDANTIC_AVAILABLE',
|
|
69
|
+
'track_agent',
|
|
70
|
+
'track_task',
|
|
71
|
+
'track_trace',
|
|
72
|
+
'track_organization',
|
|
73
|
+
'track_subscription',
|
|
74
|
+
'track_product',
|
|
75
|
+
'track_subscriber',
|
|
76
|
+
'track_quality',
|
|
77
|
+
'register_metadata_hook',
|
|
78
|
+
'unregister_metadata_hook',
|
|
79
|
+
'clear_metadata_hooks',
|
|
80
|
+
'get_registered_hooks',
|
|
81
|
+
]
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configuration for Revenium LiteLLM middleware.
|
|
3
|
+
|
|
4
|
+
Imports shared config from core and adds LiteLLM-specific settings.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
from revenium_middleware._core.config import ( # noqa: F401
|
|
11
|
+
Config as _CoreConfig,
|
|
12
|
+
SummaryFormat,
|
|
13
|
+
parse_print_summary_value,
|
|
14
|
+
get_print_summary_config,
|
|
15
|
+
get_team_id,
|
|
16
|
+
get_base_url,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
# Re-export environment variable names for backward compatibility
|
|
20
|
+
ENV_REVENIUM_PRINT_SUMMARY = _CoreConfig.ENV_REVENIUM_PRINT_SUMMARY
|
|
21
|
+
ENV_REVENIUM_TEAM_ID = _CoreConfig.ENV_REVENIUM_TEAM_ID
|
|
22
|
+
ENV_REVENIUM_METERING_BASE_URL = _CoreConfig.ENV_REVENIUM_BASE_URL
|
|
23
|
+
ENV_REVENIUM_METERING_API_KEY = _CoreConfig.ENV_REVENIUM_API_KEY
|
|
24
|
+
|
|
25
|
+
# Summary settings (LiteLLM uses a shorter retry delay)
|
|
26
|
+
SUMMARY_RETRY_ATTEMPTS = _CoreConfig.SUMMARY_RETRY_ATTEMPTS
|
|
27
|
+
SUMMARY_RETRY_DELAY: float = 1.0 # LiteLLM uses 1.0s vs core's 2.0s
|
|
28
|
+
SUMMARY_API_TIMEOUT = _CoreConfig.SUMMARY_API_TIMEOUT
|
|
29
|
+
|
|
30
|
+
DEFAULT_BASE_URL = _CoreConfig.DEFAULT_BASE_URL
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def get_api_key() -> Optional[str]:
|
|
34
|
+
"""Get the Revenium API key from environment."""
|
|
35
|
+
return os.getenv(ENV_REVENIUM_METERING_API_KEY)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
__all__ = [
|
|
39
|
+
'ENV_REVENIUM_PRINT_SUMMARY',
|
|
40
|
+
'ENV_REVENIUM_TEAM_ID',
|
|
41
|
+
'ENV_REVENIUM_METERING_BASE_URL',
|
|
42
|
+
'ENV_REVENIUM_METERING_API_KEY',
|
|
43
|
+
'SUMMARY_RETRY_ATTEMPTS',
|
|
44
|
+
'SUMMARY_RETRY_DELAY',
|
|
45
|
+
'SUMMARY_API_TIMEOUT',
|
|
46
|
+
'DEFAULT_BASE_URL',
|
|
47
|
+
'SummaryFormat',
|
|
48
|
+
'parse_print_summary_value',
|
|
49
|
+
'get_print_summary_config',
|
|
50
|
+
'get_team_id',
|
|
51
|
+
'get_base_url',
|
|
52
|
+
'get_api_key',
|
|
53
|
+
]
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Context-based metadata injection for Revenium LiteLLM middleware.
|
|
3
|
+
|
|
4
|
+
This module provides thread-safe context management for injecting metadata into
|
|
5
|
+
LiteLLM completion calls without requiring explicit kwargs. It uses Python's
|
|
6
|
+
contextvars module to maintain isolated context per execution thread/task.
|
|
7
|
+
|
|
8
|
+
Example:
|
|
9
|
+
>>> from revenium_middleware.litellm.client import metadata_context
|
|
10
|
+
>>>
|
|
11
|
+
>>> # Using context manager
|
|
12
|
+
>>> with metadata_context.set(agent="Lead Analyst", task_type="research"):
|
|
13
|
+
... response = litellm.completion(model="gpt-4", messages=[...])
|
|
14
|
+
... # Metadata automatically injected
|
|
15
|
+
>>>
|
|
16
|
+
>>> # Using direct API
|
|
17
|
+
>>> metadata_context.update(trace_id="abc-123")
|
|
18
|
+
>>> response = litellm.completion(model="gpt-4", messages=[...])
|
|
19
|
+
>>> metadata_context.clear()
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
import contextvars
|
|
23
|
+
from typing import Dict, Any, Optional
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# Thread-safe context variable for storing metadata
|
|
27
|
+
_metadata_context: contextvars.ContextVar[Dict[str, Any]] = contextvars.ContextVar(
|
|
28
|
+
'revenium_metadata',
|
|
29
|
+
default={}
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class MetadataContext:
|
|
34
|
+
"""
|
|
35
|
+
Thread-safe metadata context manager for Revenium middleware.
|
|
36
|
+
|
|
37
|
+
This class provides methods to set, get, update, and clear metadata that
|
|
38
|
+
will be automatically injected into LiteLLM completion calls. It uses
|
|
39
|
+
Python's contextvars module to ensure thread-safety and proper isolation
|
|
40
|
+
in async contexts.
|
|
41
|
+
|
|
42
|
+
All metadata set through this API will be merged with explicit usage_metadata
|
|
43
|
+
kwargs, with explicit kwargs taking precedence.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
@staticmethod
|
|
47
|
+
def set(**kwargs) -> '_MetadataContextManager':
|
|
48
|
+
"""
|
|
49
|
+
Set metadata for the current context using a context manager.
|
|
50
|
+
|
|
51
|
+
This method returns a context manager that sets the metadata on entry
|
|
52
|
+
and restores the previous metadata on exit. This is the recommended
|
|
53
|
+
way to set metadata for a specific scope.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
**kwargs: Metadata fields to set (e.g., agent="Lead Analyst",
|
|
57
|
+
task_type="research", trace_id="abc-123")
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
_MetadataContextManager: Context manager for scoped metadata
|
|
61
|
+
|
|
62
|
+
Example:
|
|
63
|
+
>>> with metadata_context.set(agent="Analyst", task_type="research"):
|
|
64
|
+
... # All LiteLLM calls here get this metadata
|
|
65
|
+
... response = litellm.completion(...)
|
|
66
|
+
"""
|
|
67
|
+
return _MetadataContextManager(kwargs)
|
|
68
|
+
|
|
69
|
+
@staticmethod
|
|
70
|
+
def get() -> Dict[str, Any]:
|
|
71
|
+
"""
|
|
72
|
+
Get the current context metadata.
|
|
73
|
+
|
|
74
|
+
Returns a copy of the current metadata dictionary. Modifying the
|
|
75
|
+
returned dictionary will not affect the context.
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
Dict[str, Any]: Copy of current metadata
|
|
79
|
+
|
|
80
|
+
Example:
|
|
81
|
+
>>> metadata_context.update(agent="Analyst")
|
|
82
|
+
>>> current = metadata_context.get()
|
|
83
|
+
>>> print(current)
|
|
84
|
+
{'agent': 'Analyst'}
|
|
85
|
+
"""
|
|
86
|
+
return _metadata_context.get().copy()
|
|
87
|
+
|
|
88
|
+
@staticmethod
|
|
89
|
+
def update(**kwargs) -> None:
|
|
90
|
+
"""
|
|
91
|
+
Update the current context metadata.
|
|
92
|
+
|
|
93
|
+
This merges the provided metadata with existing metadata in the
|
|
94
|
+
current context. Unlike set(), this does not use a context manager
|
|
95
|
+
and the metadata persists until explicitly cleared.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
**kwargs: Metadata fields to update
|
|
99
|
+
|
|
100
|
+
Example:
|
|
101
|
+
>>> metadata_context.update(agent="Analyst")
|
|
102
|
+
>>> metadata_context.update(task_type="research")
|
|
103
|
+
>>> # Both fields are now set
|
|
104
|
+
>>> metadata_context.clear() # Clean up when done
|
|
105
|
+
"""
|
|
106
|
+
current = _metadata_context.get().copy()
|
|
107
|
+
current.update(kwargs)
|
|
108
|
+
_metadata_context.set(current)
|
|
109
|
+
|
|
110
|
+
@staticmethod
|
|
111
|
+
def clear(*keys: str) -> None:
|
|
112
|
+
"""
|
|
113
|
+
Clear specific metadata fields or all metadata.
|
|
114
|
+
|
|
115
|
+
If no keys are provided, clears all metadata. If keys are provided,
|
|
116
|
+
only those specific fields are removed.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
*keys: Optional field names to clear. If not provided, clears all.
|
|
120
|
+
|
|
121
|
+
Example:
|
|
122
|
+
>>> metadata_context.update(agent="A", task_type="T", trace_id="123")
|
|
123
|
+
>>> metadata_context.clear("agent") # Remove only agent
|
|
124
|
+
>>> metadata_context.clear() # Remove all remaining
|
|
125
|
+
"""
|
|
126
|
+
if not keys:
|
|
127
|
+
# Clear all metadata
|
|
128
|
+
_metadata_context.set({})
|
|
129
|
+
else:
|
|
130
|
+
# Clear specific keys
|
|
131
|
+
current = _metadata_context.get().copy()
|
|
132
|
+
for key in keys:
|
|
133
|
+
current.pop(key, None)
|
|
134
|
+
_metadata_context.set(current)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class _MetadataContextManager:
|
|
138
|
+
"""
|
|
139
|
+
Context manager for scoped metadata injection.
|
|
140
|
+
|
|
141
|
+
This class implements the context manager protocol to provide scoped
|
|
142
|
+
metadata that is automatically cleaned up on exit. It saves the previous
|
|
143
|
+
metadata state on entry and restores it on exit.
|
|
144
|
+
|
|
145
|
+
This class should not be instantiated directly. Use MetadataContext.set()
|
|
146
|
+
instead.
|
|
147
|
+
"""
|
|
148
|
+
|
|
149
|
+
def __init__(self, metadata: Dict[str, Any]):
|
|
150
|
+
"""
|
|
151
|
+
Initialize the context manager.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
metadata: Metadata dictionary to set in this context
|
|
155
|
+
"""
|
|
156
|
+
self.metadata = metadata
|
|
157
|
+
self.token: Optional[contextvars.Token] = None
|
|
158
|
+
|
|
159
|
+
def __enter__(self) -> '_MetadataContextManager':
|
|
160
|
+
"""
|
|
161
|
+
Enter the context and set metadata.
|
|
162
|
+
|
|
163
|
+
Saves the current metadata state and sets the new metadata.
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
Self for use in with statements
|
|
167
|
+
"""
|
|
168
|
+
# Merge new metadata with existing context
|
|
169
|
+
current = _metadata_context.get().copy()
|
|
170
|
+
current.update(self.metadata)
|
|
171
|
+
self.token = _metadata_context.set(current)
|
|
172
|
+
return self
|
|
173
|
+
|
|
174
|
+
def __exit__(self, exc_type, exc_val, exc_tb) -> bool:
|
|
175
|
+
"""
|
|
176
|
+
Exit the context and restore previous metadata.
|
|
177
|
+
|
|
178
|
+
Restores the metadata state that existed before entering this context.
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
exc_type: Exception type if an exception occurred
|
|
182
|
+
exc_val: Exception value if an exception occurred
|
|
183
|
+
exc_tb: Exception traceback if an exception occurred
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
False to propagate any exception that occurred
|
|
187
|
+
"""
|
|
188
|
+
if self.token is not None:
|
|
189
|
+
_metadata_context.reset(self.token)
|
|
190
|
+
return False
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
# Global instance for convenient access
|
|
194
|
+
metadata_context = MetadataContext()
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
__all__ = ['MetadataContext', 'metadata_context']
|
|
198
|
+
|