Releases: fireflyframework/fireflyframework-pyfly
Releases · fireflyframework/fireflyframework-pyfly
v26.06.113
v26.06.113 (2026-06-17)
Added
- Server-layer observability. Observability is no longer only application-layer
(thehttp_server_requests_secondsfilter, tracing/correlation, process metrics):
pyfly now emits metrics about the ASGI server itself across Uvicorn, Granian,
and Hypercorn. A pure-ASGIServerMetricsASGIMiddleware(the uniform primary
source, running in every worker) emitsserver_active_connections,
server_in_flight_requests, andserver_requests_total; aServerMetricsBinder
bound from the in-worker ASGI lifespan emitsserver_workers,
server_uptime_seconds, andserver_started_total/server_stopped_total; and
a best-effortServerStatsPortsurfaces Uvicorn's true socket count
(server_native_connections) on the in-processserve_asyncpath. Every meter is
labeledserverandworker_pid. - Correct multi-worker aggregation. With
workers > 1,pyfly runenables
prometheus_clientmultiprocess mode (setsPROMETHEUS_MULTIPROC_DIRbefore
forking), so a single/actuator/prometheusscrape aggregates across all workers
viaMultiProcessCollector— this also fixes the previous per-worker gap for
http_server_requests_*. - Live admin Observability dashboard. A new real-time Observability view
(under Monitoring) shows server workers, uptime, active connections, in-flight
requests, requests/sec, a per-worker breakdown, and worker lifecycle, with links
to the Metrics and Traces views. Backed byGET /admin/api/observabilityand the
observabilitySSE stream. - Configuration. New
pyfly.server.observability.*keys —enabled
(defaulttrue, activated by the web/core starters),sample-interval-seconds
(5.0), andaccess-log(false, opt-in). Requires the observability extra
(prometheus_client); degrades to a no-op without it. - Local observability stack.
docker-compose.ymlgained loopback-bound
Prometheus + Grafana services (config inops/prometheus/prometheus.yml) that
scrape/actuator/prometheus.
Scope: gunicorn is intentionally not added (the stack stays async-only ASGI:
Granian > Uvicorn > Hypercorn), but theServerStatsPort+ multiprocess design is
gunicorn-ready for a future adapter.
Full Changelog: v26.06.112...v26.06.113
v26.06.112
v26.06.112 (2026-06-16)
Changed
- "PyFly by Example" — all diagrams redesigned. Every content figure in both
the English and Spanish editions was rebuilt in a single, polished design
language (consistent cards, gradient headers, numbered step flows, monospace
code tokens, brand palette, vector iconography). This fixes real defects in the
previous artwork — broken/tofu glyphs (font-dependent arrows, circled numbers
and check marks) and clipped/overflowing content — by drawing all arrows,
checkmarks and numbered badges as vector shapes and keeping figure text to
ASCII/Latin-1 only. - Two new figures where they help most: Page / Pageable / Sort (Chapter 5)
and Value Object vs Entity (Chapter 6). Both editions rebuilt and re-attached
to the release.
Full Changelog: v26.06.111...v26.06.112
v26.06.111
v26.06.111 (2026-06-16)
Added
- "PyFly by Example" — Spanish edition. The book is now published in Spanish
(book/dist/pyfly-by-example-es.{epub,pdf}) alongside the English edition,
built from a parallelbook/manuscript-es/manuscript viabook.es.yaml. The
book build (book/build/build.py) is now language-parameterized (--config,
per-manifestmanuscript_dir/output_basename/ localized labels). Both
editions are attached to the GitHub release. - Quick Start tutorial + step-by-step depth. A new "Build Lumen Step by Step"
walkthrough takes the reader from an empty folder to a running, tested wallet
feature; every chapter was deepened into a more granular, beginner-friendly
tutorial. A dedication was added (EN + ES).
Fixed
pyfly newno longer scaffolds the removedpyfly.web.portkey. The project
template (pyfly.yaml.j2) now emits the port underserver:as
pyfly.server.port(Springserver.portparity); the legacypyfly.web.port
was removed in v26.06.102 and had been left as a dead key in freshly scaffolded
applications.
Full Changelog: v26.06.110...v26.06.111
v26.06.110
v26.06.110 (2026-06-16)
Fixed
- The separate management port (actuator + admin) is now OPEN by default. When
pyfly.management.server.portruns actuator/admin on a dedicated port, the
app's user security filters (e.g. anHttpSecuritygate whosedeny-all
catch-all is scoped to the main app's URL space) were applied there too —
rejecting/admin,/actuator/info,/actuator/metricswith 401/403
while only/actuator/health(explicitly permitted) worked. The management
port is a separate, typically-internal listener (Springmanagement.server.port
parity) protected by network isolation, so it no longer applies the app's
security filters by default. Opt back in with
pyfly.management.security.enabled: true.
Full Changelog: v26.06.109...v26.06.110
v26.06.109
v26.06.109 (2026-06-16)
Fixed
- CORS preflight is no longer rejected by the security gate. The
CORSMiddlewareis now the outermost middleware (ahead of the
WebFilterChainthat holds theHttpSecuritygate) on both the Starlette
and FastAPI adapters. Previously the filter chain wrapped CORS, so a browser
OPTIONSpreflight (which carries no credentials) to a gated route was
answered with401and withoutAccess-Control-*headers — the browser
then blocked the real request ("Load failed"/"Failed to fetch"). The preflight
is now answered by CORS before the gate runs, andAccess-Control-*headers
are added to every response.
Full Changelog: v26.06.108...v26.06.109
v26.06.108
v26.06.108 (2026-06-16)
Added
- The live actuator
HealthAggregatoris exposed on
app.state.pyfly_health_aggregator. Consumers can now register extra
health indicators aftercreate_app(e.g. a readiness-only probe for an
external dependency) without introspecting route closures. It is the same
aggregator the live health routes use — whether actuator runs on the main app
(shared management mode) or on the separate management port — so indicators
added through it are reflected on/actuator/healthin either mode.
Full Changelog: v26.06.107...v26.06.108
v26.06.107
v26.06.107 (2026-06-16)
Added
- OAuth2 resource server: config-driven, multi-IdP, Spring-parity. The
bearer-token resource server now works out of the box with Keycloak,
Microsoft Entra ID (v1.0 + v2.0) and AWS Cognito via configuration
alone (no subclassing), and reaches Spring-Security parity:issuer-uriOIDC discovery — derive the JWKS endpoint + issuer from
<issuer-uri>/.well-known/openid-configuration(alternative tojwks-uri).- Config-driven claim mapping (
pyfly.security.oauth2.resource-server.*):
principal-claim-names,authorities-claim-names,scope-claim-names,
attribute-claims,authority-prefix. Claim names accept dotted paths
with a*wildcard and are colon-safe, so authorities resolve from
realm_access.roles,resource_access.*.roles(Keycloak),roles+groups
(Entra), andcognito:groups(Cognito) with zero code. audiences(a list;audmust match any) andvalidate-audience
(disable for Cognito access tokens, which carry noaud).- Configurable
algorithms,clock-skew-seconds,jwks-timeout-seconds,
jwks-cache-seconds. - New typed
ResourceServerProperties(@config_properties) and
ClaimMappings.
Fixed
- OAuth2 resource server — clock-skew leeway. JWT validation now allows 60s
of clock skew by default (configurable). Previously a token whoseiat/nbf
was a few seconds ahead of the server clock — routine with real IdPs — was
rejected as "not yet valid", causing intermittent 401s. - OAuth2 resource server — event-loop stall. The bearer filter now runs JWKS
validation (which does blocking network I/O on a cache miss) in a worker thread
(anyio.to_thread) instead of inline on the event loop. - OAuth2 resource server — multi-IdP claim coverage. Token-to-
SecurityContext
mapping previously read onlyrealm_access.rolesandscope/permissions,
silently dropping Keycloakresource_accessclient roles, Entragroups/
scp, Cognitocognito:groups, and any tokenattributes. All are now mapped
(configurably). - OAuth2 resource server — case-insensitive
Bearerscheme (RFC 7235): a
bearer …/BEARER …Authorization header is now accepted. - OAuth2 resource server — opt-in strict rejection. New
authenticate-error-mode: "401"rejects a present-but-invalid token at the
filter with401+WWW-Authenticate: Bearer error="invalid_token"(RFC
6750). Default remains"anonymous"(the gate decides) — no behavioural change
unless opted in.
Full Changelog: v26.06.106...v26.06.107
v26.06.106
v26.06.106 (2026-06-16)
Fixed
- Admin dashboard: the Overview "Thread Count" gauge no longer renders the
thread count as a percentage. The gauge widget (createGaugeChart) was a
fixed 0–100 percentage meter — it clamped its value to 100 and appended a
hard-coded%to the centre readout — but the Overview page feeds it the
absolute active-thread count. A process with 8 threads therefore showed
8%, and any count above 100 clamped to100%. The gauge now accepts a
max(the value that fills the arc) and aunit(the readout suffix); the
thread gauge passesunit: ''andmax: 100, so it shows the raw count
(8,150, …) while still acting as a thread-leak indicator (amber past 60,
red past 80). The defaults (max: 100,unit: '%') keep every percentage
gauge rendering exactly as before.
Full Changelog: v26.06.105...v26.06.106
v26.06.105
v26.06.105 (2026-06-15)
Fixed
- Management-port observability now covers BOTH the application and the
management ports. When actuator + admin run on a separate
pyfly.management.server.port, the dashboard/actuator there is the single
observability pane — but it previously reflected only the application port:- Access log: the management app was built without
RequestLoggingFilter,
so health probes, Prometheus scrapes and admin calls to the management port
produced nohttp_requestlog line. It is now wired into the management
chain (honoringpyfly.web.request-logging.enabled), so management-port
traffic is logged through pyfly's structured logger like the main app. - Metrics / HTTP exchanges / traces: the data-capture filters
(MetricsFilter,HttpExchangeRecorderFilter, the adminTraceCollector)
ran only on the main app, sohttp.server.requests,/actuator/httpexchanges
and the admin Traces view excluded management-port traffic. The same shared
capture instances now also run on the management app, so the dashboard
reflects both ports. A request traverses exactly one app's chain, so there is
no double counting; the recorders keep their own path exclusions (Prometheus
scrape, admin SSE self-polling).
- Access log: the management app was built without
Full Changelog: v26.06.104...v26.06.105
v26.06.104
v26.06.104 (2026-06-15)
Changed
- Clean error reporting — no raw tracebacks for expected (4xx) errors. A new
pyfly.kernel.exceptions.is_expected_error()classifiesBusinessException
andSecurityException(validation, business-rule, not-found, auth — the HTTP
4xx family) as expected. These are now logged at WARNING without a stack
trace; only unexpected infrastructure/5xx errors get a full traceback:- CQRS
CommandHandler.on_error/QueryHandler.on_errorno longer log
exc_info=Truefor expected errors (previously every command/query failure,
including validation, dumped a stack trace). - The web
RequestLoggingFilterlogs expected 4xx request failures at WARNING
instead of ERROR (the RFC 7807 response is unchanged).
- CQRS
- CLI shows clean one-line errors.
pyflynow surfaces uncaught failures
(e.g. a configuration/validation error during boot) as a single
Error: <message>line and exit code 1, instead of a raw Python traceback.
Pass--debug(or setPYFLY_DEBUG=1) to see the full traceback.
Full Changelog: v26.06.103...v26.06.104