opentelemetry-configuration 0.65b0__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.
- opentelemetry_configuration-0.65b0/.gitignore +73 -0
- opentelemetry_configuration-0.65b0/PKG-INFO +73 -0
- opentelemetry_configuration-0.65b0/README.rst +47 -0
- opentelemetry_configuration-0.65b0/pyproject.toml +49 -0
- opentelemetry_configuration-0.65b0/src/opentelemetry/configuration/README.md +28 -0
- opentelemetry_configuration-0.65b0/src/opentelemetry/configuration/__init__.py +42 -0
- opentelemetry_configuration-0.65b0/src/opentelemetry/configuration/_common.py +238 -0
- opentelemetry_configuration-0.65b0/src/opentelemetry/configuration/_conversion.py +118 -0
- opentelemetry_configuration-0.65b0/src/opentelemetry/configuration/_exceptions.py +59 -0
- opentelemetry_configuration-0.65b0/src/opentelemetry/configuration/_logger_provider.py +288 -0
- opentelemetry_configuration-0.65b0/src/opentelemetry/configuration/_meter_provider.py +626 -0
- opentelemetry_configuration-0.65b0/src/opentelemetry/configuration/_propagator.py +106 -0
- opentelemetry_configuration-0.65b0/src/opentelemetry/configuration/_resource.py +219 -0
- opentelemetry_configuration-0.65b0/src/opentelemetry/configuration/_sdk.py +68 -0
- opentelemetry_configuration-0.65b0/src/opentelemetry/configuration/_tracer_provider.py +508 -0
- opentelemetry_configuration-0.65b0/src/opentelemetry/configuration/file/__init__.py +60 -0
- opentelemetry_configuration-0.65b0/src/opentelemetry/configuration/file/_env_substitution.py +92 -0
- opentelemetry_configuration-0.65b0/src/opentelemetry/configuration/file/_loader.py +268 -0
- opentelemetry_configuration-0.65b0/src/opentelemetry/configuration/instrumentation.py +78 -0
- opentelemetry_configuration-0.65b0/src/opentelemetry/configuration/models.py +835 -0
- opentelemetry_configuration-0.65b0/src/opentelemetry/configuration/schema.json +2576 -0
- opentelemetry_configuration-0.65b0/src/opentelemetry/configuration/version/__init__.py +4 -0
- opentelemetry_configuration-0.65b0/tests/__init__.py +2 -0
- opentelemetry_configuration-0.65b0/tests/file/__init__.py +2 -0
- opentelemetry_configuration-0.65b0/tests/file/data/config_with_env_vars.yaml +7 -0
- opentelemetry_configuration-0.65b0/tests/file/data/invalid_yaml.yaml +3 -0
- opentelemetry_configuration-0.65b0/tests/file/data/minimal_config.json +3 -0
- opentelemetry_configuration-0.65b0/tests/file/data/minimal_config.yaml +1 -0
- opentelemetry_configuration-0.65b0/tests/file/test_env_substitution.py +150 -0
- opentelemetry_configuration-0.65b0/tests/file/test_loader.py +369 -0
- opentelemetry_configuration-0.65b0/tests/test_common.py +461 -0
- opentelemetry_configuration-0.65b0/tests/test_conversion.py +110 -0
- opentelemetry_configuration-0.65b0/tests/test_exceptions.py +74 -0
- opentelemetry_configuration-0.65b0/tests/test_instrumentation.py +250 -0
- opentelemetry_configuration-0.65b0/tests/test_logger_provider.py +496 -0
- opentelemetry_configuration-0.65b0/tests/test_meter_provider.py +977 -0
- opentelemetry_configuration-0.65b0/tests/test_meter_provider_exemplar_filter.py +58 -0
- opentelemetry_configuration-0.65b0/tests/test_models.py +12 -0
- opentelemetry_configuration-0.65b0/tests/test_propagator.py +301 -0
- opentelemetry_configuration-0.65b0/tests/test_resource.py +622 -0
- opentelemetry_configuration-0.65b0/tests/test_sdk.py +149 -0
- opentelemetry_configuration-0.65b0/tests/test_tracer_provider.py +909 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
*.py[cod]
|
|
2
|
+
*.sw[op]
|
|
3
|
+
|
|
4
|
+
# C extensions
|
|
5
|
+
*.so
|
|
6
|
+
|
|
7
|
+
# Packages
|
|
8
|
+
*.egg
|
|
9
|
+
*.egg-info
|
|
10
|
+
dist
|
|
11
|
+
build
|
|
12
|
+
eggs
|
|
13
|
+
parts
|
|
14
|
+
bin
|
|
15
|
+
include
|
|
16
|
+
var
|
|
17
|
+
sdist
|
|
18
|
+
develop-eggs
|
|
19
|
+
.installed.cfg
|
|
20
|
+
pyvenv.cfg
|
|
21
|
+
lib
|
|
22
|
+
share/
|
|
23
|
+
lib64
|
|
24
|
+
__pycache__
|
|
25
|
+
venv*/
|
|
26
|
+
.venv*/
|
|
27
|
+
|
|
28
|
+
# Installer logs
|
|
29
|
+
pip-log.txt
|
|
30
|
+
|
|
31
|
+
# Unit test / coverage reports
|
|
32
|
+
coverage.xml
|
|
33
|
+
.coverage
|
|
34
|
+
.nox
|
|
35
|
+
.tox
|
|
36
|
+
.cache
|
|
37
|
+
htmlcov
|
|
38
|
+
|
|
39
|
+
.agent/
|
|
40
|
+
|
|
41
|
+
# Translations
|
|
42
|
+
*.mo
|
|
43
|
+
|
|
44
|
+
# Mac
|
|
45
|
+
.DS_Store
|
|
46
|
+
|
|
47
|
+
# Mr Developer
|
|
48
|
+
.mr.developer.cfg
|
|
49
|
+
.project
|
|
50
|
+
.pydevproject
|
|
51
|
+
|
|
52
|
+
# JetBrains
|
|
53
|
+
.idea
|
|
54
|
+
|
|
55
|
+
# VSCode
|
|
56
|
+
.vscode
|
|
57
|
+
|
|
58
|
+
# Sphinx
|
|
59
|
+
_build/
|
|
60
|
+
|
|
61
|
+
# mypy
|
|
62
|
+
.mypy_cache/
|
|
63
|
+
target
|
|
64
|
+
|
|
65
|
+
# Django example
|
|
66
|
+
|
|
67
|
+
docs/examples/django/db.sqlite3
|
|
68
|
+
|
|
69
|
+
# Semantic conventions
|
|
70
|
+
scripts/semconv/semantic-conventions
|
|
71
|
+
|
|
72
|
+
# Benchmark result files
|
|
73
|
+
*-benchmark.json
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: opentelemetry-configuration
|
|
3
|
+
Version: 0.65b0
|
|
4
|
+
Summary: OpenTelemetry Python Declarative Configuration (experimental)
|
|
5
|
+
Project-URL: Homepage, https://github.com/open-telemetry/opentelemetry-python/tree/main/opentelemetry-configuration
|
|
6
|
+
Project-URL: Repository, https://github.com/open-telemetry/opentelemetry-python
|
|
7
|
+
Author-email: OpenTelemetry Authors <cncf-opentelemetry-contributors@lists.cncf.io>
|
|
8
|
+
License-Expression: Apache-2.0
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Framework :: OpenTelemetry
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Programming Language :: Python
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Classifier: Typing :: Typed
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Requires-Dist: jsonschema>=4.0
|
|
22
|
+
Requires-Dist: opentelemetry-api==1.44.0
|
|
23
|
+
Requires-Dist: opentelemetry-sdk==1.44.0
|
|
24
|
+
Requires-Dist: pyyaml>=5.4
|
|
25
|
+
Description-Content-Type: text/x-rst
|
|
26
|
+
|
|
27
|
+
OpenTelemetry Python SDK Declarative Configuration
|
|
28
|
+
===================================================
|
|
29
|
+
|
|
30
|
+
|pypi|
|
|
31
|
+
|
|
32
|
+
.. |pypi| image:: https://badge.fury.io/py/opentelemetry-configuration.svg
|
|
33
|
+
:target: https://pypi.org/project/opentelemetry-configuration/
|
|
34
|
+
|
|
35
|
+
.. warning::
|
|
36
|
+
|
|
37
|
+
This package is **experimental**. The API surface, type names, and behaviour
|
|
38
|
+
may change between minor versions. Pin a specific version in production.
|
|
39
|
+
|
|
40
|
+
This package implements the `OpenTelemetry declarative configuration
|
|
41
|
+
specification <https://opentelemetry.io/docs/specs/otel/configuration/>`_ for
|
|
42
|
+
the Python SDK. It parses a YAML or JSON configuration file (or
|
|
43
|
+
programmatically-constructed configuration model) into typed dataclasses and
|
|
44
|
+
applies the result to the global SDK providers.
|
|
45
|
+
|
|
46
|
+
The standard activation path is the ``OTEL_CONFIG_FILE`` environment variable,
|
|
47
|
+
which the SDK's configurator picks up automatically. Set the variable and run
|
|
48
|
+
your app under ``opentelemetry-instrument``; no code change is required.
|
|
49
|
+
|
|
50
|
+
For programmatic use:
|
|
51
|
+
|
|
52
|
+
.. code-block:: python
|
|
53
|
+
|
|
54
|
+
from opentelemetry.configuration import configure_sdk, load_config_file
|
|
55
|
+
|
|
56
|
+
config = load_config_file("otel-config.yaml")
|
|
57
|
+
configure_sdk(config)
|
|
58
|
+
|
|
59
|
+
Installation
|
|
60
|
+
------------
|
|
61
|
+
|
|
62
|
+
::
|
|
63
|
+
|
|
64
|
+
pip install opentelemetry-configuration
|
|
65
|
+
|
|
66
|
+
References
|
|
67
|
+
----------
|
|
68
|
+
|
|
69
|
+
* `OpenTelemetry declarative configuration specification
|
|
70
|
+
<https://opentelemetry.io/docs/specs/otel/configuration/>`_
|
|
71
|
+
* `Language support status matrix
|
|
72
|
+
<https://github.com/open-telemetry/opentelemetry-configuration/blob/main/language-support-status.md#python>`_
|
|
73
|
+
* `OpenTelemetry Project <https://opentelemetry.io/>`_
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
OpenTelemetry Python SDK Declarative Configuration
|
|
2
|
+
===================================================
|
|
3
|
+
|
|
4
|
+
|pypi|
|
|
5
|
+
|
|
6
|
+
.. |pypi| image:: https://badge.fury.io/py/opentelemetry-configuration.svg
|
|
7
|
+
:target: https://pypi.org/project/opentelemetry-configuration/
|
|
8
|
+
|
|
9
|
+
.. warning::
|
|
10
|
+
|
|
11
|
+
This package is **experimental**. The API surface, type names, and behaviour
|
|
12
|
+
may change between minor versions. Pin a specific version in production.
|
|
13
|
+
|
|
14
|
+
This package implements the `OpenTelemetry declarative configuration
|
|
15
|
+
specification <https://opentelemetry.io/docs/specs/otel/configuration/>`_ for
|
|
16
|
+
the Python SDK. It parses a YAML or JSON configuration file (or
|
|
17
|
+
programmatically-constructed configuration model) into typed dataclasses and
|
|
18
|
+
applies the result to the global SDK providers.
|
|
19
|
+
|
|
20
|
+
The standard activation path is the ``OTEL_CONFIG_FILE`` environment variable,
|
|
21
|
+
which the SDK's configurator picks up automatically. Set the variable and run
|
|
22
|
+
your app under ``opentelemetry-instrument``; no code change is required.
|
|
23
|
+
|
|
24
|
+
For programmatic use:
|
|
25
|
+
|
|
26
|
+
.. code-block:: python
|
|
27
|
+
|
|
28
|
+
from opentelemetry.configuration import configure_sdk, load_config_file
|
|
29
|
+
|
|
30
|
+
config = load_config_file("otel-config.yaml")
|
|
31
|
+
configure_sdk(config)
|
|
32
|
+
|
|
33
|
+
Installation
|
|
34
|
+
------------
|
|
35
|
+
|
|
36
|
+
::
|
|
37
|
+
|
|
38
|
+
pip install opentelemetry-configuration
|
|
39
|
+
|
|
40
|
+
References
|
|
41
|
+
----------
|
|
42
|
+
|
|
43
|
+
* `OpenTelemetry declarative configuration specification
|
|
44
|
+
<https://opentelemetry.io/docs/specs/otel/configuration/>`_
|
|
45
|
+
* `Language support status matrix
|
|
46
|
+
<https://github.com/open-telemetry/opentelemetry-configuration/blob/main/language-support-status.md#python>`_
|
|
47
|
+
* `OpenTelemetry Project <https://opentelemetry.io/>`_
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "opentelemetry-configuration"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "OpenTelemetry Python Declarative Configuration (experimental)"
|
|
9
|
+
readme = "README.rst"
|
|
10
|
+
license = "Apache-2.0"
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "OpenTelemetry Authors", email = "cncf-opentelemetry-contributors@lists.cncf.io" },
|
|
14
|
+
]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 3 - Alpha",
|
|
17
|
+
"Framework :: OpenTelemetry",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"Programming Language :: Python",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.10",
|
|
22
|
+
"Programming Language :: Python :: 3.11",
|
|
23
|
+
"Programming Language :: Python :: 3.12",
|
|
24
|
+
"Programming Language :: Python :: 3.13",
|
|
25
|
+
"Programming Language :: Python :: 3.14",
|
|
26
|
+
"Typing :: Typed",
|
|
27
|
+
]
|
|
28
|
+
dependencies = [
|
|
29
|
+
"opentelemetry-api == 1.44.0",
|
|
30
|
+
"opentelemetry-sdk == 1.44.0",
|
|
31
|
+
"pyyaml >= 5.4",
|
|
32
|
+
"jsonschema >= 4.0",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
[project.urls]
|
|
36
|
+
Homepage = "https://github.com/open-telemetry/opentelemetry-python/tree/main/opentelemetry-configuration"
|
|
37
|
+
Repository = "https://github.com/open-telemetry/opentelemetry-python"
|
|
38
|
+
|
|
39
|
+
[tool.hatch.version]
|
|
40
|
+
path = "src/opentelemetry/configuration/version/__init__.py"
|
|
41
|
+
|
|
42
|
+
[tool.hatch.build.targets.sdist]
|
|
43
|
+
include = [
|
|
44
|
+
"/src",
|
|
45
|
+
"/tests",
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
[tool.hatch.build.targets.wheel]
|
|
49
|
+
packages = ["src/opentelemetry"]
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# SDK Declarative Configuration
|
|
2
|
+
|
|
3
|
+
This package implements [OpenTelemetry file-based configuration](https://opentelemetry.io/docs/specs/otel/configuration).
|
|
4
|
+
|
|
5
|
+
## Files
|
|
6
|
+
|
|
7
|
+
- `schema.json`: vendored copy of the [OpenTelemetry configuration JSON schema](https://github.com/open-telemetry/opentelemetry-configuration)
|
|
8
|
+
- `models.py`: Python dataclasses generated from `schema.json` by [datamodel-code-generator](https://github.com/koxudaxi/datamodel-code-generator), using a custom template from `opentelemetry-configuration/codegen/` (see that directory's README for details)
|
|
9
|
+
|
|
10
|
+
## Spec conformance
|
|
11
|
+
|
|
12
|
+
Which fields of the configuration schema the Python SDK actually applies at runtime is tracked in the shared [language support status matrix](https://github.com/open-telemetry/opentelemetry-configuration/blob/main/language-support-status.md#python) in the `opentelemetry-configuration` repository. The Python column is the source of truth; update [`schema/meta_schema_language_python.yaml`](https://github.com/open-telemetry/opentelemetry-configuration/blob/main/schema/meta_schema_language_python.yaml) there when wiring lands or changes, and the matrix regenerates.
|
|
13
|
+
|
|
14
|
+
## Updating the schema
|
|
15
|
+
|
|
16
|
+
1. Pick the desired release tag from the [opentelemetry-configuration releases](https://github.com/open-telemetry/opentelemetry-configuration/releases) and overwrite `opentelemetry-configuration/src/opentelemetry/configuration/schema.json` with the `opentelemetry_configuration.json` published on that release.
|
|
17
|
+
|
|
18
|
+
2. Regenerate `models.py`:
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
tox -e generate-config-from-jsonschema
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
3. Update any version string references in tests and source:
|
|
25
|
+
|
|
26
|
+
```sh
|
|
27
|
+
grep -r "OLD_VERSION" opentelemetry-configuration/
|
|
28
|
+
```
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Copyright The OpenTelemetry Authors
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
"""OpenTelemetry SDK Declarative Configuration.
|
|
5
|
+
|
|
6
|
+
This package implements the OpenTelemetry declarative configuration
|
|
7
|
+
specification for the Python SDK. Load a YAML or JSON configuration file
|
|
8
|
+
(or build a configuration model programmatically) and apply it to the
|
|
9
|
+
process-global SDK providers.
|
|
10
|
+
|
|
11
|
+
The standard activation path is the ``OTEL_CONFIG_FILE`` environment
|
|
12
|
+
variable, which the SDK's configurator picks up automatically. For
|
|
13
|
+
programmatic use:
|
|
14
|
+
|
|
15
|
+
>>> from opentelemetry.configuration import (
|
|
16
|
+
... load_config_file, configure_sdk,
|
|
17
|
+
... )
|
|
18
|
+
>>> config = load_config_file("otel-config.yaml")
|
|
19
|
+
>>> configure_sdk(config)
|
|
20
|
+
|
|
21
|
+
Construct a configuration programmatically and apply it:
|
|
22
|
+
|
|
23
|
+
>>> from opentelemetry.configuration import (
|
|
24
|
+
... OpenTelemetryConfiguration, configure_sdk,
|
|
25
|
+
... )
|
|
26
|
+
>>> configure_sdk(OpenTelemetryConfiguration(file_format="1.0-rc.1"))
|
|
27
|
+
|
|
28
|
+
This package is **experimental**. The API surface, type names, and
|
|
29
|
+
behaviour may change between minor versions.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
from opentelemetry.configuration._exceptions import ConfigurationError
|
|
33
|
+
from opentelemetry.configuration._sdk import configure_sdk
|
|
34
|
+
from opentelemetry.configuration.file._loader import load_config_file
|
|
35
|
+
from opentelemetry.configuration.models import OpenTelemetryConfiguration
|
|
36
|
+
|
|
37
|
+
__all__ = [
|
|
38
|
+
"ConfigurationError",
|
|
39
|
+
"OpenTelemetryConfiguration",
|
|
40
|
+
"configure_sdk",
|
|
41
|
+
"load_config_file",
|
|
42
|
+
]
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
# Copyright The OpenTelemetry Authors
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import dataclasses
|
|
7
|
+
import inspect
|
|
8
|
+
import logging
|
|
9
|
+
from collections.abc import Callable
|
|
10
|
+
from typing import Any, Protocol
|
|
11
|
+
from urllib.parse import urlparse
|
|
12
|
+
|
|
13
|
+
from opentelemetry.configuration._exceptions import ConfigurationError
|
|
14
|
+
from opentelemetry.util._importlib_metadata import entry_points
|
|
15
|
+
|
|
16
|
+
_logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _additional_properties(cls):
|
|
20
|
+
"""Decorator for dataclasses whose JSON Schema sets additionalProperties.
|
|
21
|
+
|
|
22
|
+
Wraps the dataclass-generated ``__init__`` so that extra keyword
|
|
23
|
+
arguments are captured into an ``additional_properties`` instance
|
|
24
|
+
attribute instead of raising ``TypeError``. This lets user-defined
|
|
25
|
+
component names flow through the config pipeline without modifying
|
|
26
|
+
the codegen output for built-in fields.
|
|
27
|
+
|
|
28
|
+
Applied automatically by the custom template in ``opentelemetry-sdk/codegen/``
|
|
29
|
+
when ``additionalPropertiesType`` is present in the template context
|
|
30
|
+
(set by ``datamodel-codegen`` for schema types with ``additionalProperties``).
|
|
31
|
+
"""
|
|
32
|
+
original_init = cls.__init__
|
|
33
|
+
original_sig = inspect.signature(original_init)
|
|
34
|
+
known_fields = frozenset(f.name for f in dataclasses.fields(cls))
|
|
35
|
+
|
|
36
|
+
def _init(self, **kwargs):
|
|
37
|
+
known = {k: v for k, v in kwargs.items() if k in known_fields}
|
|
38
|
+
extra = {k: v for k, v in kwargs.items() if k not in known_fields}
|
|
39
|
+
original_init(self, **known)
|
|
40
|
+
self.additional_properties = extra
|
|
41
|
+
|
|
42
|
+
# Preserve the original parameter list for IDE autocompletion and
|
|
43
|
+
# inspect.signature(), adding **kwargs to signal extras are accepted.
|
|
44
|
+
# setattr used because pyright rejects direct __signature__ assignment.
|
|
45
|
+
params = list(original_sig.parameters.values())
|
|
46
|
+
params.append(inspect.Parameter("kwargs", inspect.Parameter.VAR_KEYWORD))
|
|
47
|
+
setattr(_init, "__signature__", original_sig.replace(parameters=params)) # noqa: B010
|
|
48
|
+
|
|
49
|
+
cls.__init__ = _init
|
|
50
|
+
return cls
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def load_entry_point(group: str, name: str) -> type:
|
|
54
|
+
"""Load a plugin class from an entry point group by name.
|
|
55
|
+
|
|
56
|
+
Returns the loaded class — callers are responsible for instantiation
|
|
57
|
+
with whatever arguments their config requires.
|
|
58
|
+
|
|
59
|
+
Raises:
|
|
60
|
+
ConfigurationError: If the entry point is not found or fails to load.
|
|
61
|
+
"""
|
|
62
|
+
try:
|
|
63
|
+
ep = next(iter(entry_points(group=group, name=name)), None)
|
|
64
|
+
if ep is None:
|
|
65
|
+
raise ConfigurationError(
|
|
66
|
+
f"Plugin '{name}' not found in group '{group}'. "
|
|
67
|
+
"Make sure the package providing this plugin is installed."
|
|
68
|
+
)
|
|
69
|
+
return ep.load()
|
|
70
|
+
except ConfigurationError:
|
|
71
|
+
raise
|
|
72
|
+
except Exception as exc:
|
|
73
|
+
raise ConfigurationError(
|
|
74
|
+
f"Failed to load plugin '{name}' from group '{group}': {exc}"
|
|
75
|
+
) from exc
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class _ComponentConfig(Protocol):
|
|
79
|
+
"""Protocol for config dataclasses decorated with @_additional_properties.
|
|
80
|
+
|
|
81
|
+
Values in ``additional_properties`` are nested config dicts (suitable
|
|
82
|
+
for ``**kwargs`` splatting to the user-defined component class) or
|
|
83
|
+
``None`` (when the YAML uses ``my_plugin:`` or ``my_plugin: null``).
|
|
84
|
+
|
|
85
|
+
Note: the generated models declare ``additional_properties`` as a
|
|
86
|
+
``ClassVar`` even though the decorator assigns it as an instance
|
|
87
|
+
attribute at runtime. This is tolerated by pyright in ``standard``
|
|
88
|
+
mode but flagged in ``strict`` mode. See #5268.
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
additional_properties: dict[str, dict[str, Any] | None]
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _resolve_component(
|
|
95
|
+
config: _ComponentConfig,
|
|
96
|
+
registry: dict[str, Callable[[Any], Any]],
|
|
97
|
+
entry_point_group: str,
|
|
98
|
+
component_type: str,
|
|
99
|
+
) -> Any:
|
|
100
|
+
"""Resolve a config dataclass to a component instance.
|
|
101
|
+
|
|
102
|
+
Checks built-in factories in ``registry`` first (by matching typed
|
|
103
|
+
field names on ``config``), then falls back to entry point loading
|
|
104
|
+
for plugin components found in ``config.additional_properties``.
|
|
105
|
+
|
|
106
|
+
The JSON schema enforces exactly one component per config block
|
|
107
|
+
(``minProperties: 1, maxProperties: 1``). If multiple typed fields or
|
|
108
|
+
``additional_properties`` entries are set (e.g. when schema validation
|
|
109
|
+
is bypassed), the first registry match wins.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
config: A dataclass with ``additional_properties`` (from the
|
|
113
|
+
``@_additional_properties`` decorator).
|
|
114
|
+
registry: Mapping of built-in component names to factory
|
|
115
|
+
callables. Each factory receives the field value from config.
|
|
116
|
+
entry_point_group: The entry point group name for plugin loading.
|
|
117
|
+
component_type: Human-readable name for error messages
|
|
118
|
+
(e.g. "span exporter").
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
The resolved component instance.
|
|
122
|
+
|
|
123
|
+
Raises:
|
|
124
|
+
ConfigurationError: If no component type is specified in config.
|
|
125
|
+
"""
|
|
126
|
+
for name, factory in registry.items():
|
|
127
|
+
value = getattr(config, name, None)
|
|
128
|
+
if value is not None:
|
|
129
|
+
return factory(value)
|
|
130
|
+
if config.additional_properties:
|
|
131
|
+
name, plugin_config = next(iter(config.additional_properties.items()))
|
|
132
|
+
return load_entry_point(entry_point_group, name)(
|
|
133
|
+
**(plugin_config or {})
|
|
134
|
+
)
|
|
135
|
+
raise ConfigurationError(f"No {component_type} type specified in config.")
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _parse_headers(
|
|
139
|
+
headers: list | None,
|
|
140
|
+
headers_list: str | None,
|
|
141
|
+
) -> dict[str, str] | None:
|
|
142
|
+
"""Merge headers struct and headers_list into a dict.
|
|
143
|
+
|
|
144
|
+
Returns None if neither is set, letting the exporter read env vars.
|
|
145
|
+
headers struct takes priority over headers_list for the same key.
|
|
146
|
+
"""
|
|
147
|
+
if headers is None and headers_list is None:
|
|
148
|
+
return None
|
|
149
|
+
result: dict[str, str] = {}
|
|
150
|
+
if headers_list:
|
|
151
|
+
for item in headers_list.split(","):
|
|
152
|
+
item = item.strip()
|
|
153
|
+
if "=" in item:
|
|
154
|
+
key, value = item.split("=", 1)
|
|
155
|
+
result[key.strip()] = value.strip()
|
|
156
|
+
elif item:
|
|
157
|
+
_logger.warning(
|
|
158
|
+
"Invalid header pair in headers_list (missing '='): %s",
|
|
159
|
+
item,
|
|
160
|
+
)
|
|
161
|
+
if headers:
|
|
162
|
+
for pair in headers:
|
|
163
|
+
if isinstance(pair, dict):
|
|
164
|
+
result[pair["name"]] = pair.get("value") or ""
|
|
165
|
+
else:
|
|
166
|
+
result[pair.name] = pair.value or ""
|
|
167
|
+
return result
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _map_compression(
|
|
171
|
+
value: str | None,
|
|
172
|
+
compression_enum: type,
|
|
173
|
+
*,
|
|
174
|
+
allow_deflate: bool = False,
|
|
175
|
+
) -> object | None:
|
|
176
|
+
"""Map a compression string to the given Compression enum value."""
|
|
177
|
+
if value is None:
|
|
178
|
+
return None
|
|
179
|
+
|
|
180
|
+
value_lower = value.lower()
|
|
181
|
+
supports_deflate = hasattr(compression_enum, "Deflate")
|
|
182
|
+
|
|
183
|
+
if value_lower == "none":
|
|
184
|
+
return None
|
|
185
|
+
if value_lower == "gzip":
|
|
186
|
+
return compression_enum.Gzip # type: ignore[attr-defined]
|
|
187
|
+
if value_lower == "deflate" and allow_deflate and supports_deflate:
|
|
188
|
+
return compression_enum.Deflate # type: ignore[attr-defined]
|
|
189
|
+
|
|
190
|
+
supported_values = ["'gzip'", "'none'"]
|
|
191
|
+
if allow_deflate and supports_deflate:
|
|
192
|
+
supported_values.insert(1, "'deflate'")
|
|
193
|
+
|
|
194
|
+
raise ConfigurationError(
|
|
195
|
+
f"Unsupported compression value '{value}'. Supported values: "
|
|
196
|
+
f"{', '.join(supported_values)}."
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def _parse_otlp_file_output_stream(output_stream: str | None) -> str | None:
|
|
201
|
+
"""Resolve an output_stream value to a file path, or None for stdout.
|
|
202
|
+
|
|
203
|
+
Per the OTel file exporter spec, output_stream is "stdout" (or
|
|
204
|
+
None, which means the same), or a "file://" URI giving a path.
|
|
205
|
+
"""
|
|
206
|
+
if output_stream is None or output_stream == "stdout":
|
|
207
|
+
return None
|
|
208
|
+
try:
|
|
209
|
+
parsed = urlparse(output_stream)
|
|
210
|
+
except ValueError as exc:
|
|
211
|
+
raise ConfigurationError(
|
|
212
|
+
f"Failed to parse output_stream '{output_stream}' for "
|
|
213
|
+
f"otlp_file_development exporter: {exc}"
|
|
214
|
+
) from exc
|
|
215
|
+
is_local_file_uri = (
|
|
216
|
+
parsed.scheme == "file"
|
|
217
|
+
and parsed.netloc in ("", "localhost")
|
|
218
|
+
and bool(parsed.path)
|
|
219
|
+
)
|
|
220
|
+
has_extra_components = parsed.params or parsed.query or parsed.fragment
|
|
221
|
+
if is_local_file_uri and not has_extra_components:
|
|
222
|
+
path = parsed.path
|
|
223
|
+
if not path.startswith("/"):
|
|
224
|
+
raise ConfigurationError(
|
|
225
|
+
f"Unsupported output_stream '{output_stream}' for "
|
|
226
|
+
"otlp_file_development exporter. Path must be absolute."
|
|
227
|
+
)
|
|
228
|
+
if path.endswith("/"):
|
|
229
|
+
raise ConfigurationError(
|
|
230
|
+
f"Unsupported output_stream '{output_stream}' for "
|
|
231
|
+
"otlp_file_development exporter. Path must be a file, "
|
|
232
|
+
"not a directory."
|
|
233
|
+
)
|
|
234
|
+
return path
|
|
235
|
+
raise ConfigurationError(
|
|
236
|
+
f"Unsupported output_stream '{output_stream}' for otlp_file_development "
|
|
237
|
+
"exporter. Supported values: stdout, file://<path>."
|
|
238
|
+
)
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# Copyright The OpenTelemetry Authors
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
"""Recursive dict-to-dataclass conversion for parsed config data.
|
|
5
|
+
|
|
6
|
+
The YAML/JSON loader produces nested dicts. Factory functions expect typed
|
|
7
|
+
dataclass instances (e.g. ``TracerProvider``, ``SpanProcessor``). This module
|
|
8
|
+
walks each field's type annotation and converts nested dicts into their
|
|
9
|
+
corresponding dataclass types.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from collections.abc import Mapping
|
|
15
|
+
from dataclasses import fields, is_dataclass
|
|
16
|
+
from enum import Enum
|
|
17
|
+
from types import UnionType
|
|
18
|
+
from typing import Any, TypeVar, Union, get_args, get_origin, get_type_hints
|
|
19
|
+
|
|
20
|
+
_T = TypeVar("_T")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _unwrap_optional(type_hint: Any) -> Any:
|
|
24
|
+
"""Strip ``None`` from a ``X | None`` / ``Optional[X]`` annotation.
|
|
25
|
+
|
|
26
|
+
Returns the unwrapped type, or the original hint if not a union with None.
|
|
27
|
+
"""
|
|
28
|
+
origin = get_origin(type_hint)
|
|
29
|
+
if origin is UnionType or origin is Union:
|
|
30
|
+
non_none = [t for t in get_args(type_hint) if t is not type(None)]
|
|
31
|
+
if len(non_none) == 1:
|
|
32
|
+
return non_none[0]
|
|
33
|
+
return type_hint
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _convert_value(value: Any, type_hint: Any) -> Any:
|
|
37
|
+
"""Convert a value according to its type hint.
|
|
38
|
+
|
|
39
|
+
Recursively converts dicts to dataclasses and lists of dicts to lists of
|
|
40
|
+
dataclasses. Other values (primitives, enums, ``dict[str, Any]`` aliases)
|
|
41
|
+
pass through unchanged.
|
|
42
|
+
"""
|
|
43
|
+
if value is None:
|
|
44
|
+
return None
|
|
45
|
+
|
|
46
|
+
unwrapped = _unwrap_optional(type_hint)
|
|
47
|
+
origin = get_origin(unwrapped)
|
|
48
|
+
|
|
49
|
+
# list[X] — recurse on each element
|
|
50
|
+
if origin is list and isinstance(value, list):
|
|
51
|
+
args = get_args(unwrapped)
|
|
52
|
+
if args:
|
|
53
|
+
item_type = args[0]
|
|
54
|
+
return [_convert_value(item, item_type) for item in value]
|
|
55
|
+
return value
|
|
56
|
+
|
|
57
|
+
# Direct dataclass type — recurse
|
|
58
|
+
if (
|
|
59
|
+
isinstance(unwrapped, type)
|
|
60
|
+
and is_dataclass(unwrapped)
|
|
61
|
+
and isinstance(value, dict)
|
|
62
|
+
):
|
|
63
|
+
return _dict_to_dataclass(value, unwrapped)
|
|
64
|
+
|
|
65
|
+
# Enum type — coerce string/value to the Enum member
|
|
66
|
+
if (
|
|
67
|
+
isinstance(unwrapped, type)
|
|
68
|
+
and issubclass(unwrapped, Enum)
|
|
69
|
+
and not isinstance(value, unwrapped)
|
|
70
|
+
):
|
|
71
|
+
return unwrapped(value)
|
|
72
|
+
|
|
73
|
+
return value
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _dict_to_dataclass(data: Mapping[str, Any], cls: type[_T]) -> _T:
|
|
77
|
+
"""Recursively convert a mapping to a dataclass instance.
|
|
78
|
+
|
|
79
|
+
For each key in ``data``:
|
|
80
|
+
- If it matches a known dataclass field, the value is converted according
|
|
81
|
+
to that field's type annotation (recursing for nested dataclasses).
|
|
82
|
+
- Unknown keys are passed through as kwargs; classes decorated with
|
|
83
|
+
``@_additional_properties`` will capture them on the instance's
|
|
84
|
+
``additional_properties`` attribute.
|
|
85
|
+
|
|
86
|
+
``ClassVar`` fields (e.g. the ``additional_properties`` annotation on
|
|
87
|
+
decorated dataclasses) are ignored as expected.
|
|
88
|
+
|
|
89
|
+
Raises:
|
|
90
|
+
TypeError: If ``cls`` is not a dataclass type.
|
|
91
|
+
"""
|
|
92
|
+
if not is_dataclass(cls):
|
|
93
|
+
raise TypeError(f"{cls.__name__} is not a dataclass")
|
|
94
|
+
|
|
95
|
+
# Annotated as ``dict[str, Any]`` so astroid stops tracing into
|
|
96
|
+
# ``get_type_hints`` — under pylint 3.x that path leads into
|
|
97
|
+
# Python 3.14's ``annotationlib`` (which uses t-strings) and crashes.
|
|
98
|
+
hints: dict[str, Any] = dict(get_type_hints(cls, include_extras=False))
|
|
99
|
+
known_fields = {f.name for f in fields(cls)}
|
|
100
|
+
kwargs: dict[str, Any] = {}
|
|
101
|
+
|
|
102
|
+
for key, value in data.items():
|
|
103
|
+
# The OTel configuration schema uses "/" as a namespace separator for
|
|
104
|
+
# development/experimental features (e.g. "otlp_file/development",
|
|
105
|
+
# "instrumentation/development"). Python identifiers cannot contain
|
|
106
|
+
# "/", so the corresponding dataclass fields use "_" instead (e.g.
|
|
107
|
+
# "otlp_file_development"). Without this normalisation the key would
|
|
108
|
+
# not match any known field and would fall through to
|
|
109
|
+
# additional_properties, causing the factory lookup to fail silently.
|
|
110
|
+
field_key = key.replace("/", "_")
|
|
111
|
+
if field_key in known_fields:
|
|
112
|
+
type_hint = hints.get(field_key)
|
|
113
|
+
kwargs[field_key] = _convert_value(value, type_hint)
|
|
114
|
+
else:
|
|
115
|
+
# Unknown key — @_additional_properties decorator will capture it.
|
|
116
|
+
kwargs[key] = value
|
|
117
|
+
|
|
118
|
+
return cls(**kwargs)
|