dash0-opentelemetry-configuration 0.65b0.post1__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.
- dash0_opentelemetry_configuration-0.65b0.post1/.gitignore +28 -0
- dash0_opentelemetry_configuration-0.65b0.post1/PKG-INFO +72 -0
- dash0_opentelemetry_configuration-0.65b0.post1/README.rst +61 -0
- dash0_opentelemetry_configuration-0.65b0.post1/pyproject.toml +28 -0
- dash0_opentelemetry_configuration-0.65b0.post1/src/opentelemetry/_configuration/README.md +28 -0
- dash0_opentelemetry_configuration-0.65b0.post1/src/opentelemetry/_configuration/__init__.py +42 -0
- dash0_opentelemetry_configuration-0.65b0.post1/src/opentelemetry/_configuration/_common.py +238 -0
- dash0_opentelemetry_configuration-0.65b0.post1/src/opentelemetry/_configuration/_conversion.py +118 -0
- dash0_opentelemetry_configuration-0.65b0.post1/src/opentelemetry/_configuration/_exceptions.py +59 -0
- dash0_opentelemetry_configuration-0.65b0.post1/src/opentelemetry/_configuration/_generated_validator.py +2936 -0
- dash0_opentelemetry_configuration-0.65b0.post1/src/opentelemetry/_configuration/_logger_provider.py +288 -0
- dash0_opentelemetry_configuration-0.65b0.post1/src/opentelemetry/_configuration/_meter_provider.py +626 -0
- dash0_opentelemetry_configuration-0.65b0.post1/src/opentelemetry/_configuration/_propagator.py +150 -0
- dash0_opentelemetry_configuration-0.65b0.post1/src/opentelemetry/_configuration/_resource.py +219 -0
- dash0_opentelemetry_configuration-0.65b0.post1/src/opentelemetry/_configuration/_sdk.py +68 -0
- dash0_opentelemetry_configuration-0.65b0.post1/src/opentelemetry/_configuration/_tracer_provider.py +508 -0
- dash0_opentelemetry_configuration-0.65b0.post1/src/opentelemetry/_configuration/_validation_exceptions.py +62 -0
- dash0_opentelemetry_configuration-0.65b0.post1/src/opentelemetry/_configuration/file/__init__.py +60 -0
- dash0_opentelemetry_configuration-0.65b0.post1/src/opentelemetry/_configuration/file/_env_substitution.py +92 -0
- dash0_opentelemetry_configuration-0.65b0.post1/src/opentelemetry/_configuration/file/_loader.py +237 -0
- dash0_opentelemetry_configuration-0.65b0.post1/src/opentelemetry/_configuration/instrumentation.py +78 -0
- dash0_opentelemetry_configuration-0.65b0.post1/src/opentelemetry/_configuration/models.py +835 -0
- dash0_opentelemetry_configuration-0.65b0.post1/src/opentelemetry/_configuration/schema.json +2576 -0
- dash0_opentelemetry_configuration-0.65b0.post1/src/opentelemetry/_configuration/version/__init__.py +4 -0
- dash0_opentelemetry_configuration-0.65b0.post1/src/opentelemetry/configuration/__init__.py +5 -0
- dash0_opentelemetry_configuration-0.65b0.post1/src/opentelemetry/configuration/file/__init__.py +5 -0
- dash0_opentelemetry_configuration-0.65b0.post1/src/opentelemetry/configuration/instrumentation.py +4 -0
- dash0_opentelemetry_configuration-0.65b0.post1/src/opentelemetry/configuration/models.py +4 -0
- dash0_opentelemetry_configuration-0.65b0.post1/src/opentelemetry/configuration/version/__init__.py +4 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Byte-compiled / optimized
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# Distribution / packaging
|
|
7
|
+
build/
|
|
8
|
+
dist/
|
|
9
|
+
*.egg-info/
|
|
10
|
+
.eggs/
|
|
11
|
+
|
|
12
|
+
# Test / coverage
|
|
13
|
+
.pytest_cache/
|
|
14
|
+
.coverage
|
|
15
|
+
htmlcov/
|
|
16
|
+
.tox/
|
|
17
|
+
|
|
18
|
+
# Environments
|
|
19
|
+
.venv/
|
|
20
|
+
venv/
|
|
21
|
+
.env
|
|
22
|
+
|
|
23
|
+
# Editor / OS
|
|
24
|
+
.idea/
|
|
25
|
+
.vscode/
|
|
26
|
+
.DS_Store
|
|
27
|
+
|
|
28
|
+
tests/e2e-kubernetes/uv.lock
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: dash0-opentelemetry-configuration
|
|
3
|
+
Version: 0.65b0.post1
|
|
4
|
+
Summary: Declarative configuration for the Dash0 OpenTelemetry distribution
|
|
5
|
+
License-Expression: Apache-2.0
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Requires-Dist: opentelemetry-api==1.44.0
|
|
8
|
+
Requires-Dist: opentelemetry-sdk==1.44.0
|
|
9
|
+
Requires-Dist: pyyaml
|
|
10
|
+
Description-Content-Type: text/x-rst
|
|
11
|
+
|
|
12
|
+
Dash0 OpenTelemetry Declarative Configuration
|
|
13
|
+
=============================================
|
|
14
|
+
|
|
15
|
+
Declarative (file-based) SDK configuration for the Dash0 OpenTelemetry Python
|
|
16
|
+
distribution, read from ``OTEL_CONFIG_FILE``.
|
|
17
|
+
|
|
18
|
+
The package is experimental, tracking a configuration format that is still at
|
|
19
|
+
``1.0-rc.1``.
|
|
20
|
+
|
|
21
|
+
Do not edit this package by hand
|
|
22
|
+
--------------------------------
|
|
23
|
+
|
|
24
|
+
Everything under ``src/`` and the whole of ``pyproject.toml`` are generated by
|
|
25
|
+
``scripts/vendor_configuration.py`` from the ``dash0`` branch of
|
|
26
|
+
https://github.com/dash0hq/opentelemetry-python. The ``UPSTREAM`` file records
|
|
27
|
+
the exact commit. A hand edit here is reverted by the next run of that script,
|
|
28
|
+
and the ``vendored-configuration`` job in CI fails while it is still present.
|
|
29
|
+
|
|
30
|
+
Two files are exempt, because the script leaves them alone: this README, and
|
|
31
|
+
``src/opentelemetry/_configuration/version/__init__.py``, which holds the
|
|
32
|
+
released version and is advanced by ``scripts/bump_for_release.py``.
|
|
33
|
+
|
|
34
|
+
Where a change belongs
|
|
35
|
+
----------------------
|
|
36
|
+
|
|
37
|
+
Changes to how configuration files are parsed, validated or applied go on the
|
|
38
|
+
fork's ``dash0`` branch, as commits on top of an upstream commit. That is what
|
|
39
|
+
makes them survive upstream moving: rebase the branch onto the new upstream
|
|
40
|
+
commit, then run::
|
|
41
|
+
|
|
42
|
+
python3 scripts/vendor_configuration.py --commit <new sha>
|
|
43
|
+
|
|
44
|
+
Changes to how the code is packaged for this distribution go in
|
|
45
|
+
``scripts/vendor_configuration.py``. It owns the rename from
|
|
46
|
+
``opentelemetry.configuration`` to ``opentelemetry._configuration``, the
|
|
47
|
+
re-export shims at the path the SDK imports, and the dependency pins, which it
|
|
48
|
+
copies from the distribution's own pins rather than repeating them.
|
|
49
|
+
|
|
50
|
+
Why the implementation sits at a private path
|
|
51
|
+
---------------------------------------------
|
|
52
|
+
|
|
53
|
+
The implementation is installed as ``opentelemetry._configuration``, a path
|
|
54
|
+
upstream does not own, and ``opentelemetry.configuration`` holds only star
|
|
55
|
+
re-export shims. ``opentelemetry/sdk/_configuration/__init__.py`` imports
|
|
56
|
+
``opentelemetry.configuration`` by name, so the shims are what make
|
|
57
|
+
``OTEL_CONFIG_FILE`` work, while the private path means installing this
|
|
58
|
+
alongside a future published ``opentelemetry-configuration`` cannot collide
|
|
59
|
+
file for file. The pyproto exporter packages in this workspace use the same
|
|
60
|
+
arrangement.
|
|
61
|
+
|
|
62
|
+
What the fork changes
|
|
63
|
+
---------------------
|
|
64
|
+
|
|
65
|
+
Upstream validates configuration files with ``jsonschema``, which brings
|
|
66
|
+
``attrs``, ``referencing``, ``jsonschema-specifications`` and ``rpds-py``. All
|
|
67
|
+
four would land in the tree the injector adds to an application's
|
|
68
|
+
``sys.path``, and ``rpds-py`` is a compiled extension, so that tree would stop
|
|
69
|
+
being pure Python. The fork compiles the schema to plain Python with
|
|
70
|
+
``fastjsonschema`` at development time instead, leaving a validator that
|
|
71
|
+
imports ``decimal`` and nothing else. The fork's own test suite checks that
|
|
72
|
+
validator against ``jsonschema`` document by document.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
Dash0 OpenTelemetry Declarative Configuration
|
|
2
|
+
=============================================
|
|
3
|
+
|
|
4
|
+
Declarative (file-based) SDK configuration for the Dash0 OpenTelemetry Python
|
|
5
|
+
distribution, read from ``OTEL_CONFIG_FILE``.
|
|
6
|
+
|
|
7
|
+
The package is experimental, tracking a configuration format that is still at
|
|
8
|
+
``1.0-rc.1``.
|
|
9
|
+
|
|
10
|
+
Do not edit this package by hand
|
|
11
|
+
--------------------------------
|
|
12
|
+
|
|
13
|
+
Everything under ``src/`` and the whole of ``pyproject.toml`` are generated by
|
|
14
|
+
``scripts/vendor_configuration.py`` from the ``dash0`` branch of
|
|
15
|
+
https://github.com/dash0hq/opentelemetry-python. The ``UPSTREAM`` file records
|
|
16
|
+
the exact commit. A hand edit here is reverted by the next run of that script,
|
|
17
|
+
and the ``vendored-configuration`` job in CI fails while it is still present.
|
|
18
|
+
|
|
19
|
+
Two files are exempt, because the script leaves them alone: this README, and
|
|
20
|
+
``src/opentelemetry/_configuration/version/__init__.py``, which holds the
|
|
21
|
+
released version and is advanced by ``scripts/bump_for_release.py``.
|
|
22
|
+
|
|
23
|
+
Where a change belongs
|
|
24
|
+
----------------------
|
|
25
|
+
|
|
26
|
+
Changes to how configuration files are parsed, validated or applied go on the
|
|
27
|
+
fork's ``dash0`` branch, as commits on top of an upstream commit. That is what
|
|
28
|
+
makes them survive upstream moving: rebase the branch onto the new upstream
|
|
29
|
+
commit, then run::
|
|
30
|
+
|
|
31
|
+
python3 scripts/vendor_configuration.py --commit <new sha>
|
|
32
|
+
|
|
33
|
+
Changes to how the code is packaged for this distribution go in
|
|
34
|
+
``scripts/vendor_configuration.py``. It owns the rename from
|
|
35
|
+
``opentelemetry.configuration`` to ``opentelemetry._configuration``, the
|
|
36
|
+
re-export shims at the path the SDK imports, and the dependency pins, which it
|
|
37
|
+
copies from the distribution's own pins rather than repeating them.
|
|
38
|
+
|
|
39
|
+
Why the implementation sits at a private path
|
|
40
|
+
---------------------------------------------
|
|
41
|
+
|
|
42
|
+
The implementation is installed as ``opentelemetry._configuration``, a path
|
|
43
|
+
upstream does not own, and ``opentelemetry.configuration`` holds only star
|
|
44
|
+
re-export shims. ``opentelemetry/sdk/_configuration/__init__.py`` imports
|
|
45
|
+
``opentelemetry.configuration`` by name, so the shims are what make
|
|
46
|
+
``OTEL_CONFIG_FILE`` work, while the private path means installing this
|
|
47
|
+
alongside a future published ``opentelemetry-configuration`` cannot collide
|
|
48
|
+
file for file. The pyproto exporter packages in this workspace use the same
|
|
49
|
+
arrangement.
|
|
50
|
+
|
|
51
|
+
What the fork changes
|
|
52
|
+
---------------------
|
|
53
|
+
|
|
54
|
+
Upstream validates configuration files with ``jsonschema``, which brings
|
|
55
|
+
``attrs``, ``referencing``, ``jsonschema-specifications`` and ``rpds-py``. All
|
|
56
|
+
four would land in the tree the injector adds to an application's
|
|
57
|
+
``sys.path``, and ``rpds-py`` is a compiled extension, so that tree would stop
|
|
58
|
+
being pure Python. The fork compiles the schema to plain Python with
|
|
59
|
+
``fastjsonschema`` at development time instead, leaving a validator that
|
|
60
|
+
imports ``decimal`` and nothing else. The fork's own test suite checks that
|
|
61
|
+
validator against ``jsonschema`` document by document.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "dash0-opentelemetry-configuration"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "Declarative configuration for the Dash0 OpenTelemetry distribution"
|
|
9
|
+
readme = "README.rst"
|
|
10
|
+
license = "Apache-2.0"
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
dependencies = [
|
|
13
|
+
"opentelemetry-api == 1.44.0",
|
|
14
|
+
"opentelemetry-sdk == 1.44.0",
|
|
15
|
+
"pyyaml",
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
[tool.hatch.version]
|
|
19
|
+
path = "src/opentelemetry/_configuration/version/__init__.py"
|
|
20
|
+
|
|
21
|
+
[tool.hatch.build.targets.sdist]
|
|
22
|
+
include = ["/src"]
|
|
23
|
+
|
|
24
|
+
[tool.hatch.build.targets.wheel]
|
|
25
|
+
packages = ["src/opentelemetry"]
|
|
26
|
+
|
|
27
|
+
[tool.pytest.ini_options]
|
|
28
|
+
testpaths = ["tests"]
|
|
@@ -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
|
+
)
|
dash0_opentelemetry_configuration-0.65b0.post1/src/opentelemetry/_configuration/_conversion.py
ADDED
|
@@ -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)
|
dash0_opentelemetry_configuration-0.65b0.post1/src/opentelemetry/_configuration/_exceptions.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Copyright The OpenTelemetry Authors
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class ConfigurationError(Exception):
|
|
6
|
+
"""Raised when configuration loading, parsing, validation, or instantiation fails.
|
|
7
|
+
|
|
8
|
+
This includes errors from:
|
|
9
|
+
- File not found or inaccessible
|
|
10
|
+
- Invalid YAML/JSON syntax
|
|
11
|
+
- Schema validation failures
|
|
12
|
+
- Environment variable substitution errors
|
|
13
|
+
- Missing required SDK extensions (e.g., propagator packages not installed)
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class MissingDependencyError(ConfigurationError, ImportError):
|
|
18
|
+
"""Raised when an optional dependency is not installed.
|
|
19
|
+
|
|
20
|
+
Inherits from both :class:`ConfigurationError` and :class:`ImportError` to
|
|
21
|
+
maintain backwards compatibility with callers that catch ``ImportError``.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
package: The name of the missing package.
|
|
25
|
+
feature: Optional description of the feature that requires the package.
|
|
26
|
+
install_name: Optional package name used in the pip install command
|
|
27
|
+
(defaults to ``package``).
|
|
28
|
+
extras: Optional extras string for the pip install command.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
package: str,
|
|
34
|
+
feature: str | None = None,
|
|
35
|
+
install_name: str | None = None,
|
|
36
|
+
extras: str | None = None,
|
|
37
|
+
) -> None:
|
|
38
|
+
self.package = package
|
|
39
|
+
self.feature = feature
|
|
40
|
+
self.install_name = install_name or package
|
|
41
|
+
self.extras = extras
|
|
42
|
+
|
|
43
|
+
if extras:
|
|
44
|
+
install_cmd = f'pip install "{self.install_name}[{extras}]"'
|
|
45
|
+
else:
|
|
46
|
+
install_cmd = f"pip install {self.install_name}"
|
|
47
|
+
|
|
48
|
+
if feature:
|
|
49
|
+
message = (
|
|
50
|
+
f"{feature} requires '{package}'. "
|
|
51
|
+
f"Install it with: {install_cmd}"
|
|
52
|
+
)
|
|
53
|
+
else:
|
|
54
|
+
message = (
|
|
55
|
+
f"'{package}' is required but not installed. "
|
|
56
|
+
f"Install it with: {install_cmd}"
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
super().__init__(message)
|