Motivation
Our distro currently derives service.name as a best-effort fallback from sys.argv[0] (entry-point script basename, stripped of .py) when the user hasn't set OTEL_SERVICE_NAME and hasn't put service.name in OTEL_RESOURCE_ATTRIBUTES. See resource.py:131-143 and resource.py:197-239.
We can do better by reading the app's declarative metadata — the Python analog of what our Node.js distro already does with package.json.
State of the art (as of 2026-07)
Where the pattern has converged (adjacent languages):
- Node.js: community
opentelemetry-resource-detector-service reads package.json → name/version. Our own Dash0 Node distro follows this.
- Java / Spring Boot Starter: reads
META-INF/build-info.properties (build.project.artifact → service.name, build.version → service.version), then MANIFEST.MF, then spring.application.name.
- .NET auto-instrumentation: entry assembly reflection.
Both Java and Node deliberately read build-time-generated metadata, not source manifests, because installed apps often don't ship the source manifest. Python has a direct analog: importlib.metadata reads the installed .dist-info/METADATA, which is derived from pyproject.toml [project] at build time.
Proposed field mapping
| Source |
OTel attribute |
[project].name / dist-info Name |
service.name |
[project].version / dist-info Version |
service.version |
| — |
service.namespace (no cross-ecosystem precedent — skip) |
Do not map [project].description, authors, or other fields — no ecosystem precedent.
Proposed precedence (mirrors Java Spring Boot Starter)
```
OTEL_SERVICE_NAME env var
service.name in OTEL_RESOURCE_ATTRIBUTES env var
> importlib.metadata Name (for installed packages)
> pyproject.toml [project].name (walk-up from sys.argv[0])
> sys.argv[0] basename without .py (current fallback)
> unknown_service:{process.executable.name} (SDK default)
```
Same stack for service.version (without the env-var layers, which OTel doesn't standardize for version).
Two-tier discovery strategy
Primary — importlib.metadata for `pip install .` deployments. Use packages_distributions() + metadata(dist_name) to read the installed .dist-info/METADATA. No file-path discovery, no tomllib dependency, works in containers where source isn't shipped. Direct analog of what Java does with build-info.properties.
Secondary — pyproject.toml walk-up for `python src/app.py`-style source runs. Walk up from Path(sys.argv[0]).resolve().parent (not cwd — that's wrong for the common invocation pattern), stopping at .git or filesystem root. tomllib is 3.11+ stdlib; make this branch conditional on availability rather than pulling tomli as a hard dep.
Edge cases
| Scenario |
Behavior |
| App installed via `pip install .` |
importlib.metadata succeeds; no file walk needed |
| App run from source, single pyproject.toml |
File walk finds it |
Monorepo, multiple pyproject.toml files |
First walk-up wins = the one owning the entry-point file |
App has no pyproject.toml (legacy setup.py only) |
Fall through to current sys.argv[0] heuristic |
pyproject.toml has no [project] table (tool-only config) |
Fall through gracefully |
Container/deployed app — pyproject.toml not shipped |
importlib.metadata still works |
Python 3.8–3.10 without tomli installed |
Skip pyproject.toml file-reading path; importlib.metadata still works |
What to avoid
- No hyphen-to-underscore normalization on
project.name (Node community doesn't do it; no reason to diverge)
- No default `service.version = "0.0.0"` — absent is better than wrong
- Don't walk up from
cwd; wrong for python path/to/app.py invocations
- Don't add
tomli as a hard dependency for 3.8–3.10 support
Primary sources
Motivation
Our distro currently derives
service.nameas a best-effort fallback fromsys.argv[0](entry-point script basename, stripped of.py) when the user hasn't setOTEL_SERVICE_NAMEand hasn't putservice.nameinOTEL_RESOURCE_ATTRIBUTES. Seeresource.py:131-143andresource.py:197-239.We can do better by reading the app's declarative metadata — the Python analog of what our Node.js distro already does with
package.json.State of the art (as of 2026-07)
Where the pattern has converged (adjacent languages):
opentelemetry-resource-detector-servicereadspackage.json→name/version. Our own Dash0 Node distro follows this.META-INF/build-info.properties(build.project.artifact→service.name,build.version→service.version), thenMANIFEST.MF, thenspring.application.name.Both Java and Node deliberately read build-time-generated metadata, not source manifests, because installed apps often don't ship the source manifest. Python has a direct analog:
importlib.metadatareads the installed.dist-info/METADATA, which is derived frompyproject.toml [project]at build time.Proposed field mapping
[project].name/ dist-infoNameservice.name[project].version/ dist-infoVersionservice.versionservice.namespace(no cross-ecosystem precedent — skip)Do not map
[project].description, authors, or other fields — no ecosystem precedent.Proposed precedence (mirrors Java Spring Boot Starter)
```
OTEL_SERVICE_NAME env var
Same stack for
service.version(without the env-var layers, which OTel doesn't standardize for version).Two-tier discovery strategy
Primary —
importlib.metadatafor `pip install .` deployments. Usepackages_distributions()+metadata(dist_name)to read the installed.dist-info/METADATA. No file-path discovery, notomllibdependency, works in containers where source isn't shipped. Direct analog of what Java does withbuild-info.properties.Secondary —
pyproject.tomlwalk-up for `python src/app.py`-style source runs. Walk up fromPath(sys.argv[0]).resolve().parent(notcwd— that's wrong for the common invocation pattern), stopping at.gitor filesystem root.tomllibis 3.11+ stdlib; make this branch conditional on availability rather than pullingtomlias a hard dep.Edge cases
importlib.metadatasucceeds; no file walk neededpyproject.tomlfilespyproject.toml(legacysetup.pyonly)sys.argv[0]heuristicpyproject.tomlhas no[project]table (tool-only config)pyproject.tomlnot shippedimportlib.metadatastill workstomliinstalledimportlib.metadatastill worksWhat to avoid
project.name(Node community doesn't do it; no reason to diverge)cwd; wrong forpython path/to/app.pyinvocationstomlias a hard dependency for 3.8–3.10 supportPrimary sources
packages_distributions(),metadata()\"unknown-python-service\"default