diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..e90e28c9 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,68 @@ +# Contributing to PyFly + +Thanks for your interest in improving PyFly — the official Python implementation of the +[Firefly Framework](https://github.com/fireflyframework). This guide covers everything you +need to make a change and open a pull request. + +## Ground rules + +- **Be respectful and constructive.** Assume good intent. +- **Keep changes focused.** One logical change per pull request; small PRs review faster. +- **Add tests for new behaviour** and a note in `CHANGELOG.md` under the next version. +- By contributing, you agree your work is licensed under **Apache-2.0** (see [`LICENSE`](LICENSE)). + +## Development setup + +PyFly targets **Python 3.12+** and uses [uv](https://github.com/astral-sh/uv) (pip also works). + +```bash +git clone https://github.com/fireflyframework/fireflyframework-pyfly.git +cd fireflyframework-pyfly + +# Install all extras + dev tooling +uv sync --all-extras --group dev +# (or: python3 -m venv .venv && source .venv/bin/activate && pip install -e ".[full]") +``` + +## The quality gates (what CI runs) + +Every pull request must pass the same gates CI enforces. Run them locally first: + +```bash +uv run ruff format . # format +uv run ruff check . # lint +uv run mypy src # type-check (strict) +uv run pytest # tests +``` + +- **Formatting & linting:** [Ruff](https://docs.astral.sh/ruff/). +- **Typing:** [mypy](https://mypy-lang.org/) in **strict** mode — every public surface is fully annotated. If it compiles, it's consistent. +- **Tests:** [pytest](https://docs.pytest.org/). Add tests next to the code they cover under `tests/`, and prefer fast, isolated unit tests; integration tests that need real infrastructure (Postgres, Kafka, Redis, …) live behind the Docker Compose stack (`docker-compose.yml`). + +## Making a change + +1. **Branch from `main`:** `git switch -c feat/short-description` (or `fix/…`, `docs/…`). +2. Make your change with tests. +3. Run the quality gates above until green. +4. Add a `CHANGELOG.md` entry under the next version heading (we use [Keep a Changelog](https://keepachangelog.com/)). +5. **Commit** with a clear, imperative message (Conventional-Commits style is welcome: `feat:`, `fix:`, `docs:`, `refactor:`, `test:`). +6. **Open a pull request** against `main` describing the what and the why. + +## Versioning & releases + +PyFly uses **Calendar Versioning** ([CalVer](https://calver.org/)) — `YY.MM.PATCH` — to stay +aligned with the rest of the Firefly Framework family (Java, .NET, Go, Rust). Releases are +published via [GitHub Releases](https://github.com/fireflyframework/fireflyframework-pyfly/releases) +(PyFly is **not** published to PyPI). Maintainers cut releases; you don't need to bump the +version in your PR — just add the changelog entry. + +## Documentation & the book + +- Framework docs live under [`docs/`](docs/README.md) — keep them in sync with code changes. +- The project-driven book, *PyFly by Example*, lives under [`book/`](book/); its figures and + cover are generated (see [`assets/README.md`](assets/README.md) for the shared brand system). + +## Questions + +Open a [discussion or issue](https://github.com/fireflyframework/fireflyframework-pyfly/issues). +Thank you for contributing! 🐍✨ diff --git a/README.md b/README.md index bf94be03..f2bbd26d 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,11 @@

- PyFly Logo + PyFly — Event-Driven Python Microservices with the Firefly Framework

+

PyFly

+

- The Official Python Implementation of the Firefly Framework + The official Python implementation of the Firefly Framework — Spring Boot's cohesion, native to async Python.

@@ -11,16 +13,46 @@ Firefly Framework Python 3.12+ License: Apache 2.0 - Version: 26.06.103 - Type Checked: mypy strict - Code Style: Ruff - Async First + Version: 26.06.113 + Type Checked: mypy strict + Code Style: Ruff + Async First

Build production-grade Python applications with the patterns you trust — dependency injection, CQRS, event-driven architecture, and more — powered by the Firefly Framework.

+

+ 📘 The Book  ·  + Quickstart  ·  + Why PyFly  ·  + Architecture  ·  + Patterns  ·  + Modules  ·  + Docs  ·  + Changelog +

