{"id":50592874,"url":"https://github.com/fireflyframework/fireflyframework-pyfly","last_synced_at":"2026-06-07T12:01:57.558Z","repository":{"id":338505984,"uuid":"1158109325","full_name":"fireflyframework/fireflyframework-pyfly","owner":"fireflyframework","description":"Official Python implementation of the Firefly Framework — async-first, type-checked microservice framework with DI, hexagonal architecture, distributed transactions (Saga/Workflow/TCC), event sourcing, IDP, ECM, notifications, and 38 fully-implemented modules","archived":false,"fork":false,"pushed_at":"2026-06-05T12:13:07.000Z","size":3554,"stargazers_count":3,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-06-05T12:28:36.859Z","etag":null,"topics":["asyncio","cqrs","dependency-injection","ecm","event-driven-architecture","event-sourcing","firefly-framework","framework","hexagonal-architecture","identity-provider","microservices","orchestration","ports-and-adapters","pyfly","python","saga","spring-boot-equivalent","tcc","webhooks","workflow-engine"],"latest_commit_sha":null,"homepage":"https://github.com/fireflyframework","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/fireflyframework.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":"ROADMAP.md","authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-02-14T20:12:35.000Z","updated_at":"2026-06-05T12:12:57.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/fireflyframework/fireflyframework-pyfly","commit_stats":null,"previous_names":["fireflyframework/pyfly","fireflyframework/fireflyframework-pyfly"],"tags_count":58,"template":false,"template_full_name":null,"purl":"pkg:github/fireflyframework/fireflyframework-pyfly","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fireflyframework%2Ffireflyframework-pyfly","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fireflyframework%2Ffireflyframework-pyfly/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fireflyframework%2Ffireflyframework-pyfly/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fireflyframework%2Ffireflyframework-pyfly/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/fireflyframework","download_url":"https://codeload.github.com/fireflyframework/fireflyframework-pyfly/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fireflyframework%2Ffireflyframework-pyfly/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34020187,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-07T02:00:07.652Z","response_time":124,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["asyncio","cqrs","dependency-injection","ecm","event-driven-architecture","event-sourcing","firefly-framework","framework","hexagonal-architecture","identity-provider","microservices","orchestration","ports-and-adapters","pyfly","python","saga","spring-boot-equivalent","tcc","webhooks","workflow-engine"],"created_at":"2026-06-05T12:00:18.091Z","updated_at":"2026-06-07T12:01:57.542Z","avatar_url":"https://github.com/fireflyframework.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"assets/pyfly-logo.png\" alt=\"PyFly Logo\" width=\"600\" /\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cstrong\u003eThe Official Python Implementation of the Firefly Framework\u003c/strong\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://github.com/fireflyframework/fireflyframework-pyfly/actions/workflows/ci.yml\"\u003e\u003cimg src=\"https://github.com/fireflyframework/fireflyframework-pyfly/actions/workflows/ci.yml/badge.svg?branch=main\" alt=\"CI\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://github.com/fireflyframework\"\u003e\u003cimg src=\"https://img.shields.io/badge/Firefly_Framework-official-ff6600?logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZmlsbD0id2hpdGUiIGQ9Ik0xMiAyQzYuNDggMiAyIDYuNDggMiAxMnM0LjQ4IDEwIDEwIDEwIDEwLTQuNDggMTAtMTBTMTcuNTIgMiAxMiAyeiIvPjwvc3ZnPg==\" alt=\"Firefly Framework\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://www.python.org/\"\u003e\u003cimg src=\"https://img.shields.io/badge/python-3.12%2B-blue?logo=python\u0026logoColor=white\" alt=\"Python 3.12+\"\u003e\u003c/a\u003e\n  \u003ca href=\"LICENSE\"\u003e\u003cimg src=\"https://img.shields.io/badge/license-Apache%202.0-green\" alt=\"License: Apache 2.0\"\u003e\u003c/a\u003e\n  \u003ca href=\"#\"\u003e\u003cimg src=\"https://img.shields.io/badge/version-26.06.43-brightgreen\" alt=\"Version: 26.06.43\"\u003e\u003c/a\u003e\n  \u003ca href=\"#\"\u003e\u003cimg src=\"https://img.shields.io/badge/type--checked-mypy%20strict-blue?logo=python\u0026logoColor=white\" alt=\"Type Checked: mypy strict\"\u003e\u003c/a\u003e\n  \u003ca href=\"#\"\u003e\u003cimg src=\"https://img.shields.io/badge/code%20style-ruff-purple?logo=ruff\u0026logoColor=white\" alt=\"Code Style: Ruff\"\u003e\u003c/a\u003e\n  \u003ca href=\"#\"\u003e\u003cimg src=\"https://img.shields.io/badge/async-first-brightgreen\" alt=\"Async First\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cem\u003eBuild production-grade Python applications with the patterns you trust — dependency injection, CQRS, event-driven architecture, and more — powered by the \u003ca href=\"https://github.com/fireflyframework\"\u003eFirefly Framework\u003c/a\u003e.\u003c/em\u003e\n\u003c/p\u003e\n\n---\n\n## The Problem\n\nYou've been here before. A new Python microservice needs to ship. Before writing a single line of business logic, you spend the first two weeks making choices:\n\n- Which web framework? (FastAPI, Flask, Starlette, Django...)\n- Which ORM? (SQLAlchemy, Tortoise, Django ORM...)\n- Which message broker? (aiokafka, aio-pika, kombu...)\n- How do you wire dependencies? (dependency-injector, python-inject, manual...)\n- How do you structure the project? (Everyone invents their own layout)\n\nYou assemble a bespoke stack, glue it together, and move on. Six months later, another team builds a second service — and makes entirely different choices. Now you have two codebases with different conventions, different testing strategies, different deployment patterns, and no shared understanding of how things work.\n\n**Python gives you infinite choice. What it doesn't give you is cohesion.**\n\n---\n\n## What is PyFly?\n\nPyFly makes these decisions for you.\n\nIt is a **cohesive, full-stack framework** for building production-grade Python applications — microservices, monoliths, and libraries — where every module is designed to work together seamlessly. Dependency injection, HTTP routing, database access, messaging, caching, security, observability, and more — all integrated, all consistent, all with production-ready defaults from day one.\n\n```python\nfrom pyfly.container import rest_controller, service\nfrom pyfly.web import request_mapping, post_mapping, Body, Valid\n\n@service\nclass OrderService:\n    def __init__(self, repo: OrderRepository, events: EventPublisher) -\u003e None:\n        self._repo = repo\n        self._events = events\n\n    async def place_order(self, order: Order) -\u003e Order:\n        saved = await self._repo.save(order)\n        await self._events.publish(OrderPlaced(order_id=saved.id))\n        return saved\n\n@rest_controller\n@request_mapping(\"/orders\")\nclass OrderController:\n    def __init__(self, service: OrderService) -\u003e None:\n        self._service = service\n\n    @post_mapping(\"\", status_code=201)\n    async def create(self, order: Valid[Body[Order]]) -\u003e Order:\n        return await self._service.place_order(order)\n```\n\nNo boilerplate. No manual wiring. The DI container resolves `OrderRepository` and `EventPublisher` from type hints, validates the request body, and publishes domain events — all out of the box.\n\nPyFly is the **official Python implementation** of the [Firefly Framework](https://github.com/fireflyframework), a battle-tested enterprise platform originally built on Spring Boot for Java (40+ modules in production). PyFly brings the same cohesive programming model to Python 3.12+ — not as a port, but as a **native implementation** reimagined for `async/await`, type hints, protocols, and the full power of modern Python.\n\n### Who is PyFly for?\n\n- **Python developers** who want enterprise-grade patterns without reinventing the wheel for every project\n- **Teams** tired of assembling bespoke stacks and want every service to follow the same conventions\n- **Architects** building polyglot platforms who need consistency across Java and Python services\n- **Anyone migrating from Spring Boot** who wants familiar concepts expressed natively in Python\n\nComing from Spring Boot? See the [Spring Boot Comparison Guide](docs/spring-comparison.md) for a side-by-side concept mapping.\n\n---\n\n## Philosophy\n\nFour principles shape every design decision in PyFly. Together, they answer a single question: *how do you build applications that are easy to start, easy to change, and ready for production from the first commit?*\n\n### Convention Over Configuration\n\nStarting a new project should take seconds, not days. PyFly ships with production-ready defaults for every module — logging formats, connection pool sizes, retry policies, security headers, health endpoints — so a new service works immediately with minimal configuration:\n\n```yaml\n# A complete, production-ready web service:\npyfly:\n  web:\n    port: 8080\n```\n\nWhen you need to customize, you override only what matters. Everything else stays sensible.\n\n### Your Code, Not Ours\n\nYour business logic should never import `sqlalchemy`, `redis`, `aiokafka`, or any other infrastructure library. PyFly enforces this through **hexagonal architecture** — the same ports-and-adapters pattern used across all Firefly Framework modules:\n\n- **Ports** are Python `Protocol` classes that define contracts\n- **Adapters** are concrete implementations that fulfill those contracts\n- Your services depend on ports. The DI container wires the adapters at startup.\n\nThe result: you can swap your database from PostgreSQL to MongoDB, your broker from Kafka to RabbitMQ, or your cache from Redis to in-memory — without touching a single line of business logic.\n\n### Async-Native, Type-Safe\n\nEvery PyFly API is designed for `asyncio` from the ground up — no sync-to-async bridges, no thread pool workarounds. Every public surface has complete type annotations validated by mypy in strict mode. If it compiles, it's consistent.\n\n### Production-Ready from Day One\n\nThe first time you run `pyfly run`, your application already has structured logging with correlation IDs, PII redaction, health check endpoints, Prometheus metrics, OWASP security headers, and graceful shutdown. These aren't features you opt into — they're the baseline.\n\n---\n\n## How It Works\n\n### Dependency Injection\n\nPyFly's DI container resolves dependencies from **type hints** — no XML, no service locators, just decorators and Python annotations. The container scans packages listed in `scan_packages`, discovers all decorated classes, and builds a complete dependency graph at startup.\n\n```python\nfrom pyfly.container import Autowired, service\n\n@service\nclass OrderService:\n    metrics: MetricsCollector = Autowired(required=False)  # field injection\n\n    def __init__(self, repo: OrderRepository, events: EventPublisher) -\u003e None:\n        self._repo = repo      # constructor injection (preferred)\n        self._events = events\n```\n\n**How resolution works:** When the container creates `OrderService`, it inspects the `__init__` type hints, finds `OrderRepository` and `EventPublisher` in the bean registry, resolves them recursively (including *their* dependencies), and injects the fully-initialized instances. After construction, it sets any `Autowired()` fields via `setattr`. The entire graph is resolved before your application handles its first request.\n\n**Stereotypes** mark classes with their architectural role and register them with the container:\n\n| Stereotype | Purpose | Layer |\n|------------|---------|-------|\n| `@component` | Generic managed bean | Any |\n| `@service` | Business logic | Service |\n| `@repository` | Data access | Data |\n| `@controller` | Web controller (template responses) | Web |\n| `@rest_controller` | REST endpoints (JSON) | Web |\n| `@shell_component` | CLI commands (import from `pyfly.shell`) | Shell |\n| `@configuration` + `@bean` | Bean factory methods | Infrastructure |\n\nAll stereotypes default to **singleton scope** (one instance per application). You can override with `@service(scope=Scope.TRANSIENT)` for a new instance on every injection, or `Scope.REQUEST` for one instance per HTTP request.\n\n**Advanced capabilities:** `Optional[T]` resolves to `None` when no bean is registered. `list[T]` collects all implementations of a type. `Qualifier(\"name\")` selects a specific named bean when multiple candidates exist. `@primary` marks the default when there are multiple implementations of the same port. The container detects circular dependencies at startup and reports them clearly rather than deadlocking at runtime.\n\n### Hexagonal Architecture\n\nEvery PyFly module that touches external systems is split into two halves: **ports** and **adapters**. Ports are abstract `Protocol` interfaces that your business logic depends on. Adapters are concrete implementations backed by real libraries. The DI container connects them at startup.\n\nThis separation is not conceptual — it is enforced by package structure:\n\n```\n┌──────────────────────────────────────────────────────────┐\n│                    APPLICATION LAYER                     │\n│                                                          │\n│  Your services, controllers, and domain logic.           │\n│  They depend ONLY on ports.                              │\n│                                                          │\n│    @service                                              │\n│    class OrderService:                                   │\n│        repo: RepositoryPort[Order, int]                  │\n│        events: EventPublisher                            │\n│        cache: CacheAdapter                               │\n│                                                          │\n└────────────────────────────┬─────────────────────────────┘\n                             │ depends on\n┌────────────────────────────┴─────────────────────────────┐\n│                 PORTS  (Python Protocols)                │\n│                                                          │\n│  pyfly.data           RepositoryPort[T, ID]              │\n│  pyfly.messaging      MessageBrokerPort                  │\n│  pyfly.cache          CacheAdapter                       │\n│  pyfly.eda            EventPublisher                     │\n│  pyfly.client         HttpClientPort                     │\n│  pyfly.scheduling     TaskExecutorPort                   │\n│  pyfly.shell          ShellRunnerPort                    │\n│  pyfly.web            WebServerPort                      │\n│                                                          │\n└────────────────────────────┬─────────────────────────────┘\n                             │ implements\n┌────────────────────────────┴─────────────────────────────┐\n│            ADAPTERS  (Concrete Implementations)          │\n│                                                          │\n│  pyfly.data.relational.sqlalchemy                        │\n│  pyfly.data.document.mongodb                             │\n│  pyfly.messaging.adapters.kafka                          │\n│  pyfly.messaging.adapters.rabbitmq                       │\n│  pyfly.cache.adapters.redis                              │\n│  pyfly.eda.adapters.memory                               │\n│  pyfly.client.adapters.httpx_adapter                     │\n│  pyfly.scheduling.adapters.asyncio_executor              │\n│  pyfly.shell.adapters.click_adapter                      │\n│  pyfly.web.adapters.starlette                            │\n│                                                          │\n└──────────────────────────────────────────────────────────┘\n```\n\nThe practical result — swap any adapter without changing a single line of business logic:\n\n```python\n# Your service depends on the port, never on the adapter\n@service\nclass OrderService:\n    def __init__(self, repo: RepositoryPort[Order, int]) -\u003e None:\n        self._repo = repo\n\n    async def place_order(self, cmd: PlaceOrder) -\u003e Order:\n        return await self._repo.save(Order(name=cmd.name))\n\n# The @repository stereotype wires the adapter at startup.\n# Switch from SQL to MongoDB by changing one class declaration:\n\n# SQL:     class OrderRepo(Repository[OrderEntity, int]): ...\n# MongoDB: class OrderRepo(MongoRepository[OrderDoc, str]): ...\n# Custom:  class OrderRepo(DynamoRepository[OrderItem, str]): ...\n#\n# OrderService never changes. Tests never change. Controllers never change.\n```\n\n### Auto-Configuration\n\nPyFly detects installed libraries at startup and wires the right adapters automatically — no manual bean registration needed.\n\nThis works through two complementary mechanisms:\n\n**1. Declarative auto-configuration** — `@configuration` classes guarded by conditions. They act as \"default with override\" factories:\n\n```python\nfrom pyfly.context.conditions import auto_configuration, conditional_on_class, conditional_on_missing_bean\nfrom pyfly.container.bean import bean\n\n@auto_configuration\n@conditional_on_missing_bean(CacheAdapter)    # only if user hasn't registered one\n@conditional_on_class(\"redis.asyncio\")        # only if redis is installed\nclass RedisCacheAutoConfig:\n    @bean\n    def cache(self) -\u003e CacheAdapter:\n        return RedisCacheAdapter(url=self._props.redis.url)\n```\n\nThis bean is created only when (1) no user-provided `CacheAdapter` exists and (2) the `redis` library is installed. If the user registers their own `CacheAdapter` via `@bean`, the auto-configuration is silently skipped.\n\n**2. Decentralized entry-point discovery** — Each subsystem owns its own `@auto_configuration` class, registered as a `pyfly.auto_configuration` entry point in `pyproject.toml`. At startup, `discover_auto_configurations()` uses `importlib.metadata.entry_points(group=\"pyfly.auto_configuration\")` to find and load them — no hardcoded imports, no central engine:\n\n| Entry Point | Class | Detects | Binds | Fallback |\n|-------------|-------|---------|-------|----------|\n| `web_fastapi` | `FastAPIAutoConfiguration` | `fastapi` | `FastAPIWebAdapter` | none |\n| `web` | `WebAutoConfiguration` | `starlette` | `StarletteWebAdapter` | none |\n| `server_granian` | `GranianServerAutoConfiguration` | `granian` | `GranianServerAdapter` | none |\n| `server_uvicorn` | `UvicornServerAutoConfiguration` | `uvicorn` | `UvicornServerAdapter` | none |\n| `server_hypercorn` | `HypercornServerAutoConfiguration` | `hypercorn` | `HypercornServerAdapter` | none |\n| `event-loop` | `EventLoopAutoConfiguration` | `uvloop` / `winloop` | Event loop policy | `asyncio` |\n| `relational` | `RelationalAutoConfiguration` | `sqlalchemy` | `Repository[T, ID]` | none |\n| `document` | `DocumentAutoConfiguration` | `motor`, `beanie` | `MongoRepository[T, ID]` | none |\n| `messaging` | `MessagingAutoConfiguration` | `aiokafka` / `aio-pika` | `KafkaAdapter` / `RabbitMQAdapter` | `InMemoryMessageBroker` |\n| `cache` | `CacheAutoConfiguration` | `redis.asyncio` | `RedisCacheAdapter` | `InMemoryCache` |\n| `client` | `ClientAutoConfiguration` | `httpx` | `HttpxClientAdapter` | none |\n| `shell` | `ShellAutoConfiguration` | `click` | `ClickShellAdapter` | none |\n| `cqrs` | `CqrsAutoConfiguration` | — | CQRS handlers | none |\n| `admin` | `AdminAutoConfiguration` | — | Admin dashboard | none |\n| `transactional` | `TransactionalEngineAutoConfiguration` | — | Saga/TCC engines | none |\n| `security-jwt` | `JwtAutoConfiguration` | `pyjwt` | `JWTService` | none |\n| `security-password` | `PasswordEncoderAutoConfiguration` | `bcrypt` | `BcryptPasswordEncoder` | none |\n| `scheduling` | `SchedulingAutoConfiguration` | `croniter` | `TaskScheduler` | none |\n| `metrics` | `MetricsAutoConfiguration` | `prometheus_client` | `MetricsRegistry` | none |\n| `tracing` | `TracingAutoConfiguration` | `opentelemetry` | `TracerProvider` | none |\n| `actuator` | `ActuatorAutoConfiguration` | — | `ActuatorRegistry`, `HealthAggregator` | none |\n| `actuator-metrics` | `MetricsActuatorAutoConfiguration` | `prometheus_client` | `MetricsEndpoint`, `PrometheusEndpoint` | none |\n| `aop` | `AopAutoConfiguration` | — | `AspectBeanPostProcessor` | none |\n\nThird-party packages can register their own auto-configurations by adding entries to the same entry-point group — the same extensibility model as Spring Boot's `META-INF/spring.factories`:\n\n```toml\n# In a third-party pyproject.toml:\n[project.entry-points.\"pyfly.auto_configuration\"]\nmy-addon = \"my_package.auto_configuration:MyAutoConfiguration\"\n```\n\n**The practical workflow:** During development, install `uv add \"pyfly[full]\"` (or `pip install pyfly[full]`) and everything auto-wires. In production Docker images, install only the extras you need (e.g., `pyfly[web,data-relational,cache]`) and the discovered auto-configurations bind exactly those adapters. You can always override any auto-configured adapter with explicit `provider` settings in `pyfly.yaml` or by registering your own bean.\n\n---\n\n## Featured Patterns\n\nPyFly is more than a web framework — it ships **production-grade implementations of the distributed patterns** that power real microservices: distributed transactions, durable workflows, event sourcing, identity, content management, multi-channel notifications, inbound/outbound webhooks, business rules, and more. Each one is a first-class module with a port-and-adapter design, CLI scaffolding, REST controllers (where applicable), metrics, tracing, and persistence.\n\nThe sections below show one representative example per pattern. The full guides live under [`docs/modules/`](docs/modules/README.md).\n\n### Saga — Distributed Transaction with Compensation\n\nCoordinate work across multiple services with automatic compensation on failure. Sagas are declared with decorators on a class; the engine builds the DAG, executes steps in dependency order, and rolls back via compensation steps if anything fails.\n\n```python\nfrom pyfly.transactional.saga import saga, saga_step\nfrom pyfly.transactional.saga.core.context import SagaContext\n\n@saga(name=\"place-order\", timeout_ms=30_000)\nclass PlaceOrderSaga:\n    def __init__(self, payments: PaymentService, inventory: InventoryService, ship: ShippingService) -\u003e None:\n        self._payments = payments\n        self._inventory = inventory\n        self._ship = ship\n\n    # `compensate=` names a method on this class to invoke if a later step fails.\n    @saga_step(id=\"reserve-inventory\", retry=3, backoff_ms=500, compensate=\"release_inventory\")\n    async def reserve(self, ctx: SagaContext) -\u003e str:\n        return await self._inventory.reserve(ctx.input[\"order_id\"])\n\n    async def release_inventory(self, ctx: SagaContext) -\u003e None:\n        await self._inventory.release(ctx.input[\"order_id\"])\n\n    @saga_step(id=\"charge-payment\", depends_on=[\"reserve-inventory\"], compensate=\"refund_payment\")\n    async def charge(self, ctx: SagaContext) -\u003e str:\n        return await self._payments.charge(ctx.input[\"order_id\"], ctx.input[\"amount\"])\n\n    async def refund_payment(self, ctx: SagaContext) -\u003e None:\n        await self._payments.refund(ctx.input[\"order_id\"])\n\n    @saga_step(id=\"ship-order\", depends_on=[\"charge-payment\"])\n    async def ship_order(self, ctx: SagaContext) -\u003e None:\n        await self._ship.dispatch(ctx.input[\"order_id\"])\n```\n\n**Highlights:** parallel-by-default DAG execution, per-step retries with jitter, backpressure, idempotency keys, metrics + tracing, pluggable persistence (in-memory, Redis, SQLAlchemy, Cache), DLQ with `RecoveryService`, REST controllers for list/start/retry, and `OrchestrationHealthIndicator`. Programmatic API also available via `SagaBuilder()`. See [docs/modules/transactional.md](docs/modules/transactional.md).\n\n### Workflow — Durable, Signal-Driven Orchestration\n\nLong-running processes that can wait for external events, sleep for hours, spawn child workflows, and be queried while running. Inspired by Temporal/Cadence, native to PyFly.\n\n```python\nfrom pyfly.transactional.workflow import (\n    workflow, workflow_step, wait_for_signal, wait_for_timer,\n    child_workflow, workflow_query, on_workflow_complete,\n)\n\n@workflow(id=\"loan-approval\", version=2, timeout_ms=7 * 24 * 3600 * 1000)\nclass LoanApprovalWorkflow:\n    def __init__(self, scoring: ScoringService, kyc: KycService) -\u003e None:\n        self._scoring = scoring\n        self._kyc = kyc\n        self._decision: str | None = None\n\n    @workflow_step(id=\"run-scoring\")\n    async def run_scoring(self, ctx) -\u003e int:\n        return await self._scoring.score(ctx.input[\"applicant_id\"])\n\n    @child_workflow(workflow_id=\"kyc-verification\", wait_for_completion=True, timeout_ms=3600_000)\n    @workflow_step(id=\"kyc\", depends_on=[\"run-scoring\"])\n    async def run_kyc(self, ctx) -\u003e dict:\n        return {\"applicant_id\": ctx.input[\"applicant_id\"]}\n\n    @wait_for_signal(\"manual-decision\", timeout_ms=48 * 3600 * 1000)\n    @workflow_step(id=\"await-officer\", depends_on=[\"kyc\"])\n    async def await_officer(self, ctx, signal_payload: dict) -\u003e str:\n        self._decision = signal_payload[\"decision\"]\n        return self._decision\n\n    @wait_for_timer(delay_ms=24 * 3600 * 1000)\n    @workflow_step(id=\"cooling-off\", depends_on=[\"await-officer\"])\n    async def cooling_off(self, ctx) -\u003e None: ...\n\n    @workflow_query(name=\"status\")\n    def get_status(self) -\u003e str:\n        return self._decision or \"pending\"\n\n    @on_workflow_complete\n    async def notify(self, ctx) -\u003e None:\n        ctx.publish(\"LoanDecided\", {\"id\": ctx.input[\"applicant_id\"], \"decision\": self._decision})\n```\n\n**Highlights:** `@wait_for_signal` / `@wait_for_timer` / `@wait_for_all` / `@wait_for_any`, child workflows, queries, scheduling via cron, lifecycle hooks (`@on_workflow_complete`, `@on_workflow_error`, `@on_step_complete`), `SignalService` + `TimerService` + `ContinueAsNewService`, `WorkflowController` REST API. See [docs/modules/transactional.md](docs/modules/transactional.md).\n\n### TCC — Try / Confirm / Cancel\n\nStrong-consistency three-phase distributed transactions for financial workloads where compensation alone isn't enough.\n\n```python\nfrom pyfly.transactional.tcc import tcc, tcc_participant, try_method, confirm_method, cancel_method\nfrom pyfly.transactional.tcc.context import TccContext\nfrom typing import Annotated\nfrom pyfly.transactional.tcc.annotations import FromTry\n\n@tcc(name=\"transfer-funds\", timeout_ms=10_000, retry_enabled=True, max_retries=3)\nclass TransferFundsTcc: ...\n\n@tcc_participant(id=\"debit-source\", order=1)\nclass DebitSource:\n    def __init__(self, ledger: Ledger) -\u003e None: self._ledger = ledger\n\n    @try_method(timeout_ms=5_000)\n    async def try_debit(self, ctx: TccContext) -\u003e str:\n        return await self._ledger.hold(ctx.input[\"from\"], ctx.input[\"amount\"])\n\n    @confirm_method()\n    async def confirm(self, hold_id: Annotated[str, FromTry()], ctx: TccContext) -\u003e None:\n        await self._ledger.commit_hold(hold_id)\n\n    @cancel_method()\n    async def cancel(self, hold_id: Annotated[str, FromTry()], ctx: TccContext) -\u003e None:\n        await self._ledger.release_hold(hold_id)\n\n@tcc_participant(id=\"credit-target\", order=2)\nclass CreditTarget:\n    def __init__(self, ledger: Ledger) -\u003e None: self._ledger = ledger\n\n    @try_method()\n    async def try_credit(self, ctx: TccContext) -\u003e str:\n        return await self._ledger.reserve(ctx.input[\"to\"], ctx.input[\"amount\"])\n\n    @confirm_method()\n    async def confirm(self, reservation_id: Annotated[str, FromTry()], ctx: TccContext) -\u003e None:\n        await self._ledger.commit_reservation(reservation_id)\n\n    @cancel_method()\n    async def cancel(self, reservation_id: Annotated[str, FromTry()], ctx: TccContext) -\u003e None:\n        await self._ledger.release_reservation(reservation_id)\n```\n\nThe TCC engine runs Try across all participants in `order`. If every Try succeeds, it runs Confirm. If any Try fails, it runs Cancel for every participant whose Try succeeded. Try-results flow into Confirm/Cancel via `Annotated[T, FromTry()]`. See [docs/modules/transactional.md](docs/modules/transactional.md).\n\n### Event Sourcing — Aggregates, Event Store, Outbox\n\nPersist state as an append-only event log. Aggregates rebuild from history; projections update read models; the outbox reliably publishes events to brokers.\n\n```python\nfrom dataclasses import dataclass\nfrom pyfly.eventsourcing import (\n    AggregateRoot, DomainEvent, domain_event,\n    EventStore, SqlAlchemyEventStore, TransactionalOutbox,\n)\n\n@domain_event\n@dataclass(frozen=True)\nclass AccountOpened(DomainEvent):\n    account_id: str\n    owner: str\n    initial_balance: int\n\n@domain_event\n@dataclass(frozen=True)\nclass MoneyDeposited(DomainEvent):\n    account_id: str\n    amount: int\n\nclass Account(AggregateRoot):\n    def __init__(self) -\u003e None:\n        super().__init__()\n        self.owner: str = \"\"\n        self.balance: int = 0\n        self.when(AccountOpened, Account._on_opened)\n        self.when(MoneyDeposited, Account._on_deposit)\n\n    @classmethod\n    def open(cls, account_id: str, owner: str, initial_balance: int) -\u003e \"Account\":\n        agg = cls()\n        agg.id = account_id\n        agg.apply(AccountOpened(account_id=account_id, owner=owner, initial_balance=initial_balance))\n        return agg\n\n    def deposit(self, amount: int) -\u003e None:\n        if amount \u003c= 0:\n            raise ValueError(\"amount must be positive\")\n        self.apply(MoneyDeposited(account_id=self.id, amount=amount))\n\n    def _on_opened(self, e: AccountOpened) -\u003e None:\n        self.owner, self.balance = e.owner, e.initial_balance\n\n    def _on_deposit(self, e: MoneyDeposited) -\u003e None:\n        self.balance += e.amount\n\n# Persisting and rebuilding\nstore: EventStore = SqlAlchemyEventStore(session_factory)\naccount = Account.open(\"acc-42\", \"Alice\", 100)\naccount.deposit(25)\nawait store.append(account.id, account.pending_events(), expected_version=account.version - len(account.pending_events()))\naccount.mark_committed()\n\n# Later — reconstruct from the log:\nevents = await store.load(\"acc-42\")\nrebuilt = Account()\nrebuilt.id = \"acc-42\"\nfor envelope in events:\n    rebuilt.replay(envelope.event_type, envelope.event)\nassert rebuilt.balance == 125\n```\n\n**Highlights:** `AggregateRoot` with `when()`/`apply()`/`replay()`, optimistic concurrency via `expected_version`, snapshots (`SnapshotStore`), `TransactionalOutbox` for at-least-once publishing, `Projection` + `ProjectionRunner` for read models, `EventUpcaster` for schema evolution. Adapters: `InMemoryEventStore`, `SqlAlchemyEventStore`. See [docs/modules/eventsourcing.md](docs/modules/eventsourcing.md).\n\n### CQRS — Command/Query Buses with Validation, Authorization, Caching\n\n```python\nfrom pydantic import BaseModel\nfrom pyfly.cqrs import command, query, CommandHandler, QueryHandler, CommandBus, QueryBus\n\nclass CreateUser(BaseModel):\n    name: str\n    email: str\n\nclass FindUserById(BaseModel):\n    user_id: int\n\n@command(CreateUser)\nclass CreateUserHandler(CommandHandler[CreateUser, int]):\n    def __init__(self, repo: UserRepository) -\u003e None:\n        self._repo = repo\n\n    async def handle(self, cmd: CreateUser) -\u003e int:\n        return (await self._repo.save(User(name=cmd.name, email=cmd.email))).id\n\n@query(FindUserById, cache_ttl=60)\nclass FindUserByIdHandler(QueryHandler[FindUserById, User]):\n    def __init__(self, repo: UserRepository) -\u003e None: self._repo = repo\n    async def handle(self, q: FindUserById) -\u003e User:\n        return await self._repo.find_by_id(q.user_id)\n\n# Dispatch:\nasync def usage(commands: CommandBus, queries: QueryBus) -\u003e None:\n    user_id = await commands.dispatch(CreateUser(name=\"Ada\", email=\"ada@example.com\"))\n    user = await queries.dispatch(FindUserById(user_id=user_id))\n```\n\nCommand/query are auto-wired via the `CqrsAutoConfiguration` entry point. Cross-cutting concerns (`@validates`, `@authorizes`, `@cacheable`) compose declaratively. See [docs/modules/cqrs.md](docs/modules/cqrs.md).\n\n### Inbound Webhooks — Verify · Dedupe · Dispatch\n\n```python\nfrom pyfly.webhooks import (\n    AbstractWebhookEventListener, HmacSignatureValidator,\n    InMemoryWebhookEventStore, WebhookProcessor,\n)\nfrom pyfly.container import service\n\n@service\nclass StripePaymentListener(AbstractWebhookEventListener):\n    source = \"stripe\"\n\n    async def handle(self, event_type: str, payload: dict) -\u003e None:\n        match event_type:\n            case \"payment_intent.succeeded\":\n                await self._mark_paid(payload[\"data\"][\"object\"][\"id\"])\n            case \"charge.refunded\":\n                await self._mark_refunded(payload[\"data\"][\"object\"][\"id\"])\n\n# Auto-wired by AdapterAutoConfiguration:\nprocessor = WebhookProcessor(\n    validator=HmacSignatureValidator(secret=os.environ[\"STRIPE_SIGNING_SECRET\"]),\n    store=InMemoryWebhookEventStore(),\n    listeners=[stripe_payment_listener],\n)\n```\n\nThe `WebhookProcessor` validates the signature, deduplicates by event id, persists to the `WebhookEventStore`, and routes to `AbstractWebhookEventListener` instances by source. Plug it into any controller (`@post_mapping(\"/webhooks/stripe\")`) and you get production-grade ingestion. See [docs/modules/webhooks.md](docs/modules/webhooks.md).\n\n### Outbound Callbacks — HMAC-Signed Webhook Dispatch\n\n```python\nfrom pyfly.callbacks import CallbackDispatcher, CallbackSubscription, CallbackConfig\n\ndispatcher: CallbackDispatcher  # @autowired\n\nawait dispatcher.dispatch(\n    event_type=\"OrderShipped\",\n    payload={\"order_id\": \"ord-42\", \"tracking\": \"1Z...\"},\n    correlation_id=\"corr-1\",\n)\n# Reads subscriptions, filters authorized domains, signs with HMAC,\n# retries with exponential backoff, persists CallbackExecution records.\n```\n\nSubscriptions, authorized domains, and execution history are first-class persisted entities. Configure retry policies, secret rotation, and per-subscription filters declaratively. See [docs/modules/callbacks.md](docs/modules/callbacks.md).\n\n### Notifications — Email · SMS · Push (Provider-Agnostic)\n\n```python\nfrom pyfly.notifications import EmailMessage, EmailService, SmsMessage, SmsService\n\nemail_service: EmailService  # auto-wired with SendGrid / Resend / SMTP / dummy\nsms_service:   SmsService    # auto-wired with Twilio / dummy\n\nawait email_service.send(EmailMessage(\n    to=[\"alice@example.com\"],\n    subject=\"Welcome to Acme\",\n    html=\"\u003ch1\u003eHi Alice\u003c/h1\u003e\",\n))\n\nawait sms_service.send(SmsMessage(to=\"+34611222333\", body=\"Your code is 4242\"))\n```\n\nConfiguration in `pyfly.yaml` selects the provider:\n\n```yaml\npyfly:\n  notifications:\n    email:\n      provider: sendgrid     # sendgrid | resend | smtp | dummy\n      api-key: ${SENDGRID_API_KEY}\n    sms:\n      provider: twilio       # twilio | dummy\n      account-sid: ${TWILIO_ACCOUNT_SID}\n      auth-token: ${TWILIO_AUTH_TOKEN}\n    push:\n      provider: firebase     # firebase | dummy\n      credentials-file: /run/secrets/firebase.json\n```\n\nSee [docs/modules/notifications.md](docs/modules/notifications.md).\n\n### Identity Provider (IDP) — Multi-Provider Auth\n\nSingle port, four interchangeable adapters (Keycloak, AWS Cognito, Azure AD, internal-DB).\n\n```python\nfrom pyfly.idp import IdpAdapter, LoginRequest\n\nidp: IdpAdapter  # auto-wired\n\nresult = await idp.login(LoginRequest(username=\"alice@acme.com\", password=\"hunter2\"))\nif result.requires_mfa:\n    result = await idp.verify_mfa(result.mfa_challenge_id, code=\"123456\")\n\nsession = await idp.introspect(result.access_token)\nprint(session.user.email, session.roles)\n```\n\n```yaml\npyfly:\n  idp:\n    provider: keycloak       # keycloak | aws-cognito | azure-ad | internal-db\n    realm: acme\n    server-url: https://auth.acme.com\n    client-id: acme-app\n    client-secret: ${KEYCLOAK_CLIENT_SECRET}\n```\n\nSwitching providers is a YAML one-liner — your business code keeps depending on `IdpAdapter`. See [docs/modules/idp.md](docs/modules/idp.md).\n\n### ECM — Documents · Folders · E-Signature\n\n```python\nfrom pyfly.ecm import DocumentService, ESignatureService, SignatureRequest, Recipient\n\ndocuments: DocumentService     # auto-wired (S3 / Azure Blob / local-fs)\nesig: ESignatureService        # auto-wired (DocuSign / Adobe Sign / Logalty / no-op)\n\ndoc = await documents.upload(\"contracts/2026/acme.pdf\", file_bytes, mime_type=\"application/pdf\")\n\nenvelope = await esig.send(SignatureRequest(\n    document_id=doc.id,\n    recipients=[\n        Recipient(email=\"alice@acme.com\", name=\"Alice\", role=\"signer\"),\n        Recipient(email=\"legal@acme.com\", name=\"Legal\", role=\"approver\"),\n    ],\n    subject=\"Please sign the SaaS agreement\",\n))\n\nstatus = await esig.status(envelope.id)\n```\n\nStorage and e-signature are independent ports — combine S3 storage with DocuSign, or Azure Blob with Adobe Sign, or local-fs with the no-op signer for tests. See [docs/modules/ecm.md](docs/modules/ecm.md).\n\n### Rule Engine — YAML DSL with AST Evaluation\n\nExternalize business rules so non-developers can change them without redeploys.\n\n```yaml\n# rules/credit_approval.yaml\nname: credit_approval\ndescription: Decision rules for personal loans\ninputs: [income, debt, credit_score, employment_years]\nrules:\n  - id: high_credit_fast_track\n    when: credit_score \u003e= 750 and income \u003e= 50000\n    then: { decision: \"approve\", limit: 50000 }\n  - id: standard_review\n    when: credit_score \u003e= 650 and (debt / income) \u003c 0.4\n    then: { decision: \"review\", limit: 25000 }\n  - id: reject_low_credit\n    when: credit_score \u003c 600 or employment_years \u003c 1\n    then: { decision: \"reject\" }\n```\n\n```python\nfrom pyfly.rule_engine import RuleEngine, RuleSetRepository\n\nrules: RuleEngine  # auto-wired\n\ndecision = await rules.evaluate(\"credit_approval\", {\n    \"income\": 75000, \"debt\": 12000, \"credit_score\": 770, \"employment_years\": 5,\n})\n# {'decision': 'approve', 'limit': 50000, '_rule_id': 'high_credit_fast_track'}\n```\n\nAudit trails (which rule fired, why), batch evaluation, hot-reload from `RuleSetRepository`. See [docs/modules/rule-engine.md](docs/modules/rule-engine.md).\n\n### Plugin SPI — `@plugin` / `@extension_point` / `@extension`\n\nBuild extensible products: define extension points, let third-party packages contribute extensions.\n\n```python\nfrom pyfly.plugins import plugin, extension_point, extension\n\n@extension_point\nclass PaymentMethod:\n    def display_name(self) -\u003e str: ...\n    async def charge(self, amount: int, customer_id: str) -\u003e str: ...\n\n@plugin(name=\"acme-stripe\", version=\"1.0.0\", depends_on=[])\nclass StripePlugin: ...\n\n@extension(point=PaymentMethod, plugin=\"acme-stripe\")\nclass StripePayment(PaymentMethod):\n    def display_name(self) -\u003e str: return \"Credit Card (Stripe)\"\n    async def charge(self, amount: int, customer_id: str) -\u003e str:\n        return await self._stripe.charges.create(amount=amount, customer=customer_id)\n```\n\nThe plugin manager resolves the dependency graph, loads plugins in order, and registers extensions with the DI container. See [docs/modules/plugins.md](docs/modules/plugins.md).\n\n### Domain — DDD Building Blocks\n\n`pyfly.domain` ships the foundational types every domain-driven design codebase ends up reinventing — `Entity`, `ValueObject`, `AggregateRoot`, `DomainEvent`, `Specification`, `DomainRepository`, and domain-flavoured exceptions. The module is **pure standard-library Python** with zero runtime dependencies.\n\n```python\nfrom dataclasses import dataclass\nfrom pyfly.domain import AggregateRoot, BusinessRuleViolation, DomainEvent, ValueObject\n\n@dataclass(frozen=True, slots=True)\nclass Money(ValueObject):\n    amount: int\n    currency: str\n\n@dataclass(frozen=True)\nclass OrderShipped(DomainEvent):\n    order_id: str = \"\"\n    tracking_number: str = \"\"\n\nclass Order(AggregateRoot[str]):\n    def __init__(self, id: str, total: Money) -\u003e None:\n        super().__init__(id)\n        self.total = total\n        self.status = \"placed\"\n\n    def ship(self, tracking_number: str) -\u003e None:\n        if self.status == \"shipped\":\n            raise BusinessRuleViolation(\"order-already-shipped\")\n        self.status = \"shipped\"\n        assert self.id is not None\n        self.raise_event(OrderShipped(order_id=self.id, tracking_number=tracking_number))\n\n# Application service:\norder = Order(\"o-1\", Money(100, \"EUR\"))\norder.ship(\"trk-42\")\n\nevents = order.clear_events()      # drained by the repository\n# repository.save(order); for e in events: bus.publish(e)\n```\n\nFor domain-tier microservices, the **`@enable_domain_stack`** starter activates CQRS, the transactional engine (saga/workflow/TCC), event sourcing, the rule engine, and the relational data layer in a single decorator — mirroring `fireflyframework-starter-domain` (Java) and `AddFireflyDomain` (.NET):\n\n```python\nfrom pyfly.core import pyfly_application\nfrom pyfly.starters.domain import enable_domain_stack\n\n@enable_domain_stack\n@pyfly_application(name=\"my-service\", scan_packages=[\"my_service\"])\nclass Application:\n    pass\n```\n\nThe full DDD primitives are also re-exported from `pyfly.starters.domain` so a single import line is enough:\n\n```python\nfrom pyfly.starters.domain import (\n    AggregateRoot, BusinessRuleViolation, DomainEvent, DomainRepository,\n    Entity, Specification, ValueObject, enable_domain_stack,\n)\n```\n\nSee **[`samples/order_service/`](samples/order_service/README.md)** for an end-to-end DDD microservice that uses every primitive: layered split (interfaces / models / core / web / sdk), a real `Order` aggregate that protects its invariants, CQRS handlers, and a `ConfirmOrderSaga` that walks the order through `PLACED → INVENTORY_RESERVED → PAID → SHIPPED` with full compensation. See [docs/modules/domain.md](docs/modules/domain.md).\n\n---\n\n## Installation\n\n\u003e **Note:** PyFly is distributed exclusively via [GitHub Releases](https://github.com/fireflyframework/fireflyframework-pyfly/releases). It is **not** published to PyPI.\n\n### Install from GitHub Release (Recommended)\n\n```bash\n# Install the latest release (uv)\nuv add \"pyfly @ https://github.com/fireflyframework/fireflyframework-pyfly/releases/latest/download/pyfly-26.5.4-py3-none-any.whl\"\n\n# Install with specific extras\nuv add \"pyfly[web,data-relational,cache] @ https://github.com/fireflyframework/fireflyframework-pyfly/releases/latest/download/pyfly-26.5.4-py3-none-any.whl\"\n\n# Or with pip\npip install \"pyfly @ https://github.com/fireflyframework/fireflyframework-pyfly/releases/latest/download/pyfly-26.5.4-py3-none-any.whl\"\n```\n\n### One-Line Install (CLI + Framework)\n\n```bash\n# Via get.pyfly.io\ncurl -fsSL https://get.pyfly.io/ | bash\n\n# Or directly from GitHub\ncurl -fsSL https://raw.githubusercontent.com/fireflyframework/fireflyframework-pyfly/main/install.sh | bash\n```\n\nThe installer clones the repo, creates a virtual environment, installs PyFly with all extras, and adds `pyfly` to your PATH. You can customize with environment variables:\n\n```bash\n# Install to a custom directory\nPYFLY_HOME=/opt/pyfly curl -fsSL https://get.pyfly.io/ | bash\n\n# Install with specific extras only\nPYFLY_EXTRAS=web,data-relational,security curl -fsSL https://get.pyfly.io/ | bash\n```\n\n### Install from Source\n\n```bash\n# Clone the repository\ngit clone https://github.com/fireflyframework/fireflyframework-pyfly.git\ncd pyfly\n\n# Run the interactive installer\nbash install.sh\n\n# Or install manually with uv\nuv sync --all-extras --group dev\n\n# Or with pip\npython3 -m venv .venv \u0026\u0026 source .venv/bin/activate\npip install -e \".[full]\"\n```\n\n### Verify Installation\n\n```bash\npyfly --version\npyfly doctor\npyfly info\n```\n\n### Create Your First Project\n\n```bash\n# Quick start — create a REST API with all the batteries\npyfly new my-service --archetype web-api\ncd my-service\npyfly run --reload\n\n# Visit http://localhost:8080/health\n```\n\nSee the [Installation Guide](docs/installation.md) for detailed options, Docker examples, and CI/CD setup.\n\n---\n\n## CLI \u0026 Project Scaffolding\n\nThe `pyfly` CLI generates production-ready project structures with DI stereotypes, Docker support, and layered architecture out of the box.\n\n### Archetypes\n\n| Command | What you get |\n|---------|-------------|\n| `pyfly new my-app` | Minimal microservice (`core` archetype) |\n| `pyfly new my-api --archetype web-api` | REST API with controllers, services, repositories |\n| `pyfly new my-api --archetype fastapi-api` | REST API with FastAPI and native OpenAPI |\n| `pyfly new my-site --archetype web` | Server-rendered HTML with Jinja2 templates |\n| `pyfly new my-svc --archetype hexagonal` | Hexagonal architecture with ports \u0026 adapters |\n| `pyfly new my-lib --archetype library` | Reusable library with `py.typed` marker |\n| `pyfly new my-tool --archetype cli` | CLI application with interactive shell and DI |\n\n### Feature Selection\n\nChoose which PyFly extras to include with `--features`:\n\n```bash\n# REST API with database and caching\npyfly new order-service --archetype web-api --features web,data-relational,cache\n```\n\nAvailable features: `web`, `data-relational`, `data-document`, `eda`, `cache`, `client`, `security`, `scheduling`, `observability`, `cqrs`, `shell`\n\n### Interactive Mode\n\nRun `pyfly new` without arguments for a guided experience:\n\n```\n$ pyfly new\n\n  ╭──────────────────────────────────╮\n  │   PyFly Project Generator        │\n  ╰──────────────────────────────────╯\n\n  Step 1 of 4 — Project Details\n  ? Project name: order-service\n  ? Package name: order_service\n\n  Step 2 of 4 — Architecture\n  ? Select archetype: (use arrow keys)\n    ❯ core          Minimal microservice with DI container and config\n      web-api       Full REST API with controller/service/repository layers\n      web           Server-rendered HTML with Jinja2 templates and static assets\n      hexagonal     Clean architecture with domain isolation\n      library       Reusable library with py.typed and packaging best practices\n      cli           Command-line application with interactive shell and DI\n\n  Step 3 of 4 — Features\n  ? Select features: (space to toggle, enter to confirm)\n    ❯ [x] web          HTTP server, REST controllers, OpenAPI docs\n      [ ] data-relational  Data Relational — SQL databases (SQLAlchemy ORM)\n      ...\n\n  Step 4 of 4 — Review \u0026 Create\n  ? Create this project? Yes\n```\n\n### Generated Web API Structure\n\n```\norder-service/\n├── Dockerfile              # Multi-stage production build\n├── README.md               # Project docs with quick start\n├── pyfly.yaml              # Framework configuration\n├── pyproject.toml          # Dependencies based on selected features\n├── .gitignore\n├── .env.example\n├── src/order_service/\n│   ├── __init__.py\n│   ├── app.py              # @pyfly_application entry point\n│   ├── main.py             # ASGI app factory\n│   ├── controllers/\n│   │   ├── __init__.py\n│   │   ├── health_controller.py   # @rest_controller — /health\n│   │   └── todo_controller.py     # @rest_controller — CRUD /todos\n│   ├── services/\n│   │   ├── __init__.py\n│   │   └── todo_service.py        # @service — business logic\n│   ├── models/\n│   │   ├── __init__.py\n│   │   └── todo.py                # Pydantic DTOs\n│   └── repositories/\n│       ├── __init__.py\n│       └── todo_repository.py     # @repository — data access\n└── tests/\n    ├── __init__.py\n    ├── conftest.py\n    └── test_todo_service.py\n```\n\n### Other CLI Commands\n\n| Command | Description |\n|---------|-------------|\n| `pyfly run --reload` | Start the application server with auto-reload |\n| `pyfly info` | Show installed framework version and extras |\n| `pyfly doctor` | Diagnose your development environment |\n| `pyfly db init` | Initialize Alembic migration environment |\n| `pyfly db migrate -m \"msg\"` | Auto-generate a database migration |\n| `pyfly db upgrade` | Apply pending migrations |\n| `pyfly license` | Display the Apache 2.0 license |\n| `pyfly sbom` | Software Bill of Materials (table or JSON) |\n\nSee the full [CLI Reference](docs/cli.md) for details.\n\n---\n\n## Modules\n\nPyFly ships with **39 fully-implemented modules** organized into five layers — covering everything from HTTP routing and database access to distributed transactions, event sourcing, identity, content management, observability, and DDD building blocks:\n\n### Foundation Layer\n\n| Module | Description | Firefly Java Equivalent |\n|--------|-------------|------------------------|\n| **Core** | Application bootstrap, lifecycle, banner, configuration | `fireflyframework-starter-core` |\n| **Kernel** | Exception hierarchy, structured error types | `fireflyframework-kernel` |\n| **Container** | Dependency injection, stereotypes, bean factories | Spring DI (built-in) |\n| **Context** | ApplicationContext, events, lifecycle hooks, conditions | Spring ApplicationContext |\n| **Config** | Decentralized auto-configuration via `@auto_configuration` entry points | Spring Auto-Configuration |\n| **Logging** | Unified structured logging — intercepts all loggers (framework + third-party) through one formatter; Spring-style config (`pyfly.logging.*` — patterns, file output, rotation, external config file); PII redaction on by default (regex; optional Microsoft Presidio via `pyfly[pii]`) | `fireflyframework-observability` |\n\n### Application Layer\n\n| Module | Description | Firefly Java Equivalent |\n|--------|-------------|------------------------|\n| **Web** | HTTP routing, controllers, middleware, OpenAPI (Starlette and FastAPI adapters) | `fireflyframework-web` |\n| **Server** | Pluggable ASGI servers (Granian, Uvicorn, Hypercorn) and event loops (uvloop, asyncio) | Embedded Tomcat/Jetty/Undertow |\n| **Data** | Repository ports, derived queries, pagination, sorting, entity mapping | Spring Data Commons |\n| **Data Relational** | SQLAlchemy adapter — specifications, transactions, custom queries | `fireflyframework-r2dbc` |\n| **Data Document** | MongoDB adapter — Beanie ODM, document repositories | `fireflyframework-mongodb` |\n| **CQRS** | Command/Query segregation with CommandBus/QueryBus, validation, authorization, caching | `fireflyframework-cqrs` |\n| **Validation** | Input validation with Pydantic | `fireflyframework-validators` |\n\n### Infrastructure Layer\n\n| Module | Description | Firefly Java Equivalent |\n|--------|-------------|------------------------|\n| **Security** | JWT, password encoding, authorization | Part of `fireflyframework-starter-application` |\n| **Messaging** | Kafka, RabbitMQ, in-memory broker | `fireflyframework-eda` |\n| **EDA** | Event-driven architecture, event bus | `fireflyframework-eda` |\n| **Cache** | Caching decorators, Redis adapter | `fireflyframework-cache` |\n| **Client** | HTTP client, circuit breaker, retry | `fireflyframework-client` |\n| **Scheduling** | Cron jobs, fixed-rate tasks | Spring Scheduling |\n| **Resilience** | Rate limiter, bulkhead, timeout, fallback | Resilience4j (in `fireflyframework-client`) |\n| **Shell** | CLI commands, interactive REPL, runners | Spring Shell |\n| **Transactional** | Saga + Workflow + TCC orchestration: signal-driven, DAG, compensation, multi-backend persistence, DLQ, recovery | `fireflyframework-orchestration` |\n| **Event Sourcing** | AggregateRoot, EventStore, snapshots, transactional outbox, projections, upcasting | `fireflyframework-eventsourcing` |\n| **Domain (DDD)** | `Entity`, `ValueObject`, `AggregateRoot`, `DomainEvent`, `Specification`, `DomainRepository`, `BusinessRuleViolation` | `fireflyframework-starter-domain` |\n| **Plugins** | `@plugin` / `@extension_point` / `@extension`, dependency-ordered lifecycle | `fireflyframework-plugins` |\n| **Rule Engine** | YAML DSL, AST evaluator, batch evaluation, rule-set repository | `fireflyframework-rule-engine` |\n| **Config Server** | Spring Cloud Config Server analogue + client | `fireflyframework-config-server` |\n\n### Integration Layer\n\n| Module | Description | Firefly Java Equivalent |\n|--------|-------------|------------------------|\n| **IDP** | Identity-provider port + Keycloak / AWS Cognito / Azure AD / internal-DB adapters | `fireflyframework-idp` + adapters |\n| **ECM** | Document storage / metadata / folders / e-signature ports + AWS S3 / Azure Blob / DocuSign / Adobe Sign / Logalty / local-fs adapters | `fireflyframework-ecm` + adapters |\n| **Notifications** | Email / SMS / push ports + SendGrid / Twilio / Firebase / Resend / SMTP / dummy adapters | `fireflyframework-notifications` + adapters |\n| **Callbacks** | Outbound webhook dispatcher with HMAC signing, retries, execution tracking | `fireflyframework-callbacks` |\n| **Webhooks** | Inbound webhook ingestion with signature validation, idempotency, listener pattern | `fireflyframework-webhooks` |\n| **Starters** | Meta-packages (`enable_core_stack` / `application` / `data` / `domain`) | `fireflyframework-starter-*` |\n\n### Cross-Cutting Layer\n\n| Module | Description | Firefly Java Equivalent |\n|--------|-------------|------------------------|\n| **AOP** | Aspect-oriented programming | Spring AOP |\n| **Observability** | Prometheus metrics, OpenTelemetry tracing | `fireflyframework-observability` |\n| **Actuator** | Health checks, monitoring endpoints | `fireflyframework-starter-core` (actuator) |\n| **Admin** | Embedded management dashboard with 15 views, SSE streams, server mode fleet monitoring | Spring Boot Admin |\n| **Testing** | Test fixtures and assertions | Spring Test |\n| **CLI** | Command-line tools | `fireflyframework-cli` |\n\n---\n\n## Documentation\n\nFull documentation lives in the [`docs/`](docs/README.md) directory:\n\n- [Getting Started Tutorial](docs/getting-started.md) — Build your first PyFly application step by step\n- [Installation](docs/installation.md) — Install and configure PyFly with the right extras\n- [Architecture Overview](docs/architecture.md) — Understand the framework's design and patterns\n- [CLI Reference](docs/cli.md) — Command-line tools (new, run, db, info, doctor, license, sbom)\n- [Spring Boot Comparison](docs/spring-comparison.md) — Side-by-side concept mapping for Java developers\n\n### Module Guides\n\nBrowse all guides in the [Module Guides Index](docs/modules/README.md):\n\n- [Web Layer](docs/modules/web.md) — REST controllers, routing, parameter binding, OpenAPI\n- [Server Layer](docs/modules/server.md) — Pluggable ASGI servers, event loops, auto-configuration\n- [Data Commons](docs/modules/data.md) — Repository ports, derived queries, pagination, sorting, entity mapping\n- [Data Relational (SQL)](docs/modules/data-relational.md) — SQLAlchemy adapter: specifications, transactions, custom queries\n- [Data Document (MongoDB)](docs/modules/data-document.md) — MongoDB adapter: MongoRepository, Beanie ODM patterns\n- [Validation](docs/modules/validation.md) — `Valid[T]` annotation, structured 422 errors\n- [WebFilters](docs/modules/web-filters.md) — Request/response filter chain\n- [Actuator](docs/modules/actuator.md) — Health checks, extensible endpoints\n- [Custom Actuator Endpoints](docs/modules/custom-actuator-endpoints.md) — Build your own actuator endpoints\n- [Transactional Engine](docs/modules/transactional.md) — Saga, Workflow, and TCC distributed transaction patterns\n- [Event Sourcing](docs/modules/eventsourcing.md) — Aggregates, event store, snapshots, outbox, projections\n- [Domain (DDD primitives)](docs/modules/domain.md) — Entity, ValueObject, AggregateRoot, DomainEvent, Specification, DomainRepository, exceptions\n- [Starters](docs/modules/starters.md) — Layered bundles (`@enable_core_stack`, `@enable_web_stack`, `@enable_application_stack`, `@enable_data_stack`, `@enable_domain_stack`) with one-line imperative APIs for .NET parity\n- [Plugins](docs/modules/plugins.md) — Plugin SPI, extension points, lifecycle\n- [Rule Engine](docs/modules/rule-engine.md) — YAML DSL, AST evaluator, batch evaluation\n- [Callbacks (outbound)](docs/modules/callbacks.md) — Dispatch domain events to external HTTP endpoints\n- [Webhooks (inbound)](docs/modules/webhooks.md) — Receive, verify, dedupe, dispatch\n- [Notifications](docs/modules/notifications.md) — Email / SMS / push abstractions\n- [IDP (Identity Provider)](docs/modules/idp.md) — Keycloak / AWS Cognito / Azure AD / internal-DB adapters\n- [ECM (Content Management)](docs/modules/ecm.md) — Documents, folders, e-signature workflows\n- [Admin Dashboard](docs/modules/admin.md) — Embedded management dashboard, server mode, custom views\n- [Logging](docs/modules/logging.md) — Unified structured logging, Spring-style `pyfly.logging.*` config, PII redaction (`pyfly[pii]` for Presidio NER)\n\n### Adapter Reference\n\nBrowse the [Adapter Catalog](docs/adapters/README.md) for setup and configuration of each concrete backend:\n\n- [SQLAlchemy](docs/adapters/sqlalchemy.md) · [MongoDB](docs/adapters/mongodb.md) · [Starlette](docs/adapters/starlette.md) · [FastAPI](docs/adapters/fastapi.md) · [Granian](docs/adapters/granian.md) · [Kafka](docs/adapters/kafka.md) · [RabbitMQ](docs/adapters/rabbitmq.md) · [Redis](docs/adapters/redis.md) · [HTTPX](docs/adapters/httpx.md) · [Click](docs/adapters/click.md)\n\nBrowse the full list in the [Documentation Table of Contents](docs/README.md).\n\n---\n\n## Roadmap\n\nSee **[ROADMAP.md](ROADMAP.md)** for the full roadmap toward feature parity with the Firefly Framework Java ecosystem (40+ modules).\n\n| Phase | Focus | Key Modules | Status |\n|-------|-------|-------------|--------|\n| **Phase 1** | Core Distributed Patterns | Saga/TCC, Workflow, Event Sourcing | Complete (v26.05.01) |\n| **Phase 2** | Business Logic | Rule Engine, Plugins | Complete (v26.05.01) |\n| **Phase 3** | Enterprise Integrations | Notifications, IDP, ECM, Webhooks, Callbacks, Config Server | Complete (v26.05.01) |\n| **Phase 4** | Administrative \u0026 DDD | Backoffice, Utils, ~~DDD starters~~ (done in v26.05.02) | DDD complete; backoffice / utils planned |\n\n**v26.05.01** closes the parity gap with the Java Firefly Framework: the transactional engine has been rewritten from scratch (Saga + Workflow + TCC), nine new modules have been added (Event Sourcing, Callbacks, Webhooks, Notifications, IDP, ECM, Plugins, Rule Engine, Config Server), 12 third-party adapters were added, four new client protocols (SOAP/gRPC/GraphQL/WebSocket) were introduced, and the validation library now ships 16 domain validators. The framework is feature-complete for production microservice workloads.\n\n---\n\n## Versioning\n\nPyFly uses **Calendar Versioning** ([CalVer](https://calver.org/)) — `YY.MM.PATCH` — to stay aligned with the rest of the Firefly Framework family (Java, .NET, Go).\n\n| Component | Meaning |\n|-----------|---------|\n| `YY` | Two-digit year (e.g., `26` = 2026) |\n| `MM` | Two-digit month of the release |\n| `PATCH` | Patch number within the month (`01`, `02`, …) |\n\n**Examples:** `26.05.01`, `26.05.02`, `26.06.01`.\n\nThe git tag and human-readable display use the leading-zero form (`v26.05.01`); the `pyproject.toml` `version` field uses PEP 440's normalized form (`26.5.1`) so Python tooling (uv, pip, hatchling) accepts it without warnings. Both reference the same release. See [docs/versioning.md](docs/versioning.md) for full details, including the migration from the previous SemVer-with-milestone scheme.\n\n---\n\n## Changelog\n\nSee **[CHANGELOG.md](CHANGELOG.md)** for detailed release notes.\n\n**Current:** `v26.05.04` (2026-05-08) — `pyfly.security` import-chain fix:\n\n- **Bug fix** — `pyfly.security/__init__.py` no longer eagerly imports a starlette-specific `SecurityMiddleware` that transitively pulls in `pyjwt`. Importing `pyfly` (and instantiating `PyFlyApplication`) now works without `[security]` extras installed. Optional symbols (`SecurityMiddleware`, `JWTService`, `BcryptPasswordEncoder`) only export when their underlying packages (`starlette`, `pyjwt`, `bcrypt`) are present. Regression test pinned in `tests/security/test_optional_imports.py`.\n- Verified: bare wheel install (`pip install pyfly`) now exposes `pyfly.domain` immediately; the `[web,cqrs,transactional,eventsourcing]` extras unblock the full application bootstrap path.\n\n**Previous:** `v26.05.03` (2026-05-08) — Functional starters + Java/.NET parity:\n\n- **Starters now actually do something** — `@enable_*_stack` decorators no longer just set a marker attribute. They now inject their property defaults between framework defaults and the user's `pyfly.yaml`, so the bundle activates the modules it promises (`pyfly.cqrs.enabled`, `pyfly.transactional.enabled`, etc.) while explicit user values still win.\n- **`@enable_web_stack` (new)** — dedicated web-tier starter for HTTP/REST APIs that don't need EDA, CQRS, or cache. Activates web framework adapter (Starlette/FastAPI), ASGI server, validation, actuator, observability, and resilience filters.\n- **Imperative API for parity with .NET** — every starter now ships a `register_*_stack(app)` function (`register_core_stack`, `register_web_stack`, `register_application_stack`, `register_data_stack`, `register_domain_stack`) — the Pythonic counterpart to .NET's `services.AddFireflyXxx(...)` extension methods. Imperative registration is authoritative (last-call-wins).\n- **One-import-line ergonomics** — every starter re-exports the most commonly used decorators and types of its tier. `from pyfly.starters.web import rest_controller, post_mapping, Body, Valid, ...`; `from pyfly.starters.domain import AggregateRoot, BusinessRuleViolation, Command, CommandHandler, command_handler, ...`.\n- **Layered docs** — new [`docs/modules/starters.md`](docs/modules/starters.md) explains the property-layering model (framework defaults \u003c starter defaults \u003c user yaml \u003c profile overlays \u003c env vars) and shows the cross-language correspondence table.\n\n**Previous:** `v26.05.02` (2026-05-08) — DDD primitives + OrderService sample + async-saga fix:\n\n- **`pyfly.domain`** — pure-Python DDD building blocks: `Entity`, `ValueObject`, `AggregateRoot`, `DomainEvent`, `Specification` (with `\u0026` / `|` / `~` combinators), `DomainRepository` protocol, `DomainException` / `BusinessRuleViolation` / `AggregateNotFound`. Mirrors `fireflyframework-starter-domain` (Java) and `FireflyFramework.Starter.Domain` (.NET).\n- **OrderService sample** — `samples/order_service/` is a complete DDD-flavoured microservice with the same layered split (interfaces / models / core / web / sdk) used by the firefly-oss Java services and the .NET OrdersService sample. Includes a real `Order` aggregate, CQRS handlers, and a `ConfirmOrderSaga` that walks the order through `PLACED → INVENTORY_RESERVED → PAID → SHIPPED` with full compensation. 13/13 tests pass end-to-end.\n- **Async-saga fix** — `@saga_step` / `@try_method` / `@confirm_method` / `@cancel_method` no longer wrap the function with a sync adapter that masked `inspect.iscoroutinefunction`. `async def` saga and TCC steps are now correctly awaited by the engine. Regression test pinned in `tests/transactional/saga/test_async_steps.py`.\n\n**Previous:** `v26.05.01` (2026-05-07) — Full Java framework parity:\n\n- **Transactional engine rewrite** — `pyfly.transactional` now ships Saga + Workflow + TCC patterns on a shared core (DAG topology, retries with jitter, backpressure, idempotency, DLQ, recovery, REST controllers, health indicators)\n- **Nine new modules** — `eventsourcing`, `callbacks`, `webhooks`, `notifications`, `idp`, `ecm`, `plugins`, `rule_engine`, `config_server`\n- **12 new third-party adapters** — Keycloak / AWS Cognito / Azure AD (IDP); AWS S3 / Azure Blob (ECM storage); DocuSign / Adobe Sign / Logalty (e-signature); SendGrid / Resend / Twilio / Firebase (notifications)\n- **Four new client protocols** — SOAP, gRPC, GraphQL, WebSocket (joining the existing HTTP / OpenAPI generators)\n- **16 domain validators** — including IBAN, BIC, ISO country/currency codes, phone numbers, dates, national IDs, sort codes, interest rates\n- **CalVer migration** — `YY.MM.PATCH` aligning with all Firefly Framework siblings (Java, .NET, Go)\n\n---\n\n## Firefly Framework Ecosystem\n\nPyFly is part of the [Firefly Framework](https://github.com/fireflyframework) ecosystem:\n\n| Platform | Repository | Status |\n|----------|-----------|--------|\n| **Java / Spring Boot** | [`fireflyframework-*`](https://github.com/fireflyframework) (40+ modules) | Production |\n| **.NET 9** | [`fireflyframework-dotnet`](https://github.com/fireflyframework/fireflyframework-dotnet) | Beta (CalVer 26.05+) |\n| **Python** | [`fireflyframework-pyfly`](https://github.com/fireflyframework/fireflyframework-pyfly) | Beta (CalVer 26.05+) |\n| **Frontend (Angular)** | [`flyfront`](https://github.com/fireflyframework/flyfront) | Active Development |\n| **GenAI** | [`fireflyframework-genai`](https://github.com/fireflyframework/fireflyframework-genai) | Active Development |\n| **CLI (Go)** | [`fireflyframework-cli`](https://github.com/fireflyframework/fireflyframework-cli) | Active Development |\n\n---\n\n## Requirements\n\n| Requirement | Version |\n|-------------|---------|\n| Python | \u003e= 3.12 |\n| uv | \u003e= 0.5 recommended (pip also supported) |\n| Git | For cloning the repository |\n| OS | macOS, Linux (Windows support planned) |\n\n---\n\n## License\n\nApache License 2.0 — [Firefly Software Foundation.](https://github.com/fireflyframework)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffireflyframework%2Ffireflyframework-pyfly","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffireflyframework%2Ffireflyframework-pyfly","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffireflyframework%2Ffireflyframework-pyfly/lists"}