From 43f3fe5cded6696ae552c49c94cd90a826481204 Mon Sep 17 00:00:00 2001 From: alex boten <223565+codeboten@users.noreply.github.com> Date: Thu, 15 Jan 2026 09:57:48 -0800 Subject: [PATCH] config: generate model code from json schema Proposing that the first step towards implementing OpenTelemetry Configuration is to produce the model code from the json schema. I did a quick search for tools available to do this and came across datamodel-codegen which seems to do what i expected. Will open following pull requests (in draft) to use this model code, i just want to keep these as clearly separated as possible to make reviewing them easier. Signed-off-by: alex boten <223565+codeboten@users.noreply.github.com> --- .../sdk/_configuration/models.py | 763 ++++++++++++++++++ pyproject.toml | 14 + tox.ini | 6 + 3 files changed, 783 insertions(+) create mode 100644 opentelemetry-sdk/src/opentelemetry/sdk/_configuration/models.py diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/models.py b/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/models.py new file mode 100644 index 0000000000..06e5107f95 --- /dev/null +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/models.py @@ -0,0 +1,763 @@ +# generated by datamodel-codegen: +# filename: https://raw.githubusercontent.com/open-telemetry/opentelemetry-configuration/refs/tags/v1.0.0-rc.3/opentelemetry_configuration.json +# timestamp: 2026-01-15T17:57:30+00:00 + +from __future__ import annotations + +from dataclasses import dataclass +from enum import Enum +from typing import Any, Optional, Union + +from typing_extensions import TypeAlias + +AlwaysOffSampler: TypeAlias = Optional[dict[str, Any]] + + +AlwaysOnSampler: TypeAlias = Optional[dict[str, Any]] + + +@dataclass +class AttributeLimits: + attribute_value_length_limit: Optional[int] = None + attribute_count_limit: Optional[int] = None + + +Value: TypeAlias = list[str] + + +Value1: TypeAlias = list[bool] + + +Value2: TypeAlias = list[float] + + +class AttributeType(Enum): + string = "string" + bool = "bool" + int = "int" + double = "double" + string_array = "string_array" + bool_array = "bool_array" + int_array = "int_array" + double_array = "double_array" + + +B3MultiPropagator: TypeAlias = Optional[dict[str, Any]] + + +B3Propagator: TypeAlias = Optional[dict[str, Any]] + + +BaggagePropagator: TypeAlias = Optional[dict[str, Any]] + + +@dataclass +class Base2ExponentialBucketHistogramAggregation: + max_scale: Optional[int] = None + max_size: Optional[int] = None + record_min_max: Optional[bool] = None + + +@dataclass +class CardinalityLimits: + default: Optional[int] = None + counter: Optional[int] = None + gauge: Optional[int] = None + histogram: Optional[int] = None + observable_counter: Optional[int] = None + observable_gauge: Optional[int] = None + observable_up_down_counter: Optional[int] = None + up_down_counter: Optional[int] = None + + +ConsoleExporter: TypeAlias = Optional[dict[str, Any]] + + +DefaultAggregation: TypeAlias = Optional[dict[str, Any]] + + +Distribution: TypeAlias = dict[str, dict[str, Any]] + + +DropAggregation: TypeAlias = Optional[dict[str, Any]] + + +class ExemplarFilter(Enum): + always_on = "always_on" + always_off = "always_off" + trace_based = "trace_based" + + +ExperimentalComposableAlwaysOffSampler: TypeAlias = Optional[dict[str, Any]] + + +ExperimentalComposableAlwaysOnSampler: TypeAlias = Optional[dict[str, Any]] + + +@dataclass +class ExperimentalComposableProbabilitySampler: + ratio: Optional[float] = None + + +@dataclass +class ExperimentalComposableRuleBasedSamplerRuleAttributePatterns: + key: str + included: Optional[list[str]] = None + excluded: Optional[list[str]] = None + + +@dataclass +class ExperimentalComposableRuleBasedSamplerRuleAttributeValues: + key: str + values: list[str] + + +ExperimentalContainerResourceDetector: TypeAlias = Optional[dict[str, Any]] + + +ExperimentalHostResourceDetector: TypeAlias = Optional[dict[str, Any]] + + +@dataclass +class ExperimentalHttpClientInstrumentation: + request_captured_headers: Optional[list[str]] = None + response_captured_headers: Optional[list[str]] = None + + +@dataclass +class ExperimentalHttpServerInstrumentation: + request_captured_headers: Optional[list[str]] = None + response_captured_headers: Optional[list[str]] = None + + +ExperimentalLanguageSpecificInstrumentation: TypeAlias = dict[ + str, dict[str, Any] +] + + +@dataclass +class ExperimentalMeterConfig: + disabled: Optional[bool] = None + + +@dataclass +class ExperimentalMeterMatcherAndConfig: + name: str + config: ExperimentalMeterConfig + + +@dataclass +class ExperimentalOtlpFileExporter: + output_stream: Optional[str] = None + + +@dataclass +class ExperimentalPeerServiceMapping: + peer: str + service: str + + +@dataclass +class ExperimentalProbabilitySampler: + ratio: Optional[float] = None + + +ExperimentalProcessResourceDetector: TypeAlias = Optional[dict[str, Any]] + + +class ExperimentalPrometheusTranslationStrategy(Enum): + underscore_escaping_with_suffixes = "underscore_escaping_with_suffixes" + underscore_escaping_without_suffixes = ( + "underscore_escaping_without_suffixes" + ) + no_utf8_escaping_with_suffixes = "no_utf8_escaping_with_suffixes" + no_translation = "no_translation" + + +ExperimentalServiceResourceDetector: TypeAlias = Optional[dict[str, Any]] + + +class ExperimentalSpanParent(Enum): + none = "none" + remote = "remote" + local = "local" + + +@dataclass +class ExperimentalTracerConfig: + disabled: Optional[bool] = None + + +@dataclass +class ExperimentalTracerMatcherAndConfig: + name: str + config: ExperimentalTracerConfig + + +@dataclass +class ExplicitBucketHistogramAggregation: + boundaries: Optional[list[float]] = None + record_min_max: Optional[bool] = None + + +class ExporterDefaultHistogramAggregation(Enum): + explicit_bucket_histogram = "explicit_bucket_histogram" + base2_exponential_bucket_histogram = "base2_exponential_bucket_histogram" + + +class ExporterTemporalityPreference(Enum): + cumulative = "cumulative" + delta = "delta" + low_memory = "low_memory" + + +@dataclass +class GrpcTls: + ca_file: Optional[str] = None + key_file: Optional[str] = None + cert_file: Optional[str] = None + insecure: Optional[bool] = None + + +@dataclass +class HttpTls: + ca_file: Optional[str] = None + key_file: Optional[str] = None + cert_file: Optional[str] = None + + +@dataclass +class IncludeExclude: + included: Optional[list[str]] = None + excluded: Optional[list[str]] = None + + +class InstrumentType(Enum): + counter = "counter" + gauge = "gauge" + histogram = "histogram" + observable_counter = "observable_counter" + observable_gauge = "observable_gauge" + observable_up_down_counter = "observable_up_down_counter" + up_down_counter = "up_down_counter" + + +JaegerPropagator: TypeAlias = Optional[dict[str, Any]] + + +LastValueAggregation: TypeAlias = Optional[dict[str, Any]] + + +@dataclass +class LogRecordLimits: + attribute_value_length_limit: Optional[int] = None + attribute_count_limit: Optional[int] = None + + +@dataclass +class NameStringValuePair: + name: str + value: Optional[str] + + +OpenCensusMetricProducer: TypeAlias = Optional[dict[str, Any]] + + +OpenTracingPropagator: TypeAlias = Optional[dict[str, Any]] + + +@dataclass +class OtlpGrpcExporter: + endpoint: Optional[str] = None + tls: Optional[GrpcTls] = None + headers: Optional[list[NameStringValuePair]] = None + headers_list: Optional[str] = None + compression: Optional[str] = None + timeout: Optional[int] = None + + +@dataclass +class OtlpGrpcMetricExporter: + endpoint: Optional[str] = None + tls: Optional[GrpcTls] = None + headers: Optional[list[NameStringValuePair]] = None + headers_list: Optional[str] = None + compression: Optional[str] = None + timeout: Optional[int] = None + temporality_preference: Optional[ExporterTemporalityPreference] = None + default_histogram_aggregation: Optional[ + ExporterDefaultHistogramAggregation + ] = None + + +class OtlpHttpEncoding(Enum): + protobuf = "protobuf" + json = "json" + + +@dataclass +class OtlpHttpExporter: + endpoint: Optional[str] = None + tls: Optional[HttpTls] = None + headers: Optional[list[NameStringValuePair]] = None + headers_list: Optional[str] = None + compression: Optional[str] = None + timeout: Optional[int] = None + encoding: Optional[OtlpHttpEncoding] = None + + +@dataclass +class OtlpHttpMetricExporter: + endpoint: Optional[str] = None + tls: Optional[HttpTls] = None + headers: Optional[list[NameStringValuePair]] = None + headers_list: Optional[str] = None + compression: Optional[str] = None + timeout: Optional[int] = None + encoding: Optional[OtlpHttpEncoding] = None + temporality_preference: Optional[ExporterTemporalityPreference] = None + default_histogram_aggregation: Optional[ + ExporterDefaultHistogramAggregation + ] = None + + +class SeverityNumber(Enum): + trace = "trace" + trace2 = "trace2" + trace3 = "trace3" + trace4 = "trace4" + debug = "debug" + debug2 = "debug2" + debug3 = "debug3" + debug4 = "debug4" + info = "info" + info2 = "info2" + info3 = "info3" + info4 = "info4" + warn = "warn" + warn2 = "warn2" + warn3 = "warn3" + warn4 = "warn4" + error = "error" + error2 = "error2" + error3 = "error3" + error4 = "error4" + fatal = "fatal" + fatal2 = "fatal2" + fatal3 = "fatal3" + fatal4 = "fatal4" + + +@dataclass +class SpanExporter: + otlp_http: Optional[OtlpHttpExporter] = None + otlp_grpc: Optional[OtlpGrpcExporter] = None + otlp_file_development: Optional[ExperimentalOtlpFileExporter] = None + console: Optional[ConsoleExporter] = None + + +class SpanKind(Enum): + internal = "internal" + server = "server" + client = "client" + producer = "producer" + consumer = "consumer" + + +@dataclass +class SpanLimits: + attribute_value_length_limit: Optional[int] = None + attribute_count_limit: Optional[int] = None + event_count_limit: Optional[int] = None + link_count_limit: Optional[int] = None + event_attribute_count_limit: Optional[int] = None + link_attribute_count_limit: Optional[int] = None + + +SumAggregation: TypeAlias = Optional[dict[str, Any]] + + +TraceContextPropagator: TypeAlias = Optional[dict[str, Any]] + + +@dataclass +class TraceIdRatioBasedSampler: + ratio: Optional[float] = None + + +@dataclass +class ViewSelector: + instrument_name: Optional[str] = None + instrument_type: Optional[InstrumentType] = None + unit: Optional[str] = None + meter_name: Optional[str] = None + meter_version: Optional[str] = None + meter_schema_url: Optional[str] = None + + +@dataclass +class Aggregation: + default: Optional[DefaultAggregation] = None + drop: Optional[DropAggregation] = None + explicit_bucket_histogram: Optional[ExplicitBucketHistogramAggregation] = ( + None + ) + base2_exponential_bucket_histogram: Optional[ + Base2ExponentialBucketHistogramAggregation + ] = None + last_value: Optional[LastValueAggregation] = None + sum: Optional[SumAggregation] = None + + +@dataclass +class AttributeNameValue: + name: str + value: Optional[Union[str, float, bool, Value, Value1, Value2]] + type: Optional[AttributeType] = None + + +@dataclass +class BatchSpanProcessor: + exporter: SpanExporter + schedule_delay: Optional[int] = None + export_timeout: Optional[int] = None + max_queue_size: Optional[int] = None + max_export_batch_size: Optional[int] = None + + +@dataclass +class ConsoleMetricExporter: + temporality_preference: Optional[ExporterTemporalityPreference] = None + default_histogram_aggregation: Optional[ + ExporterDefaultHistogramAggregation + ] = None + + +@dataclass +class ExperimentalHttpInstrumentation: + client: Optional[ExperimentalHttpClientInstrumentation] = None + server: Optional[ExperimentalHttpServerInstrumentation] = None + + +@dataclass +class ExperimentalLoggerConfig: + disabled: Optional[bool] = None + minimum_severity: Optional[SeverityNumber] = None + trace_based: Optional[bool] = None + + +@dataclass +class ExperimentalLoggerMatcherAndConfig: + name: str + config: ExperimentalLoggerConfig + + +@dataclass +class ExperimentalMeterConfigurator: + default_config: Optional[ExperimentalMeterConfig] = None + meters: Optional[list[ExperimentalMeterMatcherAndConfig]] = None + + +@dataclass +class ExperimentalOtlpFileMetricExporter: + output_stream: Optional[str] = None + temporality_preference: Optional[ExporterTemporalityPreference] = None + default_histogram_aggregation: Optional[ + ExporterDefaultHistogramAggregation + ] = None + + +@dataclass +class ExperimentalPeerInstrumentation: + service_mapping: Optional[list[ExperimentalPeerServiceMapping]] = None + + +@dataclass +class ExperimentalPrometheusMetricExporter: + host: Optional[str] = None + port: Optional[int] = None + without_scope_info: Optional[bool] = None + without_target_info: Optional[bool] = None + with_resource_constant_labels: Optional[IncludeExclude] = None + translation_strategy: Optional[ + ExperimentalPrometheusTranslationStrategy + ] = None + + +@dataclass +class ExperimentalResourceDetector: + container: Optional[ExperimentalContainerResourceDetector] = None + host: Optional[ExperimentalHostResourceDetector] = None + process: Optional[ExperimentalProcessResourceDetector] = None + service: Optional[ExperimentalServiceResourceDetector] = None + + +@dataclass +class ExperimentalTracerConfigurator: + default_config: Optional[ExperimentalTracerConfig] = None + tracers: Optional[list[ExperimentalTracerMatcherAndConfig]] = None + + +@dataclass +class LogRecordExporter: + otlp_http: Optional[OtlpHttpExporter] = None + otlp_grpc: Optional[OtlpGrpcExporter] = None + otlp_file_development: Optional[ExperimentalOtlpFileExporter] = None + console: Optional[ConsoleExporter] = None + + +@dataclass +class MetricProducer: + opencensus: Optional[OpenCensusMetricProducer] = None + + +@dataclass +class PullMetricExporter: + prometheus_development: Optional[ExperimentalPrometheusMetricExporter] = ( + None + ) + + +@dataclass +class PullMetricReader: + exporter: PullMetricExporter + producers: Optional[list[MetricProducer]] = None + cardinality_limits: Optional[CardinalityLimits] = None + + +@dataclass +class PushMetricExporter: + otlp_http: Optional[OtlpHttpMetricExporter] = None + otlp_grpc: Optional[OtlpGrpcMetricExporter] = None + otlp_file_development: Optional[ExperimentalOtlpFileMetricExporter] = None + console: Optional[ConsoleMetricExporter] = None + + +@dataclass +class SimpleLogRecordProcessor: + exporter: LogRecordExporter + + +@dataclass +class SimpleSpanProcessor: + exporter: SpanExporter + + +@dataclass +class SpanProcessor: + batch: Optional[BatchSpanProcessor] = None + simple: Optional[SimpleSpanProcessor] = None + + +@dataclass +class TextMapPropagator: + tracecontext: Optional[TraceContextPropagator] = None + baggage: Optional[BaggagePropagator] = None + b3: Optional[B3Propagator] = None + b3multi: Optional[B3MultiPropagator] = None + jaeger: Optional[JaegerPropagator] = None + ottrace: Optional[OpenTracingPropagator] = None + + +@dataclass +class ViewStream: + name: Optional[str] = None + description: Optional[str] = None + aggregation: Optional[Aggregation] = None + aggregation_cardinality_limit: Optional[int] = None + attribute_keys: Optional[IncludeExclude] = None + + +@dataclass +class BatchLogRecordProcessor: + exporter: LogRecordExporter + schedule_delay: Optional[int] = None + export_timeout: Optional[int] = None + max_queue_size: Optional[int] = None + max_export_batch_size: Optional[int] = None + + +@dataclass +class ExperimentalGeneralInstrumentation: + peer: Optional[ExperimentalPeerInstrumentation] = None + http: Optional[ExperimentalHttpInstrumentation] = None + + +@dataclass +class ExperimentalInstrumentation: + general: Optional[ExperimentalGeneralInstrumentation] = None + cpp: Optional[ExperimentalLanguageSpecificInstrumentation] = None + dotnet: Optional[ExperimentalLanguageSpecificInstrumentation] = None + erlang: Optional[ExperimentalLanguageSpecificInstrumentation] = None + go: Optional[ExperimentalLanguageSpecificInstrumentation] = None + java: Optional[ExperimentalLanguageSpecificInstrumentation] = None + js: Optional[ExperimentalLanguageSpecificInstrumentation] = None + php: Optional[ExperimentalLanguageSpecificInstrumentation] = None + python: Optional[ExperimentalLanguageSpecificInstrumentation] = None + ruby: Optional[ExperimentalLanguageSpecificInstrumentation] = None + rust: Optional[ExperimentalLanguageSpecificInstrumentation] = None + swift: Optional[ExperimentalLanguageSpecificInstrumentation] = None + + +@dataclass +class ExperimentalLoggerConfigurator: + default_config: Optional[ExperimentalLoggerConfig] = None + loggers: Optional[list[ExperimentalLoggerMatcherAndConfig]] = None + + +@dataclass +class ExperimentalResourceDetection: + attributes: Optional[IncludeExclude] = None + detectors: Optional[list[ExperimentalResourceDetector]] = None + + +@dataclass +class LogRecordProcessor: + batch: Optional[BatchLogRecordProcessor] = None + simple: Optional[SimpleLogRecordProcessor] = None + + +@dataclass +class PeriodicMetricReader: + exporter: PushMetricExporter + interval: Optional[int] = None + timeout: Optional[int] = None + producers: Optional[list[MetricProducer]] = None + cardinality_limits: Optional[CardinalityLimits] = None + + +@dataclass +class Propagator: + composite: Optional[list[TextMapPropagator]] = None + composite_list: Optional[str] = None + + +@dataclass +class Resource: + attributes: Optional[list[AttributeNameValue]] = None + detection_development: Optional[ExperimentalResourceDetection] = None + schema_url: Optional[str] = None + attributes_list: Optional[str] = None + + +@dataclass +class View: + selector: ViewSelector + stream: ViewStream + + +@dataclass +class LoggerProvider: + processors: list[LogRecordProcessor] + limits: Optional[LogRecordLimits] = None + logger_configurator_development: Optional[ + ExperimentalLoggerConfigurator + ] = None + + +@dataclass +class MetricReader: + periodic: Optional[PeriodicMetricReader] = None + pull: Optional[PullMetricReader] = None + + +@dataclass +class MeterProvider: + readers: list[MetricReader] + views: Optional[list[View]] = None + exemplar_filter: Optional[ExemplarFilter] = None + meter_configurator_development: Optional[ExperimentalMeterConfigurator] = ( + None + ) + + +@dataclass +class OpenTelemetryConfiguration: + file_format: str + disabled: Optional[bool] = None + log_level: Optional[SeverityNumber] = None + attribute_limits: Optional[AttributeLimits] = None + logger_provider: Optional[LoggerProvider] = None + meter_provider: Optional[MeterProvider] = None + propagator: Optional[Propagator] = None + tracer_provider: Optional[TracerProvider] = None + resource: Optional[Resource] = None + instrumentation_development: Optional[ExperimentalInstrumentation] = None + distribution: Optional[Distribution] = None + + +@dataclass +class ExperimentalComposableParentThresholdSampler: + root: ExperimentalComposableSampler + + +@dataclass +class ExperimentalComposableRuleBasedSampler: + rules: Optional[list[ExperimentalComposableRuleBasedSamplerRule]] = None + + +@dataclass +class ExperimentalComposableRuleBasedSamplerRule: + """ + A rule for ExperimentalComposableRuleBasedSampler. A rule can have multiple match conditions - the sampler will be applied if all match. + If no conditions are specified, the rule matches all spans that reach it. + + """ + + sampler: ExperimentalComposableSampler + attribute_values: Optional[ + ExperimentalComposableRuleBasedSamplerRuleAttributeValues + ] = None + attribute_patterns: Optional[ + ExperimentalComposableRuleBasedSamplerRuleAttributePatterns + ] = None + span_kinds: Optional[list[Optional[SpanKind]]] = None + parent: Optional[list[Optional[ExperimentalSpanParent]]] = None + + +@dataclass +class ExperimentalComposableSampler: + always_off: Optional[ExperimentalComposableAlwaysOffSampler] = None + always_on: Optional[ExperimentalComposableAlwaysOnSampler] = None + parent_threshold: Optional[ + ExperimentalComposableParentThresholdSampler + ] = None + probability: Optional[ExperimentalComposableProbabilitySampler] = None + rule_based: Optional[ExperimentalComposableRuleBasedSampler] = None + + +@dataclass +class ExperimentalJaegerRemoteSampler: + endpoint: str + initial_sampler: Sampler + interval: Optional[int] = None + + +@dataclass +class ParentBasedSampler: + root: Optional[Sampler] = None + remote_parent_sampled: Optional[Sampler] = None + remote_parent_not_sampled: Optional[Sampler] = None + local_parent_sampled: Optional[Sampler] = None + local_parent_not_sampled: Optional[Sampler] = None + + +@dataclass +class Sampler: + always_off: Optional[AlwaysOffSampler] = None + always_on: Optional[AlwaysOnSampler] = None + composite_development: Optional[ExperimentalComposableSampler] = None + jaeger_remote_development: Optional[ExperimentalJaegerRemoteSampler] = None + parent_based: Optional[ParentBasedSampler] = None + probability_development: Optional[ExperimentalProbabilitySampler] = None + trace_id_ratio_based: Optional[TraceIdRatioBasedSampler] = None + + +@dataclass +class TracerProvider: + processors: list[SpanProcessor] + limits: Optional[SpanLimits] = None + sampler: Optional[Sampler] = None + tracer_configurator_development: Optional[ + ExperimentalTracerConfigurator + ] = None diff --git a/pyproject.toml b/pyproject.toml index b6970c666d..5b53109c04 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -101,6 +101,18 @@ known-first-party = ["opentelemetry", "opentelemetry_example_app"] typeCheckingMode = "standard" pythonVersion = "3.9" +[tool.datamodel-codegen] +url = "https://raw.githubusercontent.com/open-telemetry/opentelemetry-configuration/refs/tags/v1.0.0-rc.3/opentelemetry_configuration.json" +input-file-type = "jsonschema" +output = "opentelemetry-sdk/src/opentelemetry/sdk/_configuration/models.py" +output-model-type = "dataclasses.dataclass" +field-constraints = true +formatters = ["ruff-format", "ruff-check"] +use-standard-collections = true +use-schema-description = true +use-title-as-name = true +target-python-version = "3.9" + include = [ "opentelemetry-semantic-conventions", "opentelemetry-api", @@ -129,4 +141,6 @@ dev = [ "tox", "tox-uv>=1", "pre-commit", + "datamodel-code-generator[http]", + "datamodel-code-generator[ruff]", ] diff --git a/tox.ini b/tox.ini index 52e0c1612a..b5deafafbc 100644 --- a/tox.ini +++ b/tox.ini @@ -330,6 +330,12 @@ deps = commands = python {toxinidir}/.github/workflows/generate_workflows.py +[testenv:generate-config-from-jsonschema] +deps = + tox +commands = + datamodel-codegen + [testenv:shellcheck] commands_pre = sh -c "sudo apt update -y && sudo apt install --assume-yes shellcheck"