+ +
+Table of contents + +- [📘 The Book — *PyFly by Example*](#-the-book--pyfly-by-example) +- [Why PyFly?](#why-pyfly) +- [Quickstart](#quickstart) +- [Philosophy](#philosophy) +- [Architecture](#architecture) — [Dependency Injection](#dependency-injection) · [Hexagonal](#hexagonal-architecture) · [Auto-Configuration](#auto-configuration) · [Request Lifecycle](#request-lifecycle) +- [Featured Patterns](#featured-patterns) +- [Installation](#installation) +- [CLI & Project Scaffolding](#cli--project-scaffolding) +- [Modules](#modules) +- [Documentation](#documentation) +- [The Firefly Ecosystem](#firefly-framework-ecosystem) +- [Roadmap](#roadmap) · [Versioning](#versioning) · [Changelog](#changelog) +- [Requirements](#requirements) · [Contributing](#contributing) · [License](#license) + +
+ --- ## 📘 The Book — *PyFly by Example* @@ -33,7 +65,9 @@ It covers the whole stack: dependency injection, configuration & profiles, the w --- -## The Problem +## Why PyFly? + +### The problem You'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: @@ -49,7 +83,7 @@ You assemble a bespoke stack, glue it together, and move on. Six months later, a --- -## What is PyFly? +### What PyFly is PyFly makes these decisions for you. @@ -96,6 +130,30 @@ Coming from Spring Boot? See the [Spring Boot Comparison Guide](docs/spring-comp --- +## Quickstart + +Zero to a running, production-grade service in under a minute: + +```bash +# 1 · Install the CLI + framework (one line) +curl -fsSL https://get.pyfly.io/ | bash + +# 2 · Scaffold a REST API with batteries included +pyfly new my-service --archetype web-api +cd my-service + +# 3 · Run it — structured logging, health checks, metrics & OpenAPI all on by default +pyfly run --reload + +# 4 · It's live +curl http://localhost:8080/health # {"status":"UP"} +# └ OpenAPI / Swagger UI at http://localhost:8080/docs +``` + +That is a fully wired service — DI container, web layer, actuator, observability, security headers, graceful shutdown — from **one install and one command**. See [Installation](#installation) for uv / pip / Docker options and [CLI & Project Scaffolding](#cli--project-scaffolding) for every archetype. + +--- + ## Philosophy Four 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?* @@ -133,7 +191,13 @@ The first time you run `pyfly run`, your application already has structured logg --- -## How It Works +## Architecture + +PyFly is **cohesive, layered, and hexagonal**: five layers on an async core, every external system reached through a Protocol port, and the right adapters wired automatically at startup. + +

+ PyFly architecture at a glance: one front door (pyfly + extras, @pyfly_application) over five layers — Cross-Cutting, Integration, Infrastructure, Application, Foundation — on an async core (asyncio, uvloop, ASGI). +

### Dependency Injection @@ -169,57 +233,23 @@ All stereotypes default to **singleton scope** (one instance per application). Y **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. +### Request Lifecycle + +Once the graph is built, a request flows through it end-to-end — filters, controller, service, port, adapter, database — with the response and any domain events on the way back, and you wrote none of the wiring: + +

+ PyFly request lifecycle: an HTTP request flows through the web-filter chain, the @rest_controller (with validation), the @service, a repository port, the SQLAlchemy adapter, and PostgreSQL; a domain event is published and a JSON response is returned. +

+ ### Hexagonal Architecture Every 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. This separation is not conceptual — it is enforced by package structure: -``` -┌──────────────────────────────────────────────────────────┐ -│ APPLICATION LAYER │ -│ │ -│ Your services, controllers, and domain logic. │ -│ They depend ONLY on ports. │ -│ │ -│ @service │ -│ class OrderService: │ -│ repo: RepositoryPort[Order, int] │ -│ events: EventPublisher │ -│ cache: CacheAdapter │ -│ │ -└────────────────────────────┬─────────────────────────────┘ - │ depends on -┌────────────────────────────┴─────────────────────────────┐ -│ PORTS (Python Protocols) │ -│ │ -│ pyfly.data RepositoryPort[T, ID] │ -│ pyfly.messaging MessageBrokerPort │ -│ pyfly.cache CacheAdapter │ -│ pyfly.eda EventPublisher │ -│ pyfly.client HttpClientPort │ -│ pyfly.scheduling TaskExecutorPort │ -│ pyfly.shell ShellRunnerPort │ -│ pyfly.web WebServerPort │ -│ │ -└────────────────────────────┬─────────────────────────────┘ - │ implements -┌────────────────────────────┴─────────────────────────────┐ -│ ADAPTERS (Concrete Implementations) │ -│ │ -│ pyfly.data.relational.sqlalchemy │ -│ pyfly.data.document.mongodb │ -│ pyfly.messaging.adapters.kafka │ -│ pyfly.messaging.adapters.rabbitmq │ -│ pyfly.cache.adapters.redis │ -│ pyfly.eda.adapters.memory │ -│ pyfly.client.adapters.httpx_adapter │ -│ pyfly.scheduling.adapters.asyncio_executor │ -│ pyfly.shell.adapters.click_adapter │ -│ pyfly.web.adapters.starlette │ -│ │ -└──────────────────────────────────────────────────────────┘ -``` +

+ PyFly hexagonal architecture: an application core that depends only on Protocol ports (RepositoryPort, MessageBrokerPort, CacheAdapter, EventPublisher, HttpClientPort, WebServerPort), with swappable adapters — SQLAlchemy/MongoDB, Kafka/RabbitMQ, Redis, httpx, Starlette/FastAPI — implementing each port. +

The practical result — swap any adapter without changing a single line of business logic: @@ -247,6 +277,10 @@ class OrderService: PyFly detects installed libraries at startup and wires the right adapters automatically — no manual bean registration needed. +

+ PyFly auto-configuration flow: entry-point discovery finds each @auto_configuration, conditional guards (@conditional_on_class, @conditional_on_missing_bean) decide, and the result is to bind an adapter (e.g. RedisCacheAdapter), fall back (InMemoryCache), or skip when your own bean already wins. +

+ This works through two complementary mechanisms: **1. Declarative auto-configuration** — `@configuration` classes guarded by conditions. They act as "default with override" factories: @@ -312,6 +346,10 @@ PyFly is more than a web framework — it ships **production-grade implementatio The sections below show one representative example per pattern. The full guides live under [`docs/modules/`](docs/modules/README.md). +

+ PyFly distributed transaction patterns: a saga DAG (reserve → charge → ship) with reverse-order compensation, alongside summary cards for Saga (compensation-based), Workflow (durable, signal-driven), and TCC (try/confirm/cancel). +

+ ### Saga — Distributed Transaction with Compensation Coordinate 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. @@ -1145,47 +1183,30 @@ The git tag and human-readable display use the leading-zero form (`v26.05.01`); ## Changelog -See **[CHANGELOG.md](CHANGELOG.md)** for detailed release notes. - -**Current:** `v26.05.04` (2026-05-08) — `pyfly.security` import-chain fix: - -- **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`. -- Verified: bare wheel install (`pip install pyfly`) now exposes `pyfly.domain` immediately; the `[web,cqrs,transactional,eventsourcing]` extras unblock the full application bootstrap path. - -**Previous:** `v26.05.03` (2026-05-08) — Functional starters + Java/.NET parity: - -- **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. -- **`@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. -- **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). -- **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, ...`. -- **Layered docs** — new [`docs/modules/starters.md`](docs/modules/starters.md) explains the property-layering model (framework defaults < starter defaults < user yaml < profile overlays < env vars) and shows the cross-language correspondence table. +The full release history lives in **[CHANGELOG.md](CHANGELOG.md)** ([Keep a Changelog](https://keepachangelog.com/) format). Recent highlights: -**Previous:** `v26.05.02` (2026-05-08) — DDD primitives + OrderService sample + async-saga fix: +- **`v26.06.113`** (2026-06-17) — **server-layer observability**: per-server metrics (active connections, in-flight requests, workers, uptime) across Uvicorn / Granian / Hypercorn, correct multi-worker Prometheus aggregation, and a live admin **Observability** dashboard. +- **`v26.06.112`** (2026-06-16) — *PyFly by Example* figures rebuilt in one polished, vector visual language (English + Spanish editions). +- **`v26.05.01`** (2026-05-07) — **full Java-framework parity**: the Saga + Workflow + TCC transactional engine, nine new modules (event sourcing, callbacks, webhooks, notifications, IDP, ECM, plugins, rule engine, config server), 12 third-party adapters, and the move to CalVer. -- **`pyfly.domain`** — pure-Python DDD building blocks: `Entity`, `ValueObject`, `AggregateRoot`, `DomainEvent`, `Specification` (with `&` / `|` / `~` combinators), `DomainRepository` protocol, `DomainException` / `BusinessRuleViolation` / `AggregateNotFound`. Mirrors `fireflyframework-starter-domain` (Java) and `FireflyFramework.Starter.Domain` (.NET). -- **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. -- **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`. - -**Previous:** `v26.05.01` (2026-05-07) — Full Java framework parity: - -- **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) -- **Nine new modules** — `eventsourcing`, `callbacks`, `webhooks`, `notifications`, `idp`, `ecm`, `plugins`, `rule_engine`, `config_server` -- **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) -- **Four new client protocols** — SOAP, gRPC, GraphQL, WebSocket (joining the existing HTTP / OpenAPI generators) -- **16 domain validators** — including IBAN, BIC, ISO country/currency codes, phone numbers, dates, national IDs, sort codes, interest rates -- **CalVer migration** — `YY.MM.PATCH` aligning with all Firefly Framework siblings (Java, .NET, Go) +See **[CHANGELOG.md](CHANGELOG.md)** for every release and the complete notes. --- ## Firefly Framework Ecosystem -PyFly is part of the [Firefly Framework](https://github.com/fireflyframework) ecosystem: +PyFly is part of the [Firefly Framework](https://github.com/fireflyframework) ecosystem — one programming model across every runtime: + +

+ The Firefly Framework family: Java/Spring Boot, .NET, PyFly (Python, highlighted), Rust, Go CLI, Angular frontend, and GenAI — all sharing one programming model. +

| Platform | Repository | Status | |----------|-----------|--------| | **Java / Spring Boot** | [`fireflyframework-*`](https://github.com/fireflyframework) (40+ modules) | Production | | **.NET 9** | [`fireflyframework-dotnet`](https://github.com/fireflyframework/fireflyframework-dotnet) | Beta (CalVer 26.05+) | | **Python** | [`fireflyframework-pyfly`](https://github.com/fireflyframework/fireflyframework-pyfly) | Beta (CalVer 26.05+) | +| **Rust** | [`fireflyframework-rust`](https://github.com/fireflyframework/fireflyframework-rust) | Active Development | | **Frontend (Angular)** | [`flyfront`](https://github.com/fireflyframework/flyfront) | Active Development | | **GenAI** | [`fireflyframework-genai`](https://github.com/fireflyframework/fireflyframework-genai) | Active Development | | **CLI (Go)** | [`fireflyframework-cli`](https://github.com/fireflyframework/fireflyframework-cli) | Active Development | @@ -1203,6 +1224,21 @@ PyFly is part of the [Firefly Framework](https://github.com/fireflyframework) ec --- +## Contributing + +Contributions are welcome. PyFly is type-checked with **mypy (strict)**, formatted and linted with **Ruff**, and tested with **pytest** — the same gates CI enforces: + +```bash +uv sync --all-extras --group dev +uv run ruff format . && uv run ruff check . +uv run mypy src +uv run pytest +``` + +Branch from `main`, keep changes focused, add tests for new behaviour, and open a pull request. By contributing you agree your work is licensed under Apache-2.0. See **[CONTRIBUTING.md](CONTRIBUTING.md)** for the full guide. + +--- + ## License Apache License 2.0 — [Firefly Software Foundation.](https://github.com/fireflyframework) diff --git a/assets/README.md b/assets/README.md new file mode 100644 index 00000000..2688eea2 --- /dev/null +++ b/assets/README.md @@ -0,0 +1,60 @@ +# PyFly brand assets + +The visual identity used across the README and the book. **PyFly is the green +firefly of the [Firefly Framework](https://github.com/fireflyframework) family** — +the same firefly-in-the-dark language as the Java / .NET / Rust siblings, recolored +to Python green (the logo's green-yellow sparkles *are* the family's fireflies). + +## Assets + +| File | What it is | +|------|------------| +| [`pyfly-logo.png`](pyfly-logo.png) | The master logo — the green Python snake on a leaf-wing + the `pyFly` wordmark (RGBA, 1648×748). Source of brand truth. | +| [`banner.svg`](banner.svg) | README hero banner (1280×320). Dark cosmic sky, green fireflies + light trails, the **actual snake** (cropped from the logo, embedded) and a **Maven Pro** `pyFly` wordmark vectorized to paths. | +| [`architecture.svg`](architecture.svg) | "Architecture at a glance" — front door → five layers → async core. | +| [`hexagonal.svg`](hexagonal.svg) | Ports & adapters: an application core, Protocol ports, swappable adapters. | +| [`auto-configuration.svg`](auto-configuration.svg) | Entry-point discovery → conditional guards → bind / fall back / skip. | +| [`request-lifecycle.svg`](request-lifecycle.svg) | An HTTP request flowing through filters → controller → service → port → adapter → DB. | +| [`distributed-patterns.svg`](distributed-patterns.svg) | A saga DAG with reverse-order compensation + Saga / Workflow / TCC cards. | +| [`ecosystem.svg`](ecosystem.svg) | The Firefly family constellation (Java · .NET · **PyFly** · Rust · Go · Angular · GenAI). | + +## Design system + +- **Typeface:** [Maven Pro](https://fonts.google.com/specimen/Maven+Pro) for the + wordmark (also the book's typeface). System sans / `ui-monospace` for diagram text. +- **Green palette:** lime `#7ed321` · primary `#4cbb2f` · `#43b02a` / `#3a9e23` · + mid `#2c8a1c` · deep `#1f5e16` · body text `#33402e` · muted `#7c876f` · + card stroke `#dfe7d4` · panel `#f4f9ee`. Firefly glow `#9bd24a → #c2e85f → #dff58a`. +- **Banner sky:** `#07110b → #0a1410 → #0c1a0f` with a green radial ambient. +- **Motifs:** firefly motes, a green title rule, deep-green numbered badges, + monospace module lists, and brand icons ([Simple Icons](https://simpleicons.org/)). +- **GitHub-safe:** every SVG is self-contained — no `