logfire-api 4.35.0__tar.gz → 4.37.0__tar.gz
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.
- {logfire_api-4.35.0 → logfire_api-4.37.0}/PKG-INFO +1 -1
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/__init__.pyi +2 -1
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/config.pyi +12 -4
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/logs.pyi +2 -2
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/main.pyi +19 -10
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/metrics.pyi +2 -2
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/tracer.pyi +2 -2
- logfire_api-4.37.0/logfire_api/variables/__init__.pyi +6 -0
- logfire_api-4.37.0/logfire_api/variables/_handlebars.pyi +52 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/variables/abstract.pyi +36 -6
- logfire_api-4.37.0/logfire_api/variables/composition.pyi +102 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/variables/config.pyi +12 -5
- logfire_api-4.37.0/logfire_api/variables/template_validation.pyi +53 -0
- logfire_api-4.37.0/logfire_api/variables/variable.pyi +248 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/pyproject.toml +1 -1
- logfire_api-4.35.0/logfire_api/variables/__init__.pyi +0 -5
- logfire_api-4.35.0/logfire_api/variables/variable.pyi +0 -130
- {logfire_api-4.35.0 → logfire_api-4.37.0}/.gitignore +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/README.md +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/__init__.py +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/__init__.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/ast_utils.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/async_.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/auth.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/auto_trace/__init__.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/auto_trace/import_hook.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/auto_trace/rewrite_ast.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/auto_trace/types.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/baggage.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/cli/__init__.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/cli/ai_tools.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/cli/auth.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/cli/gateway.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/cli/gateway_auth.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/cli/prompt.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/cli/run.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/cli.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/client.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/collect_system_info.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/config_params.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/constants.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/db_statement_summary.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/exporters/__init__.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/exporters/console.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/exporters/dynamic_batch.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/exporters/logs.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/exporters/otlp.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/exporters/processor_wrapper.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/exporters/quiet_metrics.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/exporters/remove_pending.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/exporters/tail_sampling.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/exporters/wrapper.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/formatter.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/forwarding.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/instrument.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/integrations/__init__.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/integrations/aiohttp_client.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/integrations/aiohttp_server.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/integrations/asgi.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/integrations/asyncpg.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/integrations/aws_lambda.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/integrations/celery.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/integrations/claude_agent_sdk.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/integrations/django.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/integrations/dspy.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/integrations/executors.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/integrations/fastapi.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/integrations/flask.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/integrations/google_genai.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/integrations/httpx.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/integrations/litellm.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/integrations/mcp.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/integrations/mysql.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/integrations/openai_agents.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/integrations/print.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/integrations/psycopg.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/integrations/pydantic_ai.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/integrations/pymongo.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/integrations/pytest.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/integrations/redis.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/integrations/requests.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/integrations/sqlalchemy.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/integrations/sqlite3.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/integrations/starlette.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/integrations/surrealdb.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/integrations/system_metrics.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/integrations/wsgi.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/json_encoder.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/json_formatter.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/json_schema.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/json_types.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/scrubbing.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/server_response.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/stack_info.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/ulid.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/_internal/utils.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/cli.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/db_api.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/exceptions.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/experimental/__init__.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/experimental/annotations.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/experimental/api_client.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/experimental/datasets/__init__.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/experimental/forwarding.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/experimental/query_client.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/integrations/__init__.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/integrations/aiohttp_client.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/integrations/flask.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/integrations/httpx.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/integrations/logging.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/integrations/loguru.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/integrations/psycopg.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/integrations/pydantic.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/integrations/redis.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/integrations/sqlalchemy.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/integrations/structlog.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/integrations/wsgi.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/propagate.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/py.typed +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/query_client.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/sampling/__init__.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/sampling/_tail_sampling.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/types.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/variables/local.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/variables/remote.pyi +0 -0
- {logfire_api-4.35.0 → logfire_api-4.37.0}/logfire_api/version.pyi +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: logfire-api
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.37.0
|
|
4
4
|
Summary: Shim for the Logfire SDK which does nothing unless Logfire is installed
|
|
5
5
|
Author-email: Pydantic Team <engineering@pydantic.dev>, Samuel Colvin <samuel@pydantic.dev>, Hasan Ramezani <hasan@pydantic.dev>, Adrian Garcia Badaracco <adrian@pydantic.dev>, David Montague <david@pydantic.dev>, Marcelo Trylesinski <marcelo@pydantic.dev>, David Hewitt <david.hewitt@pydantic.dev>, Alex Hall <alex@pydantic.dev>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -16,7 +16,7 @@ from logfire.propagate import attach_context as attach_context, get_context as g
|
|
|
16
16
|
from logfire.sampling import SamplingOptions as SamplingOptions
|
|
17
17
|
from typing import Any
|
|
18
18
|
|
|
19
|
-
__all__ = ['Logfire', 'LogfireSpan', 'LevelName', 'AdvancedOptions', 'ConsoleOptions', 'CodeSource', 'PydanticPlugin', 'configure', 'span', 'instrument', 'log', 'trace', 'debug', 'notice', 'info', 'warn', 'warning', 'error', 'exception', 'fatal', 'force_flush', 'log_slow_async_callbacks', 'install_auto_tracing', 'instrument_asgi', 'instrument_wsgi', 'instrument_pydantic', 'instrument_pydantic_ai', 'instrument_fastapi', 'instrument_openai', 'instrument_openai_agents', 'instrument_anthropic', 'instrument_google_genai', 'instrument_litellm', 'instrument_dspy', 'instrument_print', 'instrument_asyncpg', 'instrument_httpx', 'instrument_celery', 'instrument_requests', 'instrument_psycopg', 'instrument_django', 'instrument_flask', 'instrument_starlette', 'instrument_aiohttp_client', 'instrument_aiohttp_server', 'instrument_sqlalchemy', 'instrument_sqlite3', 'instrument_aws_lambda', 'instrument_redis', 'instrument_pymongo', 'instrument_mysql', 'instrument_surrealdb', 'instrument_system_metrics', 'instrument_mcp', 'instrument_claude_agent_sdk', 'AutoTraceModule', 'with_tags', 'with_settings', 'suppress_scopes', 'shutdown', 'no_auto_trace', 'ScrubMatch', 'ScrubbingOptions', 'VERSION', 'add_non_user_code_prefix', 'suppress_instrumentation', 'StructlogProcessor', 'LogfireLoggingHandler', 'loguru_handler', 'SamplingOptions', 'MetricsOptions', 'VariablesOptions', 'LocalVariablesOptions', 'variables', 'var', 'variables_clear', 'variables_get', 'variables_push', 'variables_push_types', 'variables_validate', 'variables_push_config', 'variables_pull_config', 'variables_build_config', 'logfire_info', 'get_baggage', 'set_baggage', 'get_context', 'attach_context', 'url_from_eval', 'forward_export_request', 'forward_export_request_starlette']
|
|
19
|
+
__all__ = ['Logfire', 'LogfireSpan', 'LevelName', 'AdvancedOptions', 'ConsoleOptions', 'CodeSource', 'PydanticPlugin', 'configure', 'span', 'instrument', 'log', 'trace', 'debug', 'notice', 'info', 'warn', 'warning', 'error', 'exception', 'fatal', 'force_flush', 'log_slow_async_callbacks', 'install_auto_tracing', 'instrument_asgi', 'instrument_wsgi', 'instrument_pydantic', 'instrument_pydantic_ai', 'instrument_fastapi', 'instrument_openai', 'instrument_openai_agents', 'instrument_anthropic', 'instrument_google_genai', 'instrument_litellm', 'instrument_dspy', 'instrument_print', 'instrument_asyncpg', 'instrument_httpx', 'instrument_celery', 'instrument_requests', 'instrument_psycopg', 'instrument_django', 'instrument_flask', 'instrument_starlette', 'instrument_aiohttp_client', 'instrument_aiohttp_server', 'instrument_sqlalchemy', 'instrument_sqlite3', 'instrument_aws_lambda', 'instrument_redis', 'instrument_pymongo', 'instrument_mysql', 'instrument_surrealdb', 'instrument_system_metrics', 'instrument_mcp', 'instrument_claude_agent_sdk', 'AutoTraceModule', 'with_tags', 'with_settings', 'suppress_scopes', 'shutdown', 'no_auto_trace', 'ScrubMatch', 'ScrubbingOptions', 'VERSION', 'add_non_user_code_prefix', 'suppress_instrumentation', 'StructlogProcessor', 'LogfireLoggingHandler', 'loguru_handler', 'SamplingOptions', 'MetricsOptions', 'VariablesOptions', 'LocalVariablesOptions', 'variables', 'var', 'template_var', 'variables_clear', 'variables_get', 'variables_push', 'variables_push_types', 'variables_validate', 'variables_push_config', 'variables_pull_config', 'variables_build_config', 'logfire_info', 'get_baggage', 'set_baggage', 'get_context', 'attach_context', 'url_from_eval', 'forward_export_request', 'forward_export_request_starlette']
|
|
20
20
|
|
|
21
21
|
DEFAULT_LOGFIRE_INSTANCE = Logfire()
|
|
22
22
|
span = DEFAULT_LOGFIRE_INSTANCE.span
|
|
@@ -74,6 +74,7 @@ error = DEFAULT_LOGFIRE_INSTANCE.error
|
|
|
74
74
|
fatal = DEFAULT_LOGFIRE_INSTANCE.fatal
|
|
75
75
|
exception = DEFAULT_LOGFIRE_INSTANCE.exception
|
|
76
76
|
var = DEFAULT_LOGFIRE_INSTANCE.var
|
|
77
|
+
template_var = DEFAULT_LOGFIRE_INSTANCE.template_var
|
|
77
78
|
variables_clear = DEFAULT_LOGFIRE_INSTANCE.variables_clear
|
|
78
79
|
variables_get = DEFAULT_LOGFIRE_INSTANCE.variables_get
|
|
79
80
|
variables_push = DEFAULT_LOGFIRE_INSTANCE.variables_push
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import dataclasses
|
|
2
|
+
import functools
|
|
2
3
|
import requests
|
|
3
4
|
from ..propagate import NoExtractTraceContextPropagator as NoExtractTraceContextPropagator, WarnOnExtractTraceContextPropagator as WarnOnExtractTraceContextPropagator
|
|
4
5
|
from ..types import ExceptionCallback as ExceptionCallback
|
|
@@ -81,8 +82,8 @@ class PydanticPlugin:
|
|
|
81
82
|
This class is deprecated for external use. Use `logfire.instrument_pydantic()` instead.
|
|
82
83
|
"""
|
|
83
84
|
record: PydanticPluginRecordValues = ...
|
|
84
|
-
include: set[str] = field(default_factory=set)
|
|
85
|
-
exclude: set[str] = field(default_factory=set)
|
|
85
|
+
include: set[str] = field(default_factory=set[str])
|
|
86
|
+
exclude: set[str] = field(default_factory=set[str])
|
|
86
87
|
|
|
87
88
|
@dataclass
|
|
88
89
|
class MetricsOptions:
|
|
@@ -99,6 +100,8 @@ class CodeSource:
|
|
|
99
100
|
revision: str
|
|
100
101
|
root_path: str = ...
|
|
101
102
|
|
|
103
|
+
TemplateMismatchPolicy: Incomplete
|
|
104
|
+
|
|
102
105
|
@dataclass
|
|
103
106
|
class VariablesOptions:
|
|
104
107
|
"""Configuration for managed variables using the Logfire remote API.
|
|
@@ -112,6 +115,7 @@ class VariablesOptions:
|
|
|
112
115
|
include_resource_attributes_in_context: bool = ...
|
|
113
116
|
include_baggage_in_context: bool = ...
|
|
114
117
|
instrument: bool = ...
|
|
118
|
+
template_mismatch_policy: TemplateMismatchPolicy = ...
|
|
115
119
|
def __post_init__(self) -> None: ...
|
|
116
120
|
|
|
117
121
|
@dataclass
|
|
@@ -125,6 +129,7 @@ class LocalVariablesOptions:
|
|
|
125
129
|
include_resource_attributes_in_context: bool = ...
|
|
126
130
|
include_baggage_in_context: bool = ...
|
|
127
131
|
instrument: bool = ...
|
|
132
|
+
template_mismatch_policy: TemplateMismatchPolicy = ...
|
|
128
133
|
|
|
129
134
|
class DeprecatedKwargs(TypedDict): ...
|
|
130
135
|
|
|
@@ -286,8 +291,8 @@ class LogfireConfig(_LogfireConfigData):
|
|
|
286
291
|
This is used internally and should not be called by users of the SDK.
|
|
287
292
|
|
|
288
293
|
If no provider has been explicitly configured (i.e. `variables=` was not passed to
|
|
289
|
-
`configure()`), but a `LOGFIRE_API_KEY` is available,
|
|
290
|
-
will be lazily created on the first call.
|
|
294
|
+
`configure()`), but `configure()` has been called and a `LOGFIRE_API_KEY` is available,
|
|
295
|
+
a `LogfireRemoteVariableProvider` will be lazily created on the first call.
|
|
291
296
|
|
|
292
297
|
Returns:
|
|
293
298
|
The variable provider.
|
|
@@ -406,5 +411,8 @@ def sanitize_project_name(name: str) -> str:
|
|
|
406
411
|
"""Convert `name` to a string suitable for the `requested_project_name` API parameter."""
|
|
407
412
|
def default_project_name(): ...
|
|
408
413
|
def get_runtime_version() -> str: ...
|
|
414
|
+
@functools.cache
|
|
415
|
+
def common_resource_attributes(): ...
|
|
416
|
+
def host_resource_attributes(): ...
|
|
409
417
|
|
|
410
418
|
class LogfireNotConfiguredWarning(UserWarning): ...
|
|
@@ -11,9 +11,9 @@ from weakref import WeakSet
|
|
|
11
11
|
class ProxyLoggerProvider(LoggerProvider):
|
|
12
12
|
"""A logger provider that wraps another internal logger provider allowing it to be re-assigned."""
|
|
13
13
|
provider: LoggerProvider
|
|
14
|
-
loggers: WeakSet[ProxyLogger] = dataclasses.field(default_factory=WeakSet)
|
|
14
|
+
loggers: WeakSet[ProxyLogger] = dataclasses.field(default_factory=WeakSet['ProxyLogger'])
|
|
15
15
|
lock: Lock = dataclasses.field(default_factory=Lock)
|
|
16
|
-
suppressed_scopes: set[str] = dataclasses.field(default_factory=set)
|
|
16
|
+
suppressed_scopes: set[str] = dataclasses.field(default_factory=set[str])
|
|
17
17
|
min_level: int = ...
|
|
18
18
|
def get_logger(self, name: str, version: str | None = None, schema_url: str | None = None, attributes: _ExtendedAttributes | None = None) -> Logger: ...
|
|
19
19
|
def set_min_level(self, min_level: int) -> None: ...
|
|
@@ -14,10 +14,10 @@ from ..integrations.psycopg import CommenterOptions as PsycopgCommenterOptions
|
|
|
14
14
|
from ..integrations.redis import RequestHook as RedisRequestHook, ResponseHook as RedisResponseHook
|
|
15
15
|
from ..integrations.sqlalchemy import CommenterOptions as SQLAlchemyCommenterOptions
|
|
16
16
|
from ..integrations.wsgi import RequestHook as WSGIRequestHook, ResponseHook as WSGIResponseHook
|
|
17
|
-
from ..variables import ResolveFunction as ResolveFunction, ValidationReport as ValidationReport, Variable as Variable, VariablesConfig as VariablesConfig
|
|
17
|
+
from ..variables import ResolveFunction as ResolveFunction, TemplateVariable as TemplateVariable, ValidationReport as ValidationReport, Variable as Variable, VariablesConfig as VariablesConfig
|
|
18
18
|
from ..version import VERSION as VERSION
|
|
19
19
|
from .auto_trace import AutoTraceModule as AutoTraceModule, install_auto_tracing as install_auto_tracing
|
|
20
|
-
from .config import GLOBAL_CONFIG as GLOBAL_CONFIG, LogfireConfig as LogfireConfig
|
|
20
|
+
from .config import GLOBAL_CONFIG as GLOBAL_CONFIG, LogfireConfig as LogfireConfig, TemplateMismatchPolicy as TemplateMismatchPolicy
|
|
21
21
|
from .config_params import PydanticPluginRecordValues as PydanticPluginRecordValues
|
|
22
22
|
from .constants import ATTRIBUTES_JSON_SCHEMA_KEY as ATTRIBUTES_JSON_SCHEMA_KEY, ATTRIBUTES_LOG_LEVEL_NUM_KEY as ATTRIBUTES_LOG_LEVEL_NUM_KEY, ATTRIBUTES_MESSAGE_KEY as ATTRIBUTES_MESSAGE_KEY, ATTRIBUTES_MESSAGE_TEMPLATE_KEY as ATTRIBUTES_MESSAGE_TEMPLATE_KEY, ATTRIBUTES_SAMPLE_RATE_KEY as ATTRIBUTES_SAMPLE_RATE_KEY, ATTRIBUTES_SPAN_TYPE_KEY as ATTRIBUTES_SPAN_TYPE_KEY, ATTRIBUTES_TAGS_KEY as ATTRIBUTES_TAGS_KEY, DISABLE_CONSOLE_KEY as DISABLE_CONSOLE_KEY, LEVEL_NUMBERS as LEVEL_NUMBERS, LevelName as LevelName, OTLP_MAX_INT_SIZE as OTLP_MAX_INT_SIZE, log_level_attributes as log_level_attributes
|
|
23
23
|
from .formatter import logfire_format as logfire_format, logfire_format_with_magic as logfire_format_with_magic
|
|
@@ -64,6 +64,7 @@ from wsgiref.types import WSGIApplication
|
|
|
64
64
|
|
|
65
65
|
ExcInfo = SysExcInfo | BaseException | bool | None
|
|
66
66
|
T = TypeVar('T')
|
|
67
|
+
InputsT = TypeVar('InputsT')
|
|
67
68
|
|
|
68
69
|
class Logfire:
|
|
69
70
|
"""The main logfire class."""
|
|
@@ -1214,16 +1215,21 @@ class Logfire:
|
|
|
1214
1215
|
def var(self, name: str, *, default: T, description: str | None = None) -> Variable[T]: ...
|
|
1215
1216
|
@overload
|
|
1216
1217
|
def var(self, name: str, *, type: type[T], default: T | ResolveFunction[T], description: str | None = None) -> Variable[T]: ...
|
|
1218
|
+
@overload
|
|
1219
|
+
def template_var(self, name: str, *, default: T, inputs_type: type[InputsT], description: str | None = None, template_mismatch_policy: TemplateMismatchPolicy | None = None) -> TemplateVariable[T, InputsT]: ...
|
|
1220
|
+
@overload
|
|
1221
|
+
def template_var(self, name: str, *, type: type[T], default: T | ResolveFunction[T], inputs_type: type[InputsT], description: str | None = None, template_mismatch_policy: TemplateMismatchPolicy | None = None) -> TemplateVariable[T, InputsT]: ...
|
|
1217
1222
|
def variables_clear(self) -> None:
|
|
1218
1223
|
"""Clear all registered variables from this Logfire instance.
|
|
1219
1224
|
|
|
1220
|
-
This removes all variables previously registered via [`var()`][logfire.Logfire.var]
|
|
1225
|
+
This removes all variables previously registered via [`var()`][logfire.Logfire.var]
|
|
1226
|
+
or [`template_var()`][logfire.Logfire.template_var],
|
|
1221
1227
|
allowing them to be re-registered. This is primarily intended for use in tests
|
|
1222
1228
|
to ensure a clean state between test cases.
|
|
1223
1229
|
"""
|
|
1224
|
-
def variables_get(self) -> list[Variable[Any]]:
|
|
1230
|
+
def variables_get(self) -> list[Variable[Any] | TemplateVariable[Any, Any]]:
|
|
1225
1231
|
"""Get all variables registered with this Logfire instance."""
|
|
1226
|
-
def variables_push(self, variables: list[Variable[Any]] | None = None, *, dry_run: bool = False, yes: bool = False, strict: bool = False) -> bool:
|
|
1232
|
+
def variables_push(self, variables: list[Variable[Any] | TemplateVariable[Any, Any]] | None = None, *, dry_run: bool = False, yes: bool = False, strict: bool = False) -> bool:
|
|
1227
1233
|
"""Push variable definitions (metadata only) to the configured variable provider.
|
|
1228
1234
|
|
|
1229
1235
|
This method syncs local variable definitions with the provider:
|
|
@@ -1239,7 +1245,8 @@ class Logfire:
|
|
|
1239
1245
|
registered with this Logfire instance will be pushed.
|
|
1240
1246
|
dry_run: If True, only show what would change without applying.
|
|
1241
1247
|
yes: If True, skip confirmation prompt.
|
|
1242
|
-
strict: If True, fail if any existing label values are incompatible with new schemas
|
|
1248
|
+
strict: If True, fail if any existing label values are incompatible with new schemas
|
|
1249
|
+
or any reference errors are found.
|
|
1243
1250
|
|
|
1244
1251
|
Returns:
|
|
1245
1252
|
True if changes were applied (or would be applied in dry_run mode), False otherwise.
|
|
@@ -1316,7 +1323,7 @@ class Logfire:
|
|
|
1316
1323
|
)
|
|
1317
1324
|
```
|
|
1318
1325
|
"""
|
|
1319
|
-
def variables_validate(self, variables: list[Variable[Any]] | None = None) -> ValidationReport:
|
|
1326
|
+
def variables_validate(self, variables: list[Variable[Any] | TemplateVariable[Any, Any]] | None = None) -> ValidationReport:
|
|
1320
1327
|
"""Validate that provider-side variable label values match local type definitions.
|
|
1321
1328
|
|
|
1322
1329
|
This method fetches the current variable configuration from the provider and
|
|
@@ -1350,8 +1357,10 @@ class Logfire:
|
|
|
1350
1357
|
def variables_push_config(self, config: VariablesConfig, *, mode: Literal['merge', 'replace'] = 'merge', dry_run: bool = False, yes: bool = False) -> bool:
|
|
1351
1358
|
'''Push a VariablesConfig to the configured provider.
|
|
1352
1359
|
|
|
1353
|
-
This method pushes a
|
|
1354
|
-
|
|
1360
|
+
This method pushes a VariablesConfig (including labels and rollouts) to the
|
|
1361
|
+
provider. For remote providers, version records are created from label
|
|
1362
|
+
entries with inline serialized values; `latest_version` is pull/read state
|
|
1363
|
+
derived by the server and is not pushed directly. It\'s useful for:
|
|
1355
1364
|
- Pushing configs generated or modified locally
|
|
1356
1365
|
- Pushing configs read from files
|
|
1357
1366
|
- Partial updates (merge mode) or full replacement (replace mode)
|
|
@@ -1396,7 +1405,7 @@ class Logfire:
|
|
|
1396
1405
|
print(config.model_dump_json(indent=2))
|
|
1397
1406
|
```
|
|
1398
1407
|
'''
|
|
1399
|
-
def variables_build_config(self, variables: list[Variable[Any]] | None = None) -> VariablesConfig:
|
|
1408
|
+
def variables_build_config(self, variables: list[Variable[Any] | TemplateVariable[Any, Any]] | None = None) -> VariablesConfig:
|
|
1400
1409
|
'''Build a VariablesConfig from registered Variable instances.
|
|
1401
1410
|
|
|
1402
1411
|
This creates a minimal config with just the name, schema, and example for each variable.
|
|
@@ -11,9 +11,9 @@ from weakref import WeakSet
|
|
|
11
11
|
@dataclasses.dataclass
|
|
12
12
|
class ProxyMeterProvider(MeterProvider):
|
|
13
13
|
provider: MeterProvider
|
|
14
|
-
meters: WeakSet[_ProxyMeter] = dataclasses.field(default_factory=WeakSet)
|
|
14
|
+
meters: WeakSet[_ProxyMeter] = dataclasses.field(default_factory=WeakSet['_ProxyMeter'])
|
|
15
15
|
lock: Lock = dataclasses.field(default_factory=Lock)
|
|
16
|
-
suppressed_scopes: set[str] = dataclasses.field(default_factory=set)
|
|
16
|
+
suppressed_scopes: set[str] = dataclasses.field(default_factory=set[str])
|
|
17
17
|
def get_meter(self, name: str, version: str | None = None, schema_url: str | None = None, attributes: Attributes | None = None) -> Meter: ...
|
|
18
18
|
def suppress_scopes(self, *scopes: str) -> None: ...
|
|
19
19
|
def set_meter_provider(self, meter_provider: MeterProvider) -> None: ...
|
|
@@ -25,9 +25,9 @@ class ProxyTracerProvider(TracerProvider):
|
|
|
25
25
|
"""A tracer provider that wraps another internal tracer provider allowing it to be re-assigned."""
|
|
26
26
|
provider: TracerProvider
|
|
27
27
|
config: LogfireConfig
|
|
28
|
-
tracers: WeakKeyDictionary[_ProxyTracer, Callable[[], Tracer]] = field(default_factory=WeakKeyDictionary)
|
|
28
|
+
tracers: WeakKeyDictionary[_ProxyTracer, Callable[[], Tracer]] = field(default_factory=WeakKeyDictionary['_ProxyTracer', Callable[[], Tracer]])
|
|
29
29
|
lock: Lock = field(default_factory=Lock)
|
|
30
|
-
suppressed_scopes: set[str] = field(default_factory=set)
|
|
30
|
+
suppressed_scopes: set[str] = field(default_factory=set[str])
|
|
31
31
|
def set_provider(self, provider: SDKTracerProvider) -> None: ...
|
|
32
32
|
def suppress_scopes(self, *scopes: str) -> None: ...
|
|
33
33
|
def get_tracer(self, instrumenting_module_name: str, *args: Any, is_span_tracer: bool = True, **kwargs: Any) -> _ProxyTracer: ...
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
from logfire.variables.abstract import ResolutionReason as ResolutionReason, ResolvedVariable as ResolvedVariable, SyncMode as SyncMode, ValidationReport as ValidationReport, VariableAlreadyExistsError as VariableAlreadyExistsError, VariableNotFoundError as VariableNotFoundError, VariableWriteError as VariableWriteError
|
|
2
|
+
from logfire.variables.composition import ComposedReference as ComposedReference, VariableCompositionCycleError as VariableCompositionCycleError, VariableCompositionError as VariableCompositionError
|
|
3
|
+
from logfire.variables.config import KeyIsNotPresent as KeyIsNotPresent, KeyIsPresent as KeyIsPresent, LabelRef as LabelRef, LabeledValue as LabeledValue, LatestVersion as LatestVersion, LocalVariablesOptions as LocalVariablesOptions, Rollout as Rollout, RolloutOverride as RolloutOverride, TemplateMismatchPolicy as TemplateMismatchPolicy, ValueDoesNotEqual as ValueDoesNotEqual, ValueDoesNotMatchRegex as ValueDoesNotMatchRegex, ValueEquals as ValueEquals, ValueIsIn as ValueIsIn, ValueIsNotIn as ValueIsNotIn, ValueMatchesRegex as ValueMatchesRegex, VariableConfig as VariableConfig, VariableTypeConfig as VariableTypeConfig, VariablesConfig as VariablesConfig
|
|
4
|
+
from logfire.variables.variable import ResolveFunction as ResolveFunction, TemplateInputsMismatchError as TemplateInputsMismatchError, TemplateVariable as TemplateVariable, Variable as Variable, targeting_context as targeting_context
|
|
5
|
+
|
|
6
|
+
__all__ = ['Variable', 'TemplateVariable', 'ResolvedVariable', 'ResolveFunction', 'VariablesConfig', 'VariableConfig', 'VariableTypeConfig', 'LocalVariablesOptions', 'LabeledValue', 'LabelRef', 'LatestVersion', 'Rollout', 'RolloutOverride', 'KeyIsPresent', 'KeyIsNotPresent', 'ValueEquals', 'ValueDoesNotEqual', 'ValueIsIn', 'ValueIsNotIn', 'ValueMatchesRegex', 'ValueDoesNotMatchRegex', 'targeting_context', 'ComposedReference', 'ResolutionReason', 'SyncMode', 'TemplateMismatchPolicy', 'ValidationReport', 'TemplateInputsMismatchError', 'VariableAlreadyExistsError', 'VariableCompositionCycleError', 'VariableCompositionError', 'VariableNotFoundError', 'VariableWriteError']
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from functools import cache
|
|
2
|
+
from pydantic_handlebars import CompiledTemplate, HandlebarsEnvironment
|
|
3
|
+
|
|
4
|
+
COMPOSITION_OPEN_DELIM: str
|
|
5
|
+
COMPOSITION_CLOSE_DELIM: str
|
|
6
|
+
|
|
7
|
+
@cache
|
|
8
|
+
def get_environment(strict: bool = False) -> HandlebarsEnvironment:
|
|
9
|
+
"""Return a cached `HandlebarsEnvironment` configured for `@{...}@` composition.
|
|
10
|
+
|
|
11
|
+
Uses non-default delimiters so the composition pass leaves any
|
|
12
|
+
`{{...}}` runtime placeholders in the template as plain content; a
|
|
13
|
+
subsequent render pass with the default delimiters consumes those.
|
|
14
|
+
|
|
15
|
+
When *strict* is `True`, the environment raises `HandlebarsRuntimeError`
|
|
16
|
+
for any `@{ref}@` (or dotted `@{ref.field}@`) that doesn't resolve, rather
|
|
17
|
+
than rendering it as an empty string. Composition uses the strict
|
|
18
|
+
environment for provider/override values (so a missing reference triggers a
|
|
19
|
+
fall back to the code default) and the non-strict one for the code default
|
|
20
|
+
itself (the lenient last resort, where a missing reference renders empty).
|
|
21
|
+
"""
|
|
22
|
+
@cache
|
|
23
|
+
def get_runtime_environment() -> HandlebarsEnvironment:
|
|
24
|
+
"""Return a cached default-delimiter `HandlebarsEnvironment` for `{{...}}` rendering.
|
|
25
|
+
|
|
26
|
+
Used by `TemplateVariable.get(inputs)` to render the post-composition
|
|
27
|
+
serialized value against the provided inputs.
|
|
28
|
+
"""
|
|
29
|
+
def compile_composition_template(source: str, strict: bool = False) -> CompiledTemplate:
|
|
30
|
+
"""Return a cached `CompiledTemplate` for *source* under composition delimiters.
|
|
31
|
+
|
|
32
|
+
Managed-variable values are typically stable across many resolutions, so
|
|
33
|
+
caching the parsed program lets `Variable._resolve` skip the parse on
|
|
34
|
+
every `get()` call. 1024 is large enough for any realistic number of
|
|
35
|
+
distinct templates in a single process while staying bounded for
|
|
36
|
+
long-running workers. Same rationale for `compile_runtime_template`.
|
|
37
|
+
|
|
38
|
+
*strict* selects the strict vs non-strict composition environment (see
|
|
39
|
+
`get_environment`); the two are cached separately.
|
|
40
|
+
"""
|
|
41
|
+
def compile_runtime_template(source: str) -> CompiledTemplate:
|
|
42
|
+
"""Return a cached `CompiledTemplate` for *source* under default `{{...}}` delimiters."""
|
|
43
|
+
def extract_composition_dependencies(template: str) -> frozenset[str]:
|
|
44
|
+
"""Return the top-level `@{name}@` references in *template*.
|
|
45
|
+
|
|
46
|
+
Cached because cycle / reference validation runs over the same template
|
|
47
|
+
strings multiple times per push or sync. The underlying delegation goes
|
|
48
|
+
to `pydantic_handlebars.extract_dependencies` configured for the
|
|
49
|
+
composition delimiters, so block helpers, dotted paths, and helper
|
|
50
|
+
sub-expressions are handled AST-correctly. A `frozenset` is returned so the
|
|
51
|
+
cached value can't be mutated by a caller and poison later lookups.
|
|
52
|
+
"""
|
|
@@ -2,12 +2,14 @@ import logfire
|
|
|
2
2
|
from _typeshed import Incomplete
|
|
3
3
|
from abc import ABC, abstractmethod
|
|
4
4
|
from collections.abc import Mapping, Sequence
|
|
5
|
-
from dataclasses import dataclass
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from logfire.variables.composition import ComposedReference
|
|
6
7
|
from logfire.variables.config import VariableConfig, VariableTypeConfig, VariablesConfig
|
|
8
|
+
from logfire.variables.template_validation import TemplateFieldIssue
|
|
7
9
|
from logfire.variables.variable import Variable
|
|
8
10
|
from typing import Any, Generic, TypeVar
|
|
9
11
|
|
|
10
|
-
__all__ = ['ResolvedVariable', 'ResolutionReason', 'SyncMode', 'ValidationReport', 'VariableProvider', 'NoOpVariableProvider', 'VariableWriteError', 'VariableNotFoundError', 'VariableAlreadyExistsError']
|
|
12
|
+
__all__ = ['ResolvedVariable', 'ResolutionReason', 'SyncMode', 'ValidationReport', 'VariableProvider', 'NoOpVariableProvider', 'VariableWriteError', 'VariableNotFoundError', 'VariableAlreadyExistsError', 'render_serialized_string']
|
|
11
13
|
|
|
12
14
|
SyncMode: Incomplete
|
|
13
15
|
T = TypeVar('T')
|
|
@@ -46,11 +48,27 @@ class ResolvedVariable(Generic[T_co]):
|
|
|
46
48
|
label: str | None = ...
|
|
47
49
|
version: int | None = ...
|
|
48
50
|
exception: Exception | None = ...
|
|
51
|
+
composed_from: list[ComposedReference] = field(default_factory=list['ComposedReference'])
|
|
49
52
|
reason: ResolutionReason
|
|
50
53
|
def __post_init__(self) -> None: ...
|
|
51
54
|
def __enter__(self): ...
|
|
52
55
|
def __exit__(self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: Any) -> None: ...
|
|
53
56
|
|
|
57
|
+
def render_serialized_string(serialized_json: str, inputs: Any) -> str:
|
|
58
|
+
"""Render Handlebars templates in a serialized JSON string.
|
|
59
|
+
|
|
60
|
+
Decodes the JSON, renders all string values containing `{{placeholders}}`
|
|
61
|
+
using the provided inputs, then re-encodes to JSON.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
serialized_json: A JSON-encoded string potentially containing Handlebars templates.
|
|
65
|
+
inputs: Template context values. Can be a Pydantic `BaseModel`, `dict`,
|
|
66
|
+
`Mapping`, or `None`.
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
The rendered JSON string.
|
|
70
|
+
"""
|
|
71
|
+
|
|
54
72
|
@dataclass
|
|
55
73
|
class LabelCompatibility:
|
|
56
74
|
"""Result of checking a label value's compatibility with a schema."""
|
|
@@ -72,12 +90,18 @@ class VariableChange:
|
|
|
72
90
|
local_description: str | None = ...
|
|
73
91
|
server_description: str | None = ...
|
|
74
92
|
description_differs: bool = ...
|
|
93
|
+
template_inputs_schema: dict[str, Any] | None = ...
|
|
94
|
+
value_schema_changed: bool = ...
|
|
95
|
+
inputs_schema_changed: bool = ...
|
|
75
96
|
|
|
76
97
|
@dataclass
|
|
77
98
|
class VariableDiff:
|
|
78
99
|
"""Represents the diff between local and server variables."""
|
|
79
100
|
changes: list[VariableChange]
|
|
80
101
|
orphaned_server_variables: list[str]
|
|
102
|
+
reference_errors: list[str] = field(default_factory=list[str])
|
|
103
|
+
reference_cycles: list[str] = field(default_factory=list[str])
|
|
104
|
+
template_field_issues: list[TemplateFieldIssue] = field(default_factory=list)
|
|
81
105
|
@property
|
|
82
106
|
def has_changes(self) -> bool:
|
|
83
107
|
"""Return True if there are any changes to apply."""
|
|
@@ -116,12 +140,15 @@ class ValidationReport:
|
|
|
116
140
|
variables_checked: int
|
|
117
141
|
variables_not_on_server: list[str]
|
|
118
142
|
description_differences: list[DescriptionDifference]
|
|
143
|
+
reference_errors: list[str] = field(default_factory=list[str])
|
|
144
|
+
reference_cycles: list[str] = field(default_factory=list[str])
|
|
145
|
+
template_field_issues: list[TemplateFieldIssue] = field(default_factory=list)
|
|
119
146
|
@property
|
|
120
147
|
def has_errors(self) -> bool:
|
|
121
148
|
"""Return True if there are any validation errors."""
|
|
122
149
|
@property
|
|
123
150
|
def is_valid(self) -> bool:
|
|
124
|
-
"""Return False if there are
|
|
151
|
+
"""Return False if there are validation errors, missing variables, or reference errors."""
|
|
125
152
|
def format(self, *, colors: bool = True) -> str:
|
|
126
153
|
"""Format the validation report for human-readable output.
|
|
127
154
|
|
|
@@ -276,8 +303,10 @@ class VariableProvider(ABC):
|
|
|
276
303
|
def push_config(self, config: VariablesConfig, *, mode: SyncMode = 'merge', dry_run: bool = False, yes: bool = False) -> bool:
|
|
277
304
|
"""Push a VariablesConfig to this provider.
|
|
278
305
|
|
|
279
|
-
This method pushes a
|
|
280
|
-
|
|
306
|
+
This method pushes a VariablesConfig (including labels and rollouts) to the
|
|
307
|
+
provider. For remote providers, version records are created from label
|
|
308
|
+
entries with inline serialized values; `latest_version` is pull/read state
|
|
309
|
+
derived by the server and is not pushed directly. It's useful for:
|
|
281
310
|
- Pushing configs generated or modified locally
|
|
282
311
|
- Pushing configs read from files
|
|
283
312
|
- Partial updates (merge mode) or full replacement (replace mode)
|
|
@@ -313,7 +342,8 @@ class VariableProvider(ABC):
|
|
|
313
342
|
variables: Variable instances to push.
|
|
314
343
|
dry_run: If True, only show what would change without applying.
|
|
315
344
|
yes: If True, skip confirmation prompt.
|
|
316
|
-
strict: If True, fail if any existing label values are incompatible with new schemas
|
|
345
|
+
strict: If True, fail if any existing label values are incompatible with new schemas
|
|
346
|
+
or any reference errors are found.
|
|
317
347
|
|
|
318
348
|
Returns:
|
|
319
349
|
True if changes were applied (or would be applied in dry_run mode), False otherwise.
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
from dataclasses import dataclass, field
|
|
3
|
+
from logfire.variables.abstract import ResolutionReason
|
|
4
|
+
|
|
5
|
+
__all__ = ['MAX_COMPOSITION_DEPTH', 'VariableCompositionError', 'VariableCompositionCycleError', 'ComposedReference', 'expand_references', 'find_references', 'find_references_and_errors', 'has_references']
|
|
6
|
+
|
|
7
|
+
MAX_COMPOSITION_DEPTH: int
|
|
8
|
+
|
|
9
|
+
class VariableCompositionError(Exception):
|
|
10
|
+
"""Error during variable composition (reference expansion)."""
|
|
11
|
+
class VariableCompositionCycleError(VariableCompositionError):
|
|
12
|
+
"""Circular reference detected during variable composition."""
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class ComposedReference:
|
|
16
|
+
"""Metadata about a single `@{reference}@` that was encountered during expansion.
|
|
17
|
+
|
|
18
|
+
This is a lightweight dataclass used to track composition results without
|
|
19
|
+
depending on ResolvedVariable, making it reusable from both the SDK and backend.
|
|
20
|
+
"""
|
|
21
|
+
name: str
|
|
22
|
+
value: str | None
|
|
23
|
+
label: str | None
|
|
24
|
+
version: int | None
|
|
25
|
+
reason: ResolutionReason
|
|
26
|
+
error: str | None = ...
|
|
27
|
+
composed_from: list[ComposedReference] = field(default_factory=list['ComposedReference'])
|
|
28
|
+
fatal: bool = ...
|
|
29
|
+
ResolveFn = Callable[[str], tuple[str | None, str | None, int | None, ResolutionReason]]
|
|
30
|
+
|
|
31
|
+
def has_references(serialized_value: str) -> bool:
|
|
32
|
+
"""Quick check for any `@{` in a serialized value.
|
|
33
|
+
|
|
34
|
+
Returns `True` whenever the string contains the composition open
|
|
35
|
+
delimiter, regardless of preceding backslashes. The actual
|
|
36
|
+
escape-or-real decision is made by `pydantic_handlebars` at render time
|
|
37
|
+
— distinguishing an escaped `\\@{x}@` from an unescaped `@{x}@` here
|
|
38
|
+
would require a variable-width lookbehind (the renderer counts
|
|
39
|
+
backslash parity to match Handlebars.js semantics) and is unnecessary:
|
|
40
|
+
`extract_composition_dependencies` returns an empty set for escaped-only
|
|
41
|
+
strings, and the renderer correctly leaves the literal text in place.
|
|
42
|
+
"""
|
|
43
|
+
def expand_references(serialized_value: str, variable_name: str, resolve_fn: ResolveFn, *, strict: bool = False, _visited: tuple[str, ...] = (), _depth: int = 0) -> tuple[str, list[ComposedReference]]:
|
|
44
|
+
"""Expand `@{var}@` references in a serialized variable value.
|
|
45
|
+
|
|
46
|
+
Uses the Handlebars engine so `@{}@` supports the full Handlebars
|
|
47
|
+
syntax — simple references, dotted field reads, block helpers (including
|
|
48
|
+
with dotted or sub-expression headers like `@{#if user.active}@`), and
|
|
49
|
+
helper sub-expressions — while preserving `{{runtime}}` placeholders
|
|
50
|
+
untouched.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
serialized_value: The raw JSON-serialized variable value.
|
|
54
|
+
variable_name: Name of the variable being expanded (for cycle detection).
|
|
55
|
+
resolve_fn: Function that resolves a variable name to
|
|
56
|
+
(serialized_value, label, version, reason).
|
|
57
|
+
strict: When `True`, an unresolved `@{ref}@` / `@{ref.field}@` raises
|
|
58
|
+
`HandlebarsRuntimeError` instead of rendering as an empty string.
|
|
59
|
+
The SDK composes provider/override values strictly (so a missing
|
|
60
|
+
reference falls back to the code default) and the code default
|
|
61
|
+
non-strictly (the lenient last resort). Nested expansions inherit
|
|
62
|
+
this flag.
|
|
63
|
+
_visited: Internal - ordered variable names in the current expansion chain.
|
|
64
|
+
_depth: Internal - current recursion depth.
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
tuple of (expanded_serialized_value, list_of_composed_references).
|
|
68
|
+
|
|
69
|
+
Raises:
|
|
70
|
+
VariableCompositionError: If max depth is exceeded.
|
|
71
|
+
VariableCompositionCycleError: If a circular reference is detected.
|
|
72
|
+
HandlebarsRuntimeError: Under *strict*, if a reference is unresolved.
|
|
73
|
+
"""
|
|
74
|
+
def find_references(serialized_value: str) -> list[str]:
|
|
75
|
+
"""Find all top-level `@{variable_name}@` references in a serialized value.
|
|
76
|
+
|
|
77
|
+
Walks the decoded JSON value and runs each string containing composition
|
|
78
|
+
syntax through `pydantic_handlebars.extract_dependencies`, so block
|
|
79
|
+
helpers (`@{#if var}@`), dotted paths (`@{var.field}@`), and
|
|
80
|
+
subexpressions (`@{lookup obj key}@`) are all picked up correctly. A string
|
|
81
|
+
whose `@{...}@` syntax can't be parsed is skipped (contributes no
|
|
82
|
+
references) so this never raises; use `find_references_and_errors` to also
|
|
83
|
+
surface those parse failures.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
serialized_value: The raw JSON-serialized variable value to scan.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
Sorted (alphabetical) list of unique top-level variable names referenced.
|
|
90
|
+
"""
|
|
91
|
+
def find_references_and_errors(serialized_value: str) -> tuple[list[str], list[str]]:
|
|
92
|
+
"""Find references AND parse-error messages in a serialized value.
|
|
93
|
+
|
|
94
|
+
Like `find_references`, but also returns a message for every string whose
|
|
95
|
+
`@{...}@` syntax can't be parsed (malformed template, reserved name). Used by
|
|
96
|
+
push / validate so a malformed value is surfaced as a loud error rather than
|
|
97
|
+
silently skipped the way `find_references` does (it skips them so resolution
|
|
98
|
+
can degrade gracefully).
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
``(sorted unique reference names, parse-error messages)``.
|
|
102
|
+
"""
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import re
|
|
2
2
|
from collections.abc import Mapping, Sequence
|
|
3
|
-
from logfire._internal.config import LocalVariablesOptions as LocalVariablesOptions
|
|
3
|
+
from logfire._internal.config import LocalVariablesOptions as LocalVariablesOptions, TemplateMismatchPolicy as TemplateMismatchPolicy
|
|
4
4
|
from logfire.variables.abstract import ResolvedVariable
|
|
5
5
|
from logfire.variables.variable import Variable
|
|
6
6
|
from pydantic import BaseModel
|
|
7
7
|
from typing import Any, Literal
|
|
8
8
|
|
|
9
|
-
__all__ = ['KeyIsNotPresent', 'KeyIsPresent', 'LabeledValue', 'LabelRef', 'LatestVersion', 'LocalVariablesOptions', 'Rollout', 'RolloutOverride', 'ValueDoesNotEqual', 'ValueDoesNotMatchRegex', 'ValueEquals', 'ValueIsIn', 'ValueIsNotIn', 'ValueMatchesRegex', 'VariableConfig', 'VariablesConfig', 'VariableTypeConfig']
|
|
9
|
+
__all__ = ['KeyIsNotPresent', 'KeyIsPresent', 'LabeledValue', 'LabelRef', 'LatestVersion', 'LocalVariablesOptions', 'Rollout', 'RolloutOverride', 'TemplateMismatchPolicy', 'ValueDoesNotEqual', 'ValueDoesNotMatchRegex', 'ValueEquals', 'ValueIsIn', 'ValueIsNotIn', 'ValueMatchesRegex', 'VariableConfig', 'VariablesConfig', 'VariableTypeConfig']
|
|
10
10
|
|
|
11
11
|
class ValueEquals(BaseModel):
|
|
12
12
|
"""Condition that matches when an attribute equals a specific value."""
|
|
@@ -76,7 +76,13 @@ class LabeledValue(BaseModel):
|
|
|
76
76
|
serialized_value: str
|
|
77
77
|
|
|
78
78
|
class LabelRef(BaseModel):
|
|
79
|
-
"""A label pointing to a version via a reference to another label, 'latest', or 'code_default'.
|
|
79
|
+
"""A label pointing to a version via a reference to another label, 'latest', or 'code_default'.
|
|
80
|
+
|
|
81
|
+
Note: `'latest'` and `'code_default'` are *reserved label names*. The platform rejects
|
|
82
|
+
user attempts to create labels with these names, so anywhere the SDK treats them as
|
|
83
|
+
special — `follow_ref` here, and the push-time validation that keys values by label
|
|
84
|
+
name — it can rely on them being unambiguous (no user-defined label can collide).
|
|
85
|
+
"""
|
|
80
86
|
version: int | None
|
|
81
87
|
ref: str
|
|
82
88
|
|
|
@@ -117,6 +123,7 @@ class VariableConfig(BaseModel):
|
|
|
117
123
|
type_name: str | None
|
|
118
124
|
aliases: list[VariableName] | None
|
|
119
125
|
example: str | None
|
|
126
|
+
template_inputs_schema: dict[str, Any] | None
|
|
120
127
|
def resolve_label(self, targeting_key: str | None = None, attributes: Mapping[str, Any] | None = None) -> str | None:
|
|
121
128
|
"""Evaluate rollout rules and return the selected label name.
|
|
122
129
|
|
|
@@ -182,7 +189,7 @@ class VariablesConfig(BaseModel):
|
|
|
182
189
|
Returns:
|
|
183
190
|
A ResolvedVariable containing the serialized value (or None if not found).
|
|
184
191
|
"""
|
|
185
|
-
def get_validation_errors(self, variables:
|
|
192
|
+
def get_validation_errors(self, variables: Sequence[Variable[Any]]) -> dict[str, dict[str | None, Exception]]:
|
|
186
193
|
"""Validate that all variable label values can be deserialized to their expected types.
|
|
187
194
|
|
|
188
195
|
Args:
|
|
@@ -192,7 +199,7 @@ class VariablesConfig(BaseModel):
|
|
|
192
199
|
A dict mapping variable names to dicts of label names (or None for general errors) to exceptions.
|
|
193
200
|
"""
|
|
194
201
|
@staticmethod
|
|
195
|
-
def from_variables(variables:
|
|
202
|
+
def from_variables(variables: Sequence[Variable[Any]]) -> VariablesConfig:
|
|
196
203
|
"""Create a VariablesConfig from a list of Variable instances.
|
|
197
204
|
|
|
198
205
|
This creates a minimal config with just the name, schema, and example for each variable.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
from dataclasses import dataclass, field
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
__all__ = ['TemplateFieldIssue', 'TemplateValidationResult', 'validate_template_composition', 'detect_composition_cycles', 'extract_template_strings']
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class TemplateFieldIssue:
|
|
9
|
+
"""A `{{field}}` reference that doesn't match a template variable's `template_inputs_schema`."""
|
|
10
|
+
field_name: str
|
|
11
|
+
found_in_variable: str
|
|
12
|
+
found_in_label: str | None
|
|
13
|
+
reference_path: list[str]
|
|
14
|
+
root_variable: str
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class TemplateValidationResult:
|
|
18
|
+
"""Result of template composition validation."""
|
|
19
|
+
issues: list[TemplateFieldIssue] = field(default_factory=list[TemplateFieldIssue])
|
|
20
|
+
|
|
21
|
+
def extract_template_strings(serialized_json: str) -> list[str]:
|
|
22
|
+
"""Extract all string values from serialized JSON that contain `{{...}}` templates."""
|
|
23
|
+
def validate_template_composition(variable_name: str, template_inputs_schema: dict[str, Any], get_all_serialized_values: Callable[[str], dict[str | None, str]]) -> TemplateValidationResult:
|
|
24
|
+
"""Validate that `{{field}}` references in a template variable match its schema.
|
|
25
|
+
|
|
26
|
+
Walks the composition graph starting from *variable_name*, collecting all
|
|
27
|
+
template strings from the variable's values and its `@{ref}@` dependencies,
|
|
28
|
+
then uses AST-based schema checking via `check_template_compatibility` to
|
|
29
|
+
find incompatible field references.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
variable_name: Name of the template variable to validate.
|
|
33
|
+
template_inputs_schema: JSON Schema describing the expected template inputs.
|
|
34
|
+
get_all_serialized_values: Function that returns `{label_or_none: serialized_json}`
|
|
35
|
+
for any variable name. Each key is the label that serves that value; the
|
|
36
|
+
`None` key is the code default, and `'latest'` is the latest version.
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
A :class:`TemplateValidationResult` with any issues found.
|
|
40
|
+
"""
|
|
41
|
+
def detect_composition_cycles(variable_name: str, new_references: set[str], get_all_references: Callable[[str], set[str]]) -> list[str] | None:
|
|
42
|
+
"""Check if adding *new_references* to *variable_name* would create a cycle.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
variable_name: The variable being updated.
|
|
46
|
+
new_references: Set of variable names directly referenced by the new value.
|
|
47
|
+
get_all_references: Function that returns all variable names referenced by
|
|
48
|
+
any value of the given variable name.
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
The cycle path (e.g., `['A', 'B', 'C', 'A']`) if a cycle is detected,
|
|
52
|
+
or `None` if no cycle exists.
|
|
53
|
+
"""
|