diff --git a/CHANGELOG.md b/CHANGELOG.md index a2157b91..8ee3bd3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,35 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). --- +## v26.10.06 (2026-10-14) + +### Fixed + +- Honor standard OpenTelemetry service identity for newly created trace and + metric providers ([#151](https://github.com/fireflyframework/fireflyframework-pyfly/issues/151)). + An explicit nonempty `pyfly.observability.tracing.service-name`, including + `PYFLY_OBSERVABILITY_TRACING_SERVICE_NAME`, retains priority; otherwise use + `OTEL_SERVICE_NAME`, then `service.name` from `OTEL_RESOURCE_ATTRIBUTES`, + then `pyfly.app.name`, then `pyfly-app`. +- Preserve other SDK resource attributes and use SDK parsing for resource + environment values, including URL encoding. Remove the implicit framework + service-name default that previously masked standard environment identity. + +### Documentation + +- Explain identity precedence and deliberate overrides in the observability + guide and both book editions; examples leave the service name unset so a + platform-injected `OTEL_SERVICE_NAME` is respected. +- Synchronize package, installer, site, and book download examples with + `v26.10.06` (normalized package version `26.10.6`). + +### Compatibility + +- Existing global and user-defined providers keep their resources. Identity is + configured at provider creation; restart the process to apply changes to + process-global providers. This release does not redeploy running workloads + or rewrite historical telemetry. + ## v26.10.05 (2026-10-14) ### Changed diff --git a/README.md b/README.md index 13ca3940..42d84e54 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Firefly Framework Python 3.12+ License: Apache 2.0 - Version: 26.10.05 + Version: 26.10.06 Type Checked: mypy strict Code Style: Ruff Async First @@ -61,7 +61,7 @@ It covers the whole stack: dependency injection, configuration & profiles, the web layer, the Spring-Data `Repository` (derived queries, pagination, specifications, projections), DDD aggregates & `Money`, CQRS, domain events & event sourcing, messaging, HTTP clients, sagas, caching & resilience, security, observability, testing, scheduling, and going to production. Spring developers get a **Spring parity** callout at every turn. -**📥 Download the books shipped with v26.10.05:** **[English PDF](https://github.com/fireflyframework/fireflyframework-pyfly/releases/download/v26.10.05/pyfly-by-example.pdf)** · **[English EPUB](https://github.com/fireflyframework/fireflyframework-pyfly/releases/download/v26.10.05/pyfly-by-example.epub)** · **[Spanish PDF](https://github.com/fireflyframework/fireflyframework-pyfly/releases/download/v26.10.05/pyfly-by-example-es.pdf)** · **[Spanish EPUB](https://github.com/fireflyframework/fireflyframework-pyfly/releases/download/v26.10.05/pyfly-by-example-es.epub)**. For a specific version, use its [tagged release](https://github.com/fireflyframework/fireflyframework-pyfly/releases), or build from source with `bash book/build/run.sh --out-dir book/release-output`. Run the companion sample with `cd samples/lumen && uv run --extra dev pytest` and `uv run pyfly run --server uvicorn`. +**📥 Download the books shipped with v26.10.06:** **[English PDF](https://github.com/fireflyframework/fireflyframework-pyfly/releases/download/v26.10.06/pyfly-by-example.pdf)** · **[English EPUB](https://github.com/fireflyframework/fireflyframework-pyfly/releases/download/v26.10.06/pyfly-by-example.epub)** · **[Spanish PDF](https://github.com/fireflyframework/fireflyframework-pyfly/releases/download/v26.10.06/pyfly-by-example-es.pdf)** · **[Spanish EPUB](https://github.com/fireflyframework/fireflyframework-pyfly/releases/download/v26.10.06/pyfly-by-example-es.epub)**. For a specific version, use its [tagged release](https://github.com/fireflyframework/fireflyframework-pyfly/releases), or build from source with `bash book/build/run.sh --out-dir book/release-output`. Run the companion sample with `cd samples/lumen && uv run --extra dev pytest` and `uv run pyfly run --server uvicorn`. --- @@ -850,21 +850,23 @@ See **[`samples/lumen/`](samples/lumen/README.md)** for an end-to-end DDD micros > **Note:** PyFly is distributed exclusively via [GitHub Releases](https://github.com/fireflyframework/fireflyframework-pyfly/releases). It is **not** published to PyPI. -**Upgrading to v26.10.05:** see the [dependency update notes](docs/dependency-updates.md), -including the temporary upstream source requirement and required pip upgrade -step for the optional PII extra. +**Upgrading to v26.10.06:** see [OpenTelemetry service identity](docs/modules/observability.md#service-identity-and-resource-attributes) +for standard environment-variable support in traces and metrics. The +[dependency update notes](docs/dependency-updates.md) still apply, including the +temporary upstream source requirement and required pip upgrade step for the +optional PII extra. ### Install from GitHub Release (Recommended) ```bash # Install the latest release (uv) -uv add "pyfly @ https://github.com/fireflyframework/fireflyframework-pyfly/releases/latest/download/pyfly-26.10.5-py3-none-any.whl" +uv add "pyfly @ https://github.com/fireflyframework/fireflyframework-pyfly/releases/latest/download/pyfly-26.10.6-py3-none-any.whl" # Install with specific extras -uv add "pyfly[web,data-relational,cache] @ https://github.com/fireflyframework/fireflyframework-pyfly/releases/latest/download/pyfly-26.10.5-py3-none-any.whl" +uv add "pyfly[web,data-relational,cache] @ https://github.com/fireflyframework/fireflyframework-pyfly/releases/latest/download/pyfly-26.10.6-py3-none-any.whl" # Or with pip -pip install "pyfly @ https://github.com/fireflyframework/fireflyframework-pyfly/releases/latest/download/pyfly-26.10.5-py3-none-any.whl" +pip install "pyfly @ https://github.com/fireflyframework/fireflyframework-pyfly/releases/latest/download/pyfly-26.10.6-py3-none-any.whl" ``` ### One-Line Install (CLI + Framework) @@ -1194,6 +1196,7 @@ The git tag and human-readable display use the leading-zero form (`v26.05.01`); The full release history lives in **[CHANGELOG.md](CHANGELOG.md)** ([Keep a Changelog](https://keepachangelog.com/) format). Recent highlights: +- **`v26.10.06`** (2026-10-14) — OpenTelemetry service identity for newly created trace and metric providers honors standard resource environment variables while preserving explicit PyFly overrides. - **`v26.10.05`** (2026-10-14) — refreshed dependencies and pinned Python setup workflows, with synchronized release metadata and English/Spanish book downloads. - **`v26.10.04`** (2026-10-14) — revised public OAuth references and rebuilt English/Spanish PDF/EPUB books, with synchronized package and installer metadata. - **`v26.10.03`** (2026-10-14) — authorization-code/PKCE redemption through the native HTTP endpoint, with form-encoded OAuth Basic credentials across acquisition and introspection. diff --git a/book/README.md b/book/README.md index 79e14e60..bec0af8b 100644 --- a/book/README.md +++ b/book/README.md @@ -7,10 +7,10 @@ wallet offer route, shared targeting fixture, runtime gates and operator workflo | Edition | PDF | EPUB | |---|---|---| -| English | [PDF](https://github.com/fireflyframework/fireflyframework-pyfly/releases/download/v26.10.05/pyfly-by-example.pdf) | [EPUB](https://github.com/fireflyframework/fireflyframework-pyfly/releases/download/v26.10.05/pyfly-by-example.epub) | -| Spanish | [PDF](https://github.com/fireflyframework/fireflyframework-pyfly/releases/download/v26.10.05/pyfly-by-example-es.pdf) | [EPUB](https://github.com/fireflyframework/fireflyframework-pyfly/releases/download/v26.10.05/pyfly-by-example-es.epub) | +| English | [PDF](https://github.com/fireflyframework/fireflyframework-pyfly/releases/download/v26.10.06/pyfly-by-example.pdf) | [EPUB](https://github.com/fireflyframework/fireflyframework-pyfly/releases/download/v26.10.06/pyfly-by-example.epub) | +| Spanish | [PDF](https://github.com/fireflyframework/fireflyframework-pyfly/releases/download/v26.10.06/pyfly-by-example-es.pdf) | [EPUB](https://github.com/fireflyframework/fireflyframework-pyfly/releases/download/v26.10.06/pyfly-by-example-es.epub) | -These books are rebuilt from the `v26.10.05` software tag. The independently +These books are rebuilt from the `v26.10.06` software tag. The independently published [2026-10-14 edition](https://github.com/fireflyframework/fireflyframework-pyfly/releases/tag/books-26.10.14) retains its original assets. For other editions, open the [tagged releases](https://github.com/fireflyframework/fireflyframework-pyfly/releases) diff --git a/book/manuscript-es/15-observability.md b/book/manuscript-es/15-observability.md index ab2762cc..f79c34c9 100644 --- a/book/manuscript-es/15-observability.md +++ b/book/manuscript-es/15-observability.md @@ -192,11 +192,11 @@ Esa pasada de redacción se ejecuta para *cada* logger del proceso, incluidos lo El motor de expresiones regulares viene con cada instalación. El motor NER respaldado por Presidio —que además atrapa nombres y direcciones en texto libre— está disponible mediante el extra `[pii]` y se activa automáticamente cuando se instala: ```bash -uv add "pyfly[observability,pii] @ https://github.com/fireflyframework/fireflyframework-pyfly/releases/download/v26.10.05/pyfly-26.10.5-py3-none-any.whl" +uv add "pyfly[observability,pii] @ https://github.com/fireflyframework/fireflyframework-pyfly/releases/download/v26.10.06/pyfly-26.10.6-py3-none-any.whl" uv run python -m spacy download en_core_web_sm ``` -En v26.10.05, el extra PII requiere Git y acceso a GitHub: fija una corrección +En v26.10.06, el extra PII requiere Git y acceso a GitHub: fija una corrección oficial del código fuente de Presidio para poder instalar cryptography 50. Ese código conserva el mismo número de versión que el paquete anterior de PyPI. Las instalaciones existentes con pip deben reinstalar explícitamente el @@ -687,12 +687,39 @@ pyfly: observability: tracing: enabled: true - service-name: "${pyfly.app.name}" exporter: otlp otlp: endpoint: "http://localhost:4318" ``` +Deja `tracing.service-name` sin definir para respetar la identidad que inyecta +la plataforma. Al crear proveedores de trazas y métricas, PyFly resuelve +`service.name` primero desde un `pyfly.observability.tracing.service-name` no +vacío (incluido `PYFLY_OBSERVABILITY_TRACING_SERVICE_NAME`), después desde +`OTEL_SERVICE_NAME`, después desde `service.name` en +`OTEL_RESOURCE_ATTRIBUTES`, después desde `pyfly.app.name` y, por último, +desde `pyfly-app`. + +```bash +export OTEL_SERVICE_NAME=lumen-api +export OTEL_RESOURCE_ATTRIBUTES='service.namespace=lumen,release.note=blue%2Cgreen' +``` + +El SDK decodifica los valores de los atributos (`blue%2Cgreen` pasa a ser +`blue,green`) y PyFly conserva los demás atributos del recurso. Para sustituir +deliberadamente la identidad del despliegue, define +`tracing.service-name: lumen-custom` dentro de `pyfly.observability`, o usa +`PYFLY_OBSERVABILITY_TRACING_SERVICE_NAME`. Definir explícitamente +`service-name: "${pyfly.app.name}"` también tiene prioridad sobre el entorno +OpenTelemetry; no hace falta para usar el nombre de la aplicación como respaldo. + +La identidad se configura al crear el proveedor. Reinicia el proceso para +cambiar los recursos globales; los proveedores globales existentes y los +personalizados se reutilizan sin cambiar sus nombres. Esta versión no +redespliega aplicaciones en ejecución ni reescribe la telemetría histórica. +Consulta la [guía de identidad del servicio](https://fireflyframework.github.io/fireflyframework-pyfly/docs/modules/observability/#service-identity-and-resource-attributes) +para conocer el contrato completo de propiedad de los proveedores. + **Ejecútalo — observa spans sin un colector.** Levantar Jaeger o Tempo es excesivo mientras estás aprendiendo. Configura en su lugar el **exportador de consola**, que imprime cada span por stdout. Lumen viene con diff --git a/book/manuscript/15-observability.md b/book/manuscript/15-observability.md index 77caf834..c37aa3dc 100644 --- a/book/manuscript/15-observability.md +++ b/book/manuscript/15-observability.md @@ -188,11 +188,11 @@ That redaction pass runs for *every* logger in the process, including ones insid The regex engine ships with every install. The Presidio-backed NER engine — which also catches free-text names and addresses — is available via the `[pii]` extra and activates automatically when installed: ```bash -uv add "pyfly[observability,pii] @ https://github.com/fireflyframework/fireflyframework-pyfly/releases/download/v26.10.05/pyfly-26.10.5-py3-none-any.whl" +uv add "pyfly[observability,pii] @ https://github.com/fireflyframework/fireflyframework-pyfly/releases/download/v26.10.06/pyfly-26.10.6-py3-none-any.whl" uv run python -m spacy download en_core_web_sm ``` -For v26.10.05, the optional PII extra requires Git and GitHub access: it pins +For v26.10.06, the optional PII extra requires Git and GitHub access: it pins an official Presidio source fix so cryptography 50 can be installed. The source still reports the same Presidio version as its older PyPI package. Existing pip installations must explicitly reinstall the pinned anonymizer before upgrading; @@ -743,12 +743,36 @@ pyfly: observability: tracing: enabled: true - service-name: "${pyfly.app.name}" exporter: otlp otlp: endpoint: "http://localhost:4318" ``` +Leave `tracing.service-name` unset to honor an identity injected by the platform. +For newly created trace and metric providers, PyFly resolves `service.name` from +a nonempty `pyfly.observability.tracing.service-name` (including +`PYFLY_OBSERVABILITY_TRACING_SERVICE_NAME`) first, then `OTEL_SERVICE_NAME`, +then `service.name` in `OTEL_RESOURCE_ATTRIBUTES`, then `pyfly.app.name`, and +finally `pyfly-app`. + +```bash +export OTEL_SERVICE_NAME=lumen-api +export OTEL_RESOURCE_ATTRIBUTES='service.namespace=lumen,release.note=blue%2Cgreen' +``` + +The SDK decodes resource attribute values (`blue%2Cgreen` becomes `blue,green`) +and PyFly preserves the other resource attributes. To override deployment +identity deliberately, set `tracing.service-name: lumen-custom` under +`pyfly.observability`, or use `PYFLY_OBSERVABILITY_TRACING_SERVICE_NAME`. +An explicit `service-name: "${pyfly.app.name}"` also overrides the standard +OpenTelemetry environment; it is not needed for the app-name fallback. + +Identity is configured at provider creation. Restart the process to change +process-global resources; existing globals and custom providers are reused +without renaming. This release does not redeploy running applications or rewrite +historical telemetry. See the [service identity guide](https://fireflyframework.github.io/fireflyframework-pyfly/docs/modules/observability/#service-identity-and-resource-attributes) +for the full provider ownership contract. + **Run it — see spans without a collector.** Standing up Jaeger or Tempo is overkill while you are learning. Set the **console exporter** instead, which prints each span to stdout. Lumen ships with `tracing.enabled: false`; flip it on diff --git a/docs/cli.md b/docs/cli.md index 109887f9..b3b80bcf 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -776,7 +776,7 @@ Missing optional tools are shown with a `-` dash indicator (dimmed), while missi Verifies that PyFly itself is importable and displays the installed version: ``` -✓ pyfly v26.10.05 +✓ pyfly v26.10.06 ``` ### Summary diff --git a/docs/dependency-updates.md b/docs/dependency-updates.md index fb1bea2c..60a20129 100644 --- a/docs/dependency-updates.md +++ b/docs/dependency-updates.md @@ -31,11 +31,11 @@ a release that supports cryptography 50 and that release is qualified. For an existing pip installation, first reinstall the named anonymizer from the exact source reference. Pip can otherwise retain the old index package because -both sources report version `2.2.364`, even with `--upgrade`. Then upgrade PyFly: +both sources report version `2.2.364`, even with `--upgrade`. Then upgrade PyFly to v26.10.06, which retains this source requirement: ```bash python -m pip install --upgrade --force-reinstall "presidio-anonymizer @ git+https://github.com/data-privacy-stack/presidio.git@5e2fcea990aa3b99660d2aea9121d0ba74a8940b#subdirectory=presidio-anonymizer" -python -m pip install --upgrade "pyfly[pii,security] @ https://github.com/fireflyframework/fireflyframework-pyfly/releases/download/v26.10.05/pyfly-26.10.5-py3-none-any.whl" +python -m pip install --upgrade "pyfly[pii,security] @ https://github.com/fireflyframework/fireflyframework-pyfly/releases/download/v26.10.06/pyfly-26.10.6-py3-none-any.whl" python -m pip check python -m spacy download en_core_web_sm ``` diff --git a/docs/getting-started.md b/docs/getting-started.md index 7f030cea..2568d190 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -480,7 +480,7 @@ ______ ___.__._/ ____\ | ___.__. | __// ____| |__| |____/ ____| |__| \/ \/ - PyFly v26.10.05 | Python 3.12.0 + PyFly v26.10.06 | Python 3.12.0 2026-01-15T10:30:00Z [info] starting_application app=my-service version=0.1.0 2026-01-15T10:30:00Z [info] no_active_profiles message=No active profiles set, falling back to default diff --git a/docs/installation.md b/docs/installation.md index 13dae787..94aa36cf 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -323,7 +323,7 @@ Each extra pulls in the third-party libraries needed for a specific framework mo | `cli` | click, rich, jinja2, questionary | CLI tools (pyfly new, run, info, doctor, db) | | `full` | Bundled application extras (excludes `pii`, `testing`, `testcontainers`, and `argon2`) | Broad framework installation; add optional PII/testing/password backends explicitly | -The optional `pii` extra in v26.10.05 requires Git and GitHub access for the +The optional `pii` extra in v26.10.06 requires Git and GitHub access for the immutable upstream anonymizer fix. See the [dependency update notes](dependency-updates.md#presidio-and-cryptography) before upgrading an existing PII installation. @@ -517,7 +517,7 @@ PyFly Doctor ✓ mypy — Type checker PyFly packages: - ✓ pyfly v26.10.05 + ✓ pyfly v26.10.06 All checks passed! ``` diff --git a/docs/modules/configuration.md b/docs/modules/configuration.md index f8c09bf6..38952028 100644 --- a/docs/modules/configuration.md +++ b/docs/modules/configuration.md @@ -1315,7 +1315,10 @@ read-only deployment. A separate management listener additionally requires |---|---|---| | `pyfly.observability.metrics.enabled` | `true` | Enable metrics collection. | | `pyfly.observability.tracing.enabled` | `true` | Enable distributed tracing. | -| `pyfly.observability.tracing.service-name` | `"${pyfly.app.name}"` | OpenTelemetry service name (defaults to app name). | +| `pyfly.observability.tracing.service-name` | unset | Explicit nonempty override for newly created trace and metric resources; otherwise `OTEL_SERVICE_NAME`, resource environment `service.name`, app name, then `pyfly-app`. | + +See [service identity and resource attributes](observability.md#service-identity-and-resource-attributes) +for standard environment variables, preserved attributes, and provider restart behavior. ### Full YAML Reference @@ -1441,7 +1444,6 @@ pyfly: enabled: true tracing: enabled: true - service-name: "${pyfly.app.name}" transactional: enabled: false saga: diff --git a/docs/modules/core.md b/docs/modules/core.md index 105e7c49..144d23fe 100644 --- a/docs/modules/core.md +++ b/docs/modules/core.md @@ -599,7 +599,7 @@ class BannerMode(enum.Enum): | Mode | Behavior | |---|---| | `TEXT` | Full ASCII art banner (default) with a framework version line. | -| `MINIMAL` | Single line: `:: PyFly :: (v26.10.05)` | +| `MINIMAL` | Single line: `:: PyFly :: (v26.10.06)` | | `OFF` | No banner output at all. | ### BannerPrinter Class @@ -640,7 +640,7 @@ ______ ___.__._/ ____\ | ___.__. | __// ____| |__| |____/ ____| |__| \/ \/ -:: PyFly Framework :: (v26.10.05) +:: PyFly Framework :: (v26.10.06) ``` ### Custom Banner Files diff --git a/docs/modules/logging.md b/docs/modules/logging.md index 788d8bb5..91f10165 100644 --- a/docs/modules/logging.md +++ b/docs/modules/logging.md @@ -252,10 +252,10 @@ country code. Install the optional extra from the release artifact: ```bash -pip install "pyfly[pii] @ https://github.com/fireflyframework/fireflyframework-pyfly/releases/download/v26.10.05/pyfly-26.10.5-py3-none-any.whl" +pip install "pyfly[pii] @ https://github.com/fireflyframework/fireflyframework-pyfly/releases/download/v26.10.06/pyfly-26.10.6-py3-none-any.whl" ``` -Version 26.10.05 pins the official Presidio anonymizer source fix for +Version 26.10.06 pins the official Presidio anonymizer source fix for cryptography 50. This extra requires Git and GitHub access during installation; existing pip installations must explicitly reinstall the pinned anonymizer source before upgrading. See [dependency update notes](../dependency-updates.md#presidio-and-cryptography) diff --git a/docs/modules/observability.md b/docs/modules/observability.md index 8e3c7f99..8e8804d9 100644 --- a/docs/modules/observability.md +++ b/docs/modules/observability.md @@ -943,7 +943,6 @@ pyfly: observability: tracing: enabled: true # Default: true - service-name: "${pyfly.app.name}" # Inherits app name by default exporter: otlp # otlp | console | none otlp: endpoint: "http://localhost:4318" # OTLP/HTTP exporter endpoint @@ -976,6 +975,46 @@ configured under `metrics.otlp.endpoint` or `OTEL_EXPORTER_OTLP_METRICS_ENDPOINT traces endpoint with `/v1/traces` rewritten to `/v1/metrics` (a base URL gains `/v1/metrics`). With no endpoint at all the provider has no reader: instruments still work, nothing is exported. +### Service identity and resource attributes + +For each newly created OpenTelemetry trace or metric provider, PyFly chooses +the resource's `service.name` in this order: + +1. A nonempty `pyfly.observability.tracing.service-name`, including an explicit + `PYFLY_OBSERVABILITY_TRACING_SERVICE_NAME` environment override. +2. `OTEL_SERVICE_NAME`. +3. `service.name` from `OTEL_RESOURCE_ATTRIBUTES`. +4. `pyfly.app.name`. +5. `pyfly-app`. + +Leave `tracing.service-name` unset when your deployment supplies the standard +OpenTelemetry identity. The framework default does not set this property: +adding `service-name: "${pyfly.app.name}"` yourself is an explicit override +that takes precedence over both OpenTelemetry environment variables. + +```bash +export OTEL_SERVICE_NAME=lumen-api +export OTEL_RESOURCE_ATTRIBUTES='service.namespace=lumen,deployment.environment.name=production,release.note=blue%2Cgreen' +uv run pyfly run --server uvicorn +``` + +The OpenTelemetry SDK parses resource environment attributes, including +URL-encoded values: `release.note` above becomes `blue,green`. Other resource +attributes, including SDK metadata, are preserved when PyFly selects the service +name. Both newly created providers use the same resolution rules; this does not +change Prometheus metric names or labels. + +To deliberately override a platform-injected identity for both signals, set +`pyfly.observability.tracing.service-name: lumen-custom` in your configuration, +or `PYFLY_OBSERVABILITY_TRACING_SERVICE_NAME=lumen-custom` in the environment. +An empty property lets resolution continue to the OpenTelemetry environment. + +Resource identity is fixed when the provider is created. Restart the process +after changing these settings for process-global providers. PyFly reuses +existing globals and backs off for user-defined providers; it does not rename +their resources. This change does not redeploy workloads or rewrite historical +traces or metrics in your telemetry backend. + ### Provider ownership and isolated contexts Default providers are **process owned**: OpenTelemetry's SDK shuts them down at process exit. diff --git a/docs/versioning.md b/docs/versioning.md index 43471fac..f70676ae 100644 --- a/docs/versioning.md +++ b/docs/versioning.md @@ -64,6 +64,7 @@ rare case where a substantial change needs an additional review window. | Version | Date | Notes | |---------|------|-------| +| `26.10.06` | 2026-10-14 | OpenTelemetry resource environment variables identify newly created trace and metric providers; explicit PyFly service-name overrides retain precedence. | | `26.10.05` | 2026-10-14 | Refreshed dependencies, pinned Python setup workflows, and synchronized release metadata and book downloads. | | `26.10.04` | 2026-10-14 | Revised OAuth references and EN/ES books included in the software release; synchronized release metadata. | | `26.10.03` | 2026-10-14 | HTTP authorization-code/PKCE redemption and interoperable OAuth Basic credentials. | @@ -103,20 +104,20 @@ shipped, with the version metadata updated. ```python import pyfly -print(pyfly.__version__) # → "26.10.05" +print(pyfly.__version__) # → "26.10.06" ``` The CLI reads the installed distribution metadata and reports the normalized package version: ```bash -pyfly --version # → pyfly, version 26.10.5 +pyfly --version # → pyfly, version 26.10.6 ``` The startup banner displays the leading-zero form: ``` -:: PyFly Framework :: (v26.10.05) (Python 3.13.9) +:: PyFly Framework :: (v26.10.06) (Python 3.13.9) ``` --- diff --git a/install.sh b/install.sh index 83258cff..7a7ba742 100755 --- a/install.sh +++ b/install.sh @@ -27,7 +27,7 @@ set -euo pipefail # ── Constants ────────────────────────────────────────────────────────────────── -PYFLY_VERSION="26.10.05" +PYFLY_VERSION="26.10.06" PYFLY_REPO="https://github.com/fireflyframework/fireflyframework-pyfly.git" DEFAULT_INSTALL_DIR="$HOME/.pyfly" MIN_PYTHON_MAJOR=3 diff --git a/pyproject.toml b/pyproject.toml index eb87ace4..3b72fc3a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ name = "pyfly" # CalVer YY.MM.PATCH — package metadata uses PEP 440 normalized form (26.5.4); # git tag, GitHub release and human-readable display use leading-zero form # (v26.05.04) to match the Java/.NET/Go siblings. -version = "26.10.5" +version = "26.10.6" description = "The official Python implementation of the Firefly Framework — DI, CQRS, EDA, hexagonal architecture, and more." readme = "README.md" license = "Apache-2.0" diff --git a/src/pyfly/__init__.py b/src/pyfly/__init__.py index cd804978..a628ea4b 100644 --- a/src/pyfly/__init__.py +++ b/src/pyfly/__init__.py @@ -14,4 +14,4 @@ # limitations under the License. """PyFly — Enterprise Python Framework.""" -__version__ = "26.10.05" +__version__ = "26.10.06" diff --git a/src/pyfly/observability/auto_configuration.py b/src/pyfly/observability/auto_configuration.py index e806caec..6b3f66c7 100644 --- a/src/pyfly/observability/auto_configuration.py +++ b/src/pyfly/observability/auto_configuration.py @@ -83,17 +83,18 @@ async def close(self) -> None: @staticmethod def _service_name(config: Config) -> str: - """``pyfly.observability.tracing.service-name``, else ``pyfly.app.name``, else ``pyfly-app``. + """Explicit PyFly override, then standard OTel environment, then app name. Shared with :class:`MeterProviderAutoConfiguration` so traces and metrics carry one ``service.name`` — a dashboard joins the two on it. """ - return str( - config.get( - "pyfly.observability.tracing.service-name", - config.get("pyfly.app.name", "pyfly-app"), - ) - ) + from opentelemetry.sdk.resources import OTELResourceDetector + + explicit = config.get("pyfly.observability.tracing.service-name") + if explicit: + return str(explicit) + detected = OTELResourceDetector().detect().attributes.get("service.name") + return str(detected or config.get("pyfly.app.name") or "pyfly-app") @bean def tracer_provider(self, config: Config) -> TracerProvider: diff --git a/src/pyfly/resources/pyfly-defaults.yaml b/src/pyfly/resources/pyfly-defaults.yaml index e7ef0d28..9e9afc3e 100644 --- a/src/pyfly/resources/pyfly-defaults.yaml +++ b/src/pyfly/resources/pyfly-defaults.yaml @@ -209,7 +209,7 @@ pyfly: enabled: true tracing: enabled: true - service-name: "${pyfly.app.name}" + # Leave service-name unset so the platform's OTEL resource identity can take precedence. transactional: enabled: false saga: diff --git a/tests/observability/test_otel_resource_identity.py b/tests/observability/test_otel_resource_identity.py new file mode 100644 index 00000000..a5a1288b --- /dev/null +++ b/tests/observability/test_otel_resource_identity.py @@ -0,0 +1,152 @@ +"""Issue #151: exported traces and metrics retain the platform's service identity.""" + +import json +import os +import queue +import threading +from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer +from pathlib import Path + +import pytest +from opentelemetry.proto.collector.metrics.v1.metrics_service_pb2 import ExportMetricsServiceRequest +from opentelemetry.proto.collector.trace.v1.trace_service_pb2 import ExportTraceServiceRequest + +from pyfly.core.config import Config +from pyfly.observability.auto_configuration import MeterProviderAutoConfiguration, TracingAutoConfiguration + + +@pytest.fixture +def otlp_receiver(monkeypatch): + for name in list(os.environ): + if name.startswith("OTEL_") or name in { + "PYFLY_APP_NAME", + "PYFLY_OBSERVABILITY_TRACING_SERVICE_NAME", + "PYFLY_OBSERVABILITY_TRACING_EXPORTER", + "PYFLY_OBSERVABILITY_TRACING_OTLP_ENDPOINT", + "PYFLY_OBSERVABILITY_METRICS_OTLP_ENDPOINT", + "PYFLY_OBSERVABILITY_TRACING_REGISTER_GLOBAL", + "PYFLY_OBSERVABILITY_METRICS_REGISTER_GLOBAL", + }: + monkeypatch.delenv(name) + received = queue.Queue() + + class Receiver(BaseHTTPRequestHandler): + def do_POST(self): + received.put((self.path, self.rfile.read(int(self.headers["Content-Length"])))) + self.send_response(200) + self.send_header("Content-Length", "0") + self.end_headers() + + def log_message(self, *args): + pass + + server = ThreadingHTTPServer(("127.0.0.1", 0), Receiver) + thread = threading.Thread(target=lambda: server.serve_forever(poll_interval=0.01), daemon=True) + thread.start() + try: + yield f"http://127.0.0.1:{server.server_port}", received + finally: + server.shutdown() + server.server_close() + thread.join(timeout=2) + + +@pytest.mark.parametrize( + "app_name,explicit_name,env,expected", + [ + (None, None, {"OTEL_SERVICE_NAME": "core-flydocs"}, "core-flydocs"), + ( + None, + None, + {"OTEL_RESOURCE_ATTRIBUTES": "service.name=core-flydocs-worker"}, + "core-flydocs-worker", + ), + ( + "shared-app", + None, + {"OTEL_SERVICE_NAME": "core-flydocs-bbox-worker", "OTEL_RESOURCE_ATTRIBUTES": "service.name=ignored"}, + "core-flydocs-bbox-worker", + ), + ( + None, + None, + {"OTEL_SERVICE_NAME": "", "OTEL_RESOURCE_ATTRIBUTES": "service.name=attributes-worker"}, + "attributes-worker", + ), + ("app-fallback", None, {"OTEL_SERVICE_NAME": "", "OTEL_RESOURCE_ATTRIBUTES": ""}, "app-fallback"), + (None, None, {}, "pyfly-app"), + ( + "shared-app", + "explicit-service", + {"OTEL_SERVICE_NAME": "injected-service", "OTEL_RESOURCE_ATTRIBUTES": "service.name=attributes-service"}, + "explicit-service", + ), + ( + None, + "yaml-service", + {"PYFLY_OBSERVABILITY_TRACING_SERVICE_NAME": "pyfly-env-service", "OTEL_SERVICE_NAME": "otel-service"}, + "pyfly-env-service", + ), + ("shared-app", "", {"OTEL_SERVICE_NAME": "injected-service"}, "injected-service"), + ( + None, + None, + {"OTEL_RESOURCE_ATTRIBUTES": "invalid,service.name=encoded%2Cworker%3Dblue"}, + "encoded,worker=blue", + ), + ("app-fallback", None, {"OTEL_RESOURCE_ATTRIBUTES": "service.name="}, "app-fallback"), + ], + ids=[ + "standard-service-env", + "resource-attributes", + "service-env-precedence", + "empty-env-uses-attributes", + "empty-env-uses-app", + "framework-fallback", + "explicit-pyfly-config", + "pyfly-env-override", + "empty-explicit-config", + "sdk-resource-parsing", + "empty-attribute-uses-app", + ], +) +def test_exported_signals_share_resolved_service_identity( + tmp_path: Path, monkeypatch, otlp_receiver, app_name, explicit_name, env, expected +): + endpoint, received = otlp_receiver + for name, value in env.items(): + monkeypatch.setenv(name, value) + resource_attributes = "service.namespace=platform,deployment.environment.name=test" + if env.get("OTEL_RESOURCE_ATTRIBUTES"): + resource_attributes += "," + env["OTEL_RESOURCE_ATTRIBUTES"] + monkeypatch.setenv("OTEL_RESOURCE_ATTRIBUTES", resource_attributes) + tracing = {"register-global": False, "otlp": {"endpoint": endpoint}} + if explicit_name is not None: + tracing["service-name"] = explicit_name + data = {"pyfly": {"observability": {"tracing": tracing, "metrics": {"register-global": False}}}} + if app_name is not None: + data["pyfly"]["app"] = {"name": app_name} + (tmp_path / "pyfly.yaml").write_text(json.dumps(data)) + config = Config.from_sources(tmp_path) + tracer = TracingAutoConfiguration().tracer_provider(config) + meter = None + try: + meter = MeterProviderAutoConfiguration().meter_provider(config) + with tracer.get_tracer("issue-151").start_as_current_span("workload-operation"): + meter.get_meter("issue-151").create_counter("workload.operations").add(1) + assert tracer.force_flush(timeout_millis=5000) + assert meter.force_flush(timeout_millis=5000) + exports = dict(received.get(timeout=5) for _ in range(2)) + traces = ExportTraceServiceRequest.FromString(exports["/v1/traces"]) + metrics = ExportMetricsServiceRequest.FromString(exports["/v1/metrics"]) + assert traces.resource_spans[0].scope_spans[0].spans[0].name == "workload-operation" + assert metrics.resource_metrics[0].scope_metrics[0].metrics[0].sum.data_points[0].as_int == 1 + for resource in (traces.resource_spans[0].resource, metrics.resource_metrics[0].resource): + attributes = {item.key: item.value.string_value for item in resource.attributes} + assert attributes["service.name"] == expected + assert attributes["service.namespace"] == "platform" + assert attributes["deployment.environment.name"] == "test" + finally: + tracer.shutdown() + if meter is not None: + meter.shutdown() diff --git a/uv.lock b/uv.lock index 8b274586..342917e4 100644 --- a/uv.lock +++ b/uv.lock @@ -2992,7 +2992,7 @@ wheels = [ [[package]] name = "pyfly" -version = "26.10.5" +version = "26.10.6" source = { editable = "." } dependencies = [ { name = "pydantic" }, diff --git a/web/index.html b/web/index.html index 4a37a308..ca483254 100644 --- a/web/index.html +++ b/web/index.html @@ -90,7 +90,7 @@

Apache 2.0 mypy strict async-first - v26.10.05 + v26.10.06 @@ -406,8 +406,8 @@

It's live

pyfly by example

The official, project-driven book. Across 18 chapters in five parts it builds Lumen — a real wallet & ledger microservice — from an empty folder into a secured, observable, event-driven service. Every listing is drawn from a running project. Spring developers get a parity callout at every turn.

- Download PDF - Download EPUB + Download PDF + Download EPUB Source on GitHub