diff --git a/CHANGELOG.md b/CHANGELOG.md index 93b9907..39b0acf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,42 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project uses **CalVer `YY.M.PP`** (PEP 440 may normalise patch numbers for the Python wheel — e.g. `26.06.00` → `26.6.0`). +## [26.6.10] - 2026-06-15 + +### Changed + +- **Adopted pyfly's separate management port (app `8080`, management `9090`).** + Upgraded to pyfly `v26.06.103`, which serves the actuator (`/actuator/*`) and + the admin dashboard (`/admin`) on a dedicated management port + (`pyfly.management.server.port`, default `9090`) instead of the business API + port. flydocs now runs the API on **`8080`** (`pyfly.server.port`, was `8400`) + and exposes actuator/admin/health on **`9090`**: + - `pyfly.yaml`, `IDPSettings.port` and `FLYDOCS_PORT` default to `8080`; + `pyfly.management.server.port: 9090` is configured explicitly. + - `Dockerfile` exposes `8080` + `9090`; `docker-compose` maps both for the API + and exposes each worker's management port (`9091`/`9092`), and every + health-check now probes `:9090/actuator/health/readiness`. + - Worker health server (`worker_health_port`) defaults to `9090` to match. + - **Migration:** point load balancers / clients at `:8080` for the API and + Kubernetes probes / Prometheus at `:9090`. Set + `PYFLY_MANAGEMENT_SERVER_PORT=8080` to collapse back to a single port. + +- **`pyfly` is now consumed from its published GitHub tag.** `[tool.uv.sources]` + pins `pyfly` to `git tag v26.06.103` (matching the `fireflyframework-agentic` + pattern) instead of the local editable path; the dependency floor is + `>=26.6.103`. + +### Fixed + +- `main.py` read the removed `pyfly.web.host` key; it now reads + `pyfly.server.host` (Spring `server.address` parity). + +### Added + +- **Python SDK:** `Client` / `AsyncClient` accept an optional `management_url` + so `health()` can target the management port (`:9090`) while API calls use the + business `base_url` (`:8080`); back-compatible — unset falls back to `base_url`. + ## [26.6.9] - 2026-06-15 ### Fixed diff --git a/Dockerfile b/Dockerfile index b675495..429f097 100644 --- a/Dockerfile +++ b/Dockerfile @@ -162,7 +162,9 @@ RUN find /app -type f -exec chmod a+r {} + \ ENV PYTHONPATH=/app/src USER idp -EXPOSE 8400 +# 8080 = business API (pyfly.server.port); 9090 = management (actuator + admin, +# pyfly.management.server.port) and the worker health server. +EXPOSE 8080 9090 ENTRYPOINT ["/app/docker-entrypoint.sh"] CMD ["serve"] diff --git a/QUICKSTART.md b/QUICKSTART.md index bc0c4e2..4070b9a 100644 --- a/QUICKSTART.md +++ b/QUICKSTART.md @@ -17,13 +17,13 @@ The repo ships with a docker-compose stack that brings up the service, a Postgre ```bash git clone https://github.com/firefly-operationOS/flydocs.git cd flydocs -task docker:up:test # serves http://localhost:8400 backed by a mock LLM +task docker:up:test # serves http://localhost:8080 backed by a mock LLM ``` While it boots, verify the readiness probe: ```bash -curl http://localhost:8400/actuator/health/readiness +curl http://localhost:9090/actuator/health/readiness # {"status":"UP","components":{"database_health":...,"eda_health":...}} ``` @@ -35,7 +35,7 @@ B64=$(base64 < invoice.pdf | tr -d '\n') # 2. POST a minimal ExtractionRequest. ``document_types[]`` declares what to extract; # ``files[]`` carries the binary. Everything else has sensible defaults. -curl -sS http://localhost:8400/api/v1/extract \ +curl -sS http://localhost:8080/api/v1/extract \ -H 'Content-Type: application/json' \ -d @- < http://localhost:{{.PORT}}"' - 'echo "docs -> http://localhost:{{.PORT}}/docs"' - - 'echo "health-> http://localhost:{{.PORT}}/actuator/health"' + - 'echo "health-> http://localhost:{{.MGMT_PORT}}/actuator/health"' docker:logs: desc: Tail logs from every container. @@ -179,17 +180,17 @@ tasks: health: desc: Curl the /actuator/health endpoint of the running API. cmds: - - 'curl -fsS http://localhost:{{.PORT}}/actuator/health | jq .' + - 'curl -fsS http://localhost:{{.MGMT_PORT}}/actuator/health | jq .' health:readiness: desc: Curl /actuator/health/readiness -- shows the database + EDA components. cmds: - - 'curl -fsS http://localhost:{{.PORT}}/actuator/health/readiness | jq .' + - 'curl -fsS http://localhost:{{.MGMT_PORT}}/actuator/health/readiness | jq .' health:liveness: desc: Curl /actuator/health/liveness -- cheap, no broker pings. cmds: - - 'curl -fsS http://localhost:{{.PORT}}/actuator/health/liveness | jq .' + - 'curl -fsS http://localhost:{{.MGMT_PORT}}/actuator/health/liveness | jq .' eda:outbox: desc: | diff --git a/docker-compose.yml b/docker-compose.yml index 903d023..c48a5bc 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -72,7 +72,8 @@ services: container_name: flydocs-api command: ["serve"] ports: - - "${IDP_PORT:-8400}:8400" + - "${IDP_PORT:-8080}:8080" + - "${IDP_MGMT_PORT:-9090}:9090" # Optional .env -- compose v2 honours ``required: false`` so the stack # boots even without a local .env (provider keys fall back to the host # shell env). ``task docker:up`` also auto-creates .env from env_template. @@ -97,7 +98,7 @@ services: gotenberg: condition: service_healthy healthcheck: - test: ["CMD", "curl", "--fail", "http://localhost:8400/actuator/health/readiness"] + test: ["CMD", "curl", "--fail", "http://localhost:9090/actuator/health/readiness"] interval: 5s timeout: 3s retries: 30 @@ -107,6 +108,10 @@ services: image: flydocs:latest container_name: flydocs-worker command: ["worker"] + # Worker health/management server (pyfly actuator) on 9090 inside the + # container; mapped to a distinct host port to avoid clashing with the API. + ports: + - "${IDP_WORKER_MGMT_PORT:-9091}:9090" env_file: - path: .env required: false @@ -126,7 +131,7 @@ services: gotenberg: condition: service_healthy healthcheck: - test: ["CMD", "curl", "--fail", "http://localhost:8400/actuator/health/readiness"] + test: ["CMD", "curl", "--fail", "http://localhost:9090/actuator/health/readiness"] interval: 5s timeout: 3s retries: 30 @@ -144,6 +149,10 @@ services: image: flydocs:latest container_name: flydocs-bbox-worker command: ["bbox-worker"] + # Worker health/management server (pyfly actuator) on 9090 inside the + # container; mapped to a distinct host port to avoid clashing with the API. + ports: + - "${IDP_BBOX_WORKER_MGMT_PORT:-9092}:9090" env_file: - path: .env required: false @@ -169,7 +178,7 @@ services: gotenberg: condition: service_healthy healthcheck: - test: ["CMD", "curl", "--fail", "http://localhost:8400/actuator/health/readiness"] + test: ["CMD", "curl", "--fail", "http://localhost:9090/actuator/health/readiness"] interval: 5s timeout: 3s retries: 30 diff --git a/docs/README.md b/docs/README.md index a486bcc..3b111bd 100644 --- a/docs/README.md +++ b/docs/README.md @@ -127,7 +127,7 @@ DTOs documented here. Two paths: ```bash # Against a running service: -curl -s http://localhost:8400/openapi.json | jq +curl -s http://localhost:8080/openapi.json | jq # Or via the task target (writes to ./openapi.json): task openapi diff --git a/docs/cicd.md b/docs/cicd.md index 9c20494..98dd9df 100644 --- a/docs/cicd.md +++ b/docs/cicd.md @@ -242,11 +242,11 @@ spec: livenessProbe: httpGet: path: /actuator/health/liveness - port: 8400 + port: 9090 readinessProbe: httpGet: path: /actuator/health/readiness - port: 8400 + port: 9090 ``` `/actuator/health/readiness` reflects the DB + EDA bus state via the diff --git a/docs/deployment.md b/docs/deployment.md index 9a0f2c7..4ac4671 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -69,7 +69,7 @@ The full list lives in [`env_template`](../env_template). The hot ones: ```env -FLYDOCS_PORT=8400 +FLYDOCS_PORT=8080 FLYDOCS_LOG_LEVEL=INFO FLYDOCS_DATABASE_URL=postgresql+asyncpg://idp:s3cret@db:5432/flydocs @@ -206,11 +206,11 @@ step, not a request handler. ```yaml livenessProbe: - httpGet: { path: /actuator/health/liveness, port: 8400 } + httpGet: { path: /actuator/health/liveness, port: 9090 } initialDelaySeconds: 10 periodSeconds: 30 readinessProbe: - httpGet: { path: /actuator/health/readiness, port: 8400 } + httpGet: { path: /actuator/health/readiness, port: 9090 } initialDelaySeconds: 5 periodSeconds: 5 ``` @@ -255,7 +255,7 @@ task inside the worker process, so all three workloads take the probes above. Specifics: - **Port.** `FLYDOCS_WORKER_HEALTH_PORT` when set, otherwise - `FLYDOCS_PORT` (default 8400). `0` disables the server — useful when + `FLYDOCS_PORT` (default 8080). `0` disables the server — useful when `serve` and `worker` share a host in dev. The server binds `0.0.0.0` because the kubelet probes the pod IP, never loopback. - **Probe semantics.** Indicators discovered from the DI container diff --git a/docs/migration-v0-to-v1.md b/docs/migration-v0-to-v1.md index 775d3b5..cdfdb8d 100644 --- a/docs/migration-v0-to-v1.md +++ b/docs/migration-v0-to-v1.md @@ -774,7 +774,7 @@ import com.firefly.flydocs.sdk.model.WebhookEnvelope; **Python (v0):** ```python -client = FlydocsClient(base_url="http://localhost:8400") +client = FlydocsClient(base_url="http://localhost:8080") result = client.extract(ExtractionRequest( documents=[DocumentInput.from_path("invoice.pdf")], docs=[DocSpec( @@ -788,7 +788,7 @@ print(result.request_id, result.model) **Python (v1):** ```python -client = FlydocsClient(base_url="http://localhost:8400") +client = FlydocsClient(base_url="http://localhost:8080") result = client.extract(ExtractionRequest( files=[FileInput.from_path("invoice.pdf")], document_types=[DocumentTypeSpec( @@ -802,7 +802,7 @@ print(result.id, result.pipeline.model) **Java (v0):** ```java -FlydocsClient client = FlydocsClient.builder().baseUrl("http://localhost:8400").build(); +FlydocsClient client = FlydocsClient.builder().baseUrl("http://localhost:8080").build(); ExtractionResult result = client.extract(ExtractionRequest.of( List.of(DocumentInput.ofPath(Path.of("invoice.pdf"))), List.of(DocSpec.builder() @@ -815,7 +815,7 @@ System.out.println(result.requestId() + " " + result.model()); **Java (v1):** ```java -FlydocsClient client = FlydocsClient.builder().baseUrl("http://localhost:8400").build(); +FlydocsClient client = FlydocsClient.builder().baseUrl("http://localhost:8080").build(); ExtractionResult result = client.extract(ExtractionRequest.of( List.of(FileInput.ofPath(Path.of("invoice.pdf"))), List.of(DocumentTypeSpec.builder() diff --git a/docs/payload-reference.md b/docs/payload-reference.md index 055ed95..9c1585c 100644 --- a/docs/payload-reference.md +++ b/docs/payload-reference.md @@ -699,18 +699,18 @@ with `?wait_for_bboxes=true&timeout=60` on ```bash # 1. Submit -curl -sS http://localhost:8400/api/v1/extractions \ +curl -sS http://localhost:8080/api/v1/extractions \ -H 'content-type: application/json' \ -H 'idempotency-key: '"$(uuidgen)" \ -d @request.json # → 202 {"id": "ext_01HEM...", "status": "queued", ...} # 2. Poll state -curl -sS http://localhost:8400/api/v1/extractions/ext_01HEM... +curl -sS http://localhost:8080/api/v1/extractions/ext_01HEM... # → 200 {"id":"ext_01HEM...","status":"running",...} # 3. Fetch result (long-poll for grounded bboxes) -curl -sS 'http://localhost:8400/api/v1/extractions/ext_01HEM.../result?wait_for_bboxes=true&timeout=120' +curl -sS 'http://localhost:8080/api/v1/extractions/ext_01HEM.../result?wait_for_bboxes=true&timeout=120' # → 200 {"id":"ext_01HEM...","result":{...ExtractionResult...}} ``` diff --git a/env_template b/env_template index 4ad1743..0811544 100644 --- a/env_template +++ b/env_template @@ -8,12 +8,18 @@ # Service # ---------------------------------------------------------------------------- FLYDOCS_LOG_LEVEL=INFO -FLYDOCS_PORT=8400 +# Business API port (pyfly.server.port). The actuator (/actuator/*) and the +# admin dashboard (/admin) are served on the separate management port 9090 +# (pyfly.management.server.port), not this port. +FLYDOCS_PORT=8080 # Port for the HTTP health server the worker modes (``flydocs worker`` / # ``flydocs bbox-worker``) run for Kubernetes httpGet probes against -# /actuator/health/{liveness,readiness}. Unset reuses FLYDOCS_PORT; -# 0 disables it (dev setups running serve + worker on the same host). -#FLYDOCS_WORKER_HEALTH_PORT=8400 +# /actuator/health/{liveness,readiness}. Defaults to the management port 9090 +# (matching where serve-mode exposes the actuator); 0 disables it (dev setups +# running serve + worker on the same host, where serve already owns 9090). +#FLYDOCS_WORKER_HEALTH_PORT=9090 +# Override the management (actuator + admin) port if 9090 clashes locally. +#PYFLY_MANAGEMENT_SERVER_PORT=9090 # ---------------------------------------------------------------------------- # Persistence diff --git a/pyfly.yaml b/pyfly.yaml index e44e6d1..9370568 100644 --- a/pyfly.yaml +++ b/pyfly.yaml @@ -13,7 +13,13 @@ pyfly: server: enabled: true host: 0.0.0.0 - port: 8400 + port: 8080 + # Management server (Spring management.server.* parity): the actuator + # (/actuator/*) and the admin dashboard (/admin) are served on this dedicated + # port, NOT the business API port above. Probes / Prometheus target 9090. + management: + server: + port: 9090 # Observability -- Prometheus metrics + OpenTelemetry tracing. observability: diff --git a/pyproject.toml b/pyproject.toml index c21cd6b..6da3838 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,7 +2,7 @@ name = "flydocs" # CalVer YY.MM.PP -- bumped per release. Note that PEP 440 normalises # ``26.05.01`` -> ``26.5.1`` in the built wheel filename. -version = "26.6.9" +version = "26.6.10" description = "Pure-multimodal Intelligent Document Processing service: structured fields + bounding boxes, validation, authenticity checks, LLM judge, and a business-rule engine. Sync + queue-backed async APIs over fireflyframework-pyfly and -agentic. Part of Firefly OperationOS, platform-agnostic by design." readme = "README.md" requires-python = ">=3.13" @@ -19,7 +19,7 @@ dependencies = [ # so a fresh ``uv sync`` is enough to boot the full stack. The ``web`` # extra declares starlette + uvicorn, which the worker health server # imports directly; the floor carries ``pyfly.actuator.install_health_indicators``. - "pyfly[fastapi,web,observability,security,data-relational,postgresql,eda,redis,client,scheduling,cli]>=26.6.98", + "pyfly[fastapi,web,observability,security,data-relational,postgresql,eda,redis,client,scheduling,cli]>=26.6.103", # GenAI metaframework -- FireflyAgent with multimodal content (BinaryContent/ImageUrl) # over pydantic-ai. Pulls in the OpenAI / Anthropic / Bedrock providers via pydantic-ai-slim. @@ -126,10 +126,12 @@ override-dependencies = [ ] [tool.uv.sources] -# Sibling-repo sources. The agentic framework is consumed from its published -# git tag so a fresh clone / CI build resolves it without the sibling path. -# pyfly stays on the local checkout (released on its own cadence). -pyfly = { path = "../../fireflyframework/fireflyframework-pyfly", editable = true } +# Sibling-repo sources. Both frameworks are consumed from their published git +# tags so a fresh clone / CI build resolves them without a sibling path. The +# (vestigial) ./vendor clone + Dockerfile BuildKit context for pyfly are now +# no-ops — the path-rewrite sed no longer matches a git source — exactly as for +# agentic; they can be removed in a later cleanup. +pyfly = { git = "https://github.com/fireflyframework/fireflyframework-pyfly.git", tag = "v26.06.103" } fireflyframework-agentic = { git = "https://github.com/fireflyframework/fireflyframework-agentic.git", tag = "v26.05.30" } [tool.hatch.build.targets.wheel] diff --git a/scripts/kyb_real_test.py b/scripts/kyb_real_test.py index d6df5b9..a614739 100644 --- a/scripts/kyb_real_test.py +++ b/scripts/kyb_real_test.py @@ -40,7 +40,7 @@ import httpx -API = os.environ.get("FLYDOCS_API", "http://localhost:8400") +API = os.environ.get("FLYDOCS_API", "http://localhost:8080") DEED_PDF = Path("/Users/ancongui/Downloads/resolicituddedocumentacin/2023.03.17_Escrit. consitiucion_DF&IS_registrada.pdf") PACTO_PDF = Path("/Users/ancongui/Downloads/resolicituddedocumentacin/2023.04.21_DF&IS_-_Pacto_de_Socios_Anexos_firmado.pdf") diff --git a/scripts/smoke_async_postgres_eda.sh b/scripts/smoke_async_postgres_eda.sh index f863c45..5ed8b6c 100755 --- a/scripts/smoke_async_postgres_eda.sh +++ b/scripts/smoke_async_postgres_eda.sh @@ -38,7 +38,8 @@ set -euo pipefail PDF="${1:-$HOME/Downloads/escritura_poderes_2025.pdf}" -API="${FLYDOCS_URL:-http://localhost:8400}" +API="${FLYDOCS_URL:-http://localhost:8080}" +MGMT_API="${FLYDOCS_MGMT_URL:-http://localhost:9090}" MODEL="${FLYDOCS_MODEL:-anthropic:claude-sonnet-4-6}" POLL_INTERVAL_S="${POLL_INTERVAL_S:-3}" POLL_MAX_S="${POLL_MAX_S:-300}" @@ -204,7 +205,7 @@ jq ' echo echo "[smoke] /actuator/health/readiness:" -curl -sS "$API/actuator/health/readiness" | jq . +curl -sS "$MGMT_API/actuator/health/readiness" | jq . echo echo "[smoke] final EDA cursor:" diff --git a/scripts/smoke_bbox_real.py b/scripts/smoke_bbox_real.py index 558cb7f..345a687 100644 --- a/scripts/smoke_bbox_real.py +++ b/scripts/smoke_bbox_real.py @@ -46,7 +46,7 @@ import pymupdf # pyright: ignore[reportMissingImports] from PIL import Image, ImageDraw, ImageFont -API = "http://localhost:8400" +API = "http://localhost:8080" OUT = Path("/tmp/flydocs-viz") OUT.mkdir(parents=True, exist_ok=True) diff --git a/sdks/java/QUICKSTART.md b/sdks/java/QUICKSTART.md index d770f07..4c08e79 100644 --- a/sdks/java/QUICKSTART.md +++ b/sdks/java/QUICKSTART.md @@ -56,7 +56,7 @@ into the application context from `flydocs.*` properties. From the flydocs repo root: ```bash -task docker:up:test # serves http://localhost:8400 backed by a mock LLM +task docker:up:test # serves http://localhost:8080 backed by a mock LLM ``` If you already have a running flydocs deployment, point `baseUrl` at it and @@ -90,7 +90,7 @@ public class Quickstart { // 3. Call the service. FlydocsClientAsync is the primary integration // surface; it's reactive (Project Reactor) and non-blocking. try (FlydocsClientAsync flydocs = FlydocsClientAsync.builder() - .baseUrl("http://localhost:8400") + .baseUrl("http://localhost:8080") .build()) { ExtractionResult result = flydocs.extract(request) @@ -127,7 +127,7 @@ the autoconfig wire everything from properties: ```yaml # application.yaml flydocs: - base-url: http://localhost:8400 + base-url: http://localhost:8080 api-key: ${FLYDOCS_API_KEY} # optional, Authorization: Bearer timeout: 60s max-attempts: 3 # retry transient 5xx @@ -173,7 +173,7 @@ If you can't take a reactive dependency: import com.firefly.flydocs.sdk.FlydocsClient; FlydocsClient flydocs = FlydocsClient.builder() - .baseUrl("http://localhost:8400") + .baseUrl("http://localhost:8080") .build(); ExtractionResult result = flydocs.extract(request); diff --git a/sdks/java/README.md b/sdks/java/README.md index ca97214..15dd4b4 100644 --- a/sdks/java/README.md +++ b/sdks/java/README.md @@ -68,7 +68,7 @@ Then in your project's `pom.xml`: ```yaml # application.yaml flydocs: - base-url: http://localhost:8400 + base-url: http://localhost:8080 api-key: ${FLYDOCS_API_KEY} # optional; sent as Authorization: Bearer … timeout: 60s max-attempts: 3 # retry transient 5xx with exponential backoff @@ -99,7 +99,7 @@ import com.firefly.flydocs.sdk.model.*; import java.nio.file.Path; FlydocsClient flydocs = FlydocsClient.builder() - .baseUrl("http://localhost:8400") + .baseUrl("http://localhost:8080") .apiKey(System.getenv("FLYDOCS_API_KEY")) .build(); @@ -133,7 +133,7 @@ import com.firefly.flydocs.sdk.model.*; import java.time.Duration; FlydocsClientAsync flydocs = FlydocsClientAsync.builder() - .baseUrl("http://localhost:8400") + .baseUrl("http://localhost:8080") .build(); flydocs.extractions().create(submitRequest, "my-app:invoice:42") @@ -260,7 +260,7 @@ cd sdks/java mvn verify # core + starter unit tests # Live integration tests against a running service (tag-gated): -FLYDOCS_BASE_URL=http://localhost:8400 \ +FLYDOCS_BASE_URL=http://localhost:8080 \ mvn -pl flydocs-sdk test -Dgroups=integration ``` diff --git a/sdks/java/TUTORIAL.md b/sdks/java/TUTORIAL.md index 7d615d4..eb72bd5 100644 --- a/sdks/java/TUTORIAL.md +++ b/sdks/java/TUTORIAL.md @@ -5,7 +5,7 @@ A complete walkthrough of the flydocs Java/Spring Boot SDK against the v1 API. E > **Prerequisites** > A flydocs service reachable at some base URL. For local development: > ```bash -> task docker:up:test # starts flydocs + a mock LLM at http://localhost:8400 +> task docker:up:test # starts flydocs + a mock LLM at http://localhost:8080 > ``` > Java 25 + Maven 3.9+ on the build host. @@ -72,7 +72,7 @@ import com.firefly.flydocs.sdk.model.*; import java.nio.file.Path; FlydocsClient flydocs = FlydocsClient.builder() - .baseUrl("http://localhost:8400") + .baseUrl("http://localhost:8080") .build(); ExtractionRequest req = ExtractionRequest.builder() @@ -368,7 +368,7 @@ when the SDK is on the classpath and the base URL is configured. ```yaml # application.yaml flydocs: - base-url: http://localhost:8400 + base-url: http://localhost:8080 api-key: ${FLYDOCS_API_KEY} # optional, Authorization: Bearer timeout: 60s max-attempts: 3 # retry transient 5xx + timeouts @@ -420,7 +420,7 @@ test, non-Spring app): ```java FlydocsClientAsync flydocs = FlydocsClientAsync.builder() - .baseUrl("http://localhost:8400") + .baseUrl("http://localhost:8080") .apiKey(System.getenv("FLYDOCS_API_KEY")) .timeout(Duration.ofSeconds(60)) .maxAttempts(3) // retry 5xx + timeouts @@ -445,7 +445,7 @@ yourself; let Spring handle it when you use the starter. ```java try (FlydocsClient flydocs = FlydocsClient.builder() - .baseUrl("http://localhost:8400") + .baseUrl("http://localhost:8080") .maxAttempts(3) .build()) { ExtractionResult result = flydocs.extract(req); @@ -463,7 +463,7 @@ import com.firefly.flydocs.sdk.FlydocsClientAsync; import reactor.core.publisher.Mono; FlydocsClientAsync flydocs = FlydocsClientAsync.builder() - .baseUrl("http://localhost:8400") + .baseUrl("http://localhost:8080") .build(); Mono result = flydocs.extract(req, "my-idempotency-key", "my-correlation-id"); diff --git a/sdks/java/flydocs-examples/README.md b/sdks/java/flydocs-examples/README.md index 83727f8..618c3de 100644 --- a/sdks/java/flydocs-examples/README.md +++ b/sdks/java/flydocs-examples/README.md @@ -18,7 +18,7 @@ Plus [`ExampleHelpers`](./src/main/java/com/firefly/flydocs/examples/ExampleHelp Spin up a local flydocs first: ```bash -task docker:up:test # serves http://localhost:8400 backed by the mock LLM +task docker:up:test # serves http://localhost:8080 backed by the mock LLM ``` Then run any example. The plain extractor / async / sync ones take a PDF path as `-Dexec.args`: @@ -56,7 +56,7 @@ mvn -pl flydocs-examples compile exec:java \ The webhook receiver is a Spring Boot app — run it with `spring-boot:run`: ```bash -FLYDOCS_BASE_URL=http://localhost:8400 \ +FLYDOCS_BASE_URL=http://localhost:8080 \ FLYDOCS_WEBHOOK_SECRET=super-secret \ mvn -pl flydocs-examples spring-boot:run \ -Dspring-boot.run.mainClass=com.firefly.flydocs.examples.WebhookReceiverApplication @@ -66,7 +66,7 @@ Then POST a flydocs-signed envelope to `http://localhost:8080/flydocs/webhook` w ## Configuration -Every example reads `FLYDOCS_BASE_URL` from the environment; if unset it defaults to `http://localhost:8400`. Point at any flydocs deployment to run against real infrastructure. +Every example reads `FLYDOCS_BASE_URL` from the environment; if unset it defaults to `http://localhost:8080`. Point at any flydocs deployment to run against real infrastructure. The mock LLM that `task docker:up:test` brings up accepts any document and returns a fixed schema-compatible response, so the examples work end-to-end without an Anthropic / OpenAI key. diff --git a/sdks/java/flydocs-examples/src/main/java/com/firefly/flydocs/examples/ExampleHelpers.java b/sdks/java/flydocs-examples/src/main/java/com/firefly/flydocs/examples/ExampleHelpers.java index 568fa70..25e8e55 100644 --- a/sdks/java/flydocs-examples/src/main/java/com/firefly/flydocs/examples/ExampleHelpers.java +++ b/sdks/java/flydocs-examples/src/main/java/com/firefly/flydocs/examples/ExampleHelpers.java @@ -63,6 +63,6 @@ static RuleSpec customerNamePresentRule() { /** Default base URL when {@code FLYDOCS_BASE_URL} is unset. */ static String defaultBaseUrl() { String env = System.getenv("FLYDOCS_BASE_URL"); - return env != null && !env.isEmpty() ? env : "http://localhost:8400"; + return env != null && !env.isEmpty() ? env : "http://localhost:8080"; } } diff --git a/sdks/java/flydocs-examples/src/main/java/com/firefly/flydocs/examples/WebhookReceiverApplication.java b/sdks/java/flydocs-examples/src/main/java/com/firefly/flydocs/examples/WebhookReceiverApplication.java index 4e0e0d9..f9020b4 100644 --- a/sdks/java/flydocs-examples/src/main/java/com/firefly/flydocs/examples/WebhookReceiverApplication.java +++ b/sdks/java/flydocs-examples/src/main/java/com/firefly/flydocs/examples/WebhookReceiverApplication.java @@ -37,13 +37,13 @@ * *
{@code
  * flydocs:
- *   base-url: http://localhost:8400        # required to enable the starter
+ *   base-url: http://localhost:8080        # required to enable the starter
  *   webhook:
  *     secret: ${FLYDOCS_WEBHOOK_SECRET}
  * }
* *
{@code
- * FLYDOCS_BASE_URL=http://localhost:8400 \
+ * FLYDOCS_BASE_URL=http://localhost:8080 \
  * FLYDOCS_WEBHOOK_SECRET=super-secret \
  * mvn -pl flydocs-examples spring-boot:run \
  *     -Dspring-boot.run.mainClass=com.firefly.flydocs.examples.WebhookReceiverApplication
diff --git a/sdks/java/flydocs-sdk/src/main/java/com/firefly/flydocs/sdk/FlydocsClient.java b/sdks/java/flydocs-sdk/src/main/java/com/firefly/flydocs/sdk/FlydocsClient.java
index 993e399..c0f5e96 100644
--- a/sdks/java/flydocs-sdk/src/main/java/com/firefly/flydocs/sdk/FlydocsClient.java
+++ b/sdks/java/flydocs-sdk/src/main/java/com/firefly/flydocs/sdk/FlydocsClient.java
@@ -36,7 +36,7 @@
  *
  * 
{@code
  * FlydocsClient flydocs = FlydocsClient.builder()
- *         .baseUrl("http://localhost:8400")
+ *         .baseUrl("http://localhost:8080")
  *         .build();
  *
  * VersionInfo info        = flydocs.version();
diff --git a/sdks/java/flydocs-sdk/src/main/java/com/firefly/flydocs/sdk/FlydocsClientAsync.java b/sdks/java/flydocs-sdk/src/main/java/com/firefly/flydocs/sdk/FlydocsClientAsync.java
index d113f5f..2cc9de5 100644
--- a/sdks/java/flydocs-sdk/src/main/java/com/firefly/flydocs/sdk/FlydocsClientAsync.java
+++ b/sdks/java/flydocs-sdk/src/main/java/com/firefly/flydocs/sdk/FlydocsClientAsync.java
@@ -56,7 +56,7 @@
  *
  * 
{@code
  * FlydocsClientAsync flydocs = FlydocsClientAsync.builder()
- *         .baseUrl("http://localhost:8400")
+ *         .baseUrl("http://localhost:8080")
  *         .timeout(Duration.ofSeconds(60))
  *         .build();
  *
diff --git a/sdks/java/flydocs-sdk/src/test/java/com/firefly/flydocs/sdk/integration/LiveApiIntegrationTest.java b/sdks/java/flydocs-sdk/src/test/java/com/firefly/flydocs/sdk/integration/LiveApiIntegrationTest.java
index c6f1051..0b16606 100644
--- a/sdks/java/flydocs-sdk/src/test/java/com/firefly/flydocs/sdk/integration/LiveApiIntegrationTest.java
+++ b/sdks/java/flydocs-sdk/src/test/java/com/firefly/flydocs/sdk/integration/LiveApiIntegrationTest.java
@@ -39,7 +39,7 @@
  * Activate explicitly:

* *
{@code
- * FLYDOCS_BASE_URL=http://localhost:8400 \
+ * FLYDOCS_BASE_URL=http://localhost:8080 \
  *   mvn -pl flydocs-sdk test -Dgroups=integration
  * }
*/ diff --git a/sdks/java/flydocs-spring-boot-starter/src/main/java/com/firefly/flydocs/sdk/spring/FlydocsProperties.java b/sdks/java/flydocs-spring-boot-starter/src/main/java/com/firefly/flydocs/sdk/spring/FlydocsProperties.java index 9f00abf..c706794 100644 --- a/sdks/java/flydocs-spring-boot-starter/src/main/java/com/firefly/flydocs/sdk/spring/FlydocsProperties.java +++ b/sdks/java/flydocs-spring-boot-starter/src/main/java/com/firefly/flydocs/sdk/spring/FlydocsProperties.java @@ -36,7 +36,7 @@ public class FlydocsProperties { /** - * Base URL of the flydocs service, e.g. {@code http://localhost:8400}. + * Base URL of the flydocs service, e.g. {@code http://localhost:8080}. * Required. */ @Nullable diff --git a/sdks/java/flydocs-spring-boot-starter/src/test/java/com/firefly/flydocs/sdk/spring/FlydocsAutoConfigurationTest.java b/sdks/java/flydocs-spring-boot-starter/src/test/java/com/firefly/flydocs/sdk/spring/FlydocsAutoConfigurationTest.java index 0b988b7..54b789d 100644 --- a/sdks/java/flydocs-spring-boot-starter/src/test/java/com/firefly/flydocs/sdk/spring/FlydocsAutoConfigurationTest.java +++ b/sdks/java/flydocs-spring-boot-starter/src/test/java/com/firefly/flydocs/sdk/spring/FlydocsAutoConfigurationTest.java @@ -42,7 +42,7 @@ void doesNotRegisterClientsWithoutBaseUrl() { @Test void registersAsyncAndBlockingClientsWhenBaseUrlSet() { runner - .withPropertyValues("flydocs.base-url=http://localhost:8400") + .withPropertyValues("flydocs.base-url=http://localhost:8080") .run(ctx -> { assertThat(ctx).hasSingleBean(FlydocsClientAsync.class); assertThat(ctx).hasSingleBean(FlydocsClient.class); @@ -55,7 +55,7 @@ void registersAsyncAndBlockingClientsWhenBaseUrlSet() { void registersWebhookVerifierWhenSecretSet() { runner .withPropertyValues( - "flydocs.base-url=http://localhost:8400", + "flydocs.base-url=http://localhost:8080", "flydocs.webhook.secret=super-secret") .run(ctx -> { assertThat(ctx).hasSingleBean(WebhookVerifier.class); @@ -67,7 +67,7 @@ void registersWebhookVerifierWhenSecretSet() { void honoursTimeoutAndRetryProperties() { runner .withPropertyValues( - "flydocs.base-url=http://localhost:8400", + "flydocs.base-url=http://localhost:8080", "flydocs.api-key=my-key", "flydocs.timeout=30s", "flydocs.max-attempts=3", @@ -88,7 +88,7 @@ void honoursTimeoutAndRetryProperties() { @Test void userBeanOverridesAutoConfiguration() { runner - .withPropertyValues("flydocs.base-url=http://localhost:8400") + .withPropertyValues("flydocs.base-url=http://localhost:8080") .withUserConfiguration(CustomConfig.class) .run(ctx -> { assertThat(ctx).hasSingleBean(FlydocsClientAsync.class); diff --git a/sdks/python/QUICKSTART.md b/sdks/python/QUICKSTART.md index a370eb0..3cdfea3 100644 --- a/sdks/python/QUICKSTART.md +++ b/sdks/python/QUICKSTART.md @@ -17,7 +17,7 @@ The SDK depends only on `httpx` and `pydantic`. From the repo root: ```bash -task docker:up:test # serves http://localhost:8400 backed by a mock LLM +task docker:up:test # serves http://localhost:8080 backed by a mock LLM ``` If you already have a running flydocs deployment, point `base_url` at it and skip this step. @@ -63,7 +63,7 @@ async def main() -> None: # 3. Call the service. AsyncClient is the primary integration surface; # close it as a context manager. - async with AsyncClient("http://localhost:8400") as flydocs: + async with AsyncClient("http://localhost:8080") as flydocs: result = await flydocs.extract(request) # 4. Read the response. v1 nests model + latency under ``result.pipeline``; @@ -107,7 +107,7 @@ If you can't run an event loop: ```python from flydocs_sdk import Client -with Client("http://localhost:8400") as flydocs: +with Client("http://localhost:8080") as flydocs: result = flydocs.extract(request) ``` diff --git a/sdks/python/README.md b/sdks/python/README.md index 033f505..3c4bc52 100644 --- a/sdks/python/README.md +++ b/sdks/python/README.md @@ -45,7 +45,7 @@ invoice = DocumentTypeSpec( ], ) -with Client("http://localhost:8400") as flydocs: +with Client("http://localhost:8080") as flydocs: result = flydocs.extract( ExtractionRequest( files=[FileInput.from_path("invoice.pdf")], @@ -77,7 +77,7 @@ from flydocs_sdk import ( ) async def main() -> None: - async with AsyncClient("http://localhost:8400") as flydocs: + async with AsyncClient("http://localhost:8080") as flydocs: ext = await flydocs.extractions.create( SubmitExtractionRequest( files=[FileInput.from_path("invoice.pdf")], diff --git a/sdks/python/TUTORIAL.md b/sdks/python/TUTORIAL.md index 90b9e11..a57e22a 100644 --- a/sdks/python/TUTORIAL.md +++ b/sdks/python/TUTORIAL.md @@ -565,7 +565,7 @@ from flydocs_sdk import ( AsyncClient, ExtractionStatus, FileInput, SubmitExtractionRequest, ) -async with AsyncClient("http://localhost:8400") as flydocs: +async with AsyncClient("http://localhost:8080") as flydocs: ext = await flydocs.extractions.create( SubmitExtractionRequest( files=[FileInput.from_path("big-batch.pdf")], @@ -771,7 +771,12 @@ The error also carries the full RFC 7807 view via `exc.as_problem_details()` ret **Bring your own httpx client.** `AsyncClient(..., http_client=existing)` shares your app's connection pool. The SDK never closes transports it didn't create. -**Health checks.** `await flydocs.health("readiness")` returns the actuator JSON. +**Health checks.** The actuator lives on the management port (`9090`), separate from the business API (`8080`). Point the client at it via `management_url`, then call `.health()`: + +```python +flydocs = AsyncClient("http://localhost:8080", management_url="http://localhost:9090") +await flydocs.health("readiness") # GET http://localhost:9090/actuator/health/readiness +``` **Cost tracking.** When the service has cost tracking enabled, `result.pipeline.usage` carries per-agent and per-model token + USD breakdowns; webhook envelopes carry the same. @@ -887,7 +892,7 @@ rules = [ async def main(invoice_path: str) -> None: - async with AsyncClient("http://localhost:8400") as flydocs: + async with AsyncClient("http://localhost:8080") as flydocs: ext = await flydocs.extractions.create( SubmitExtractionRequest( files=[FileInput.from_path(invoice_path)], @@ -950,7 +955,7 @@ For scripts, batch tools, and callers that can't run an event loop, `Client` wra ```python from flydocs_sdk import Client -with Client("http://localhost:8400") as flydocs: +with Client("http://localhost:8080") as flydocs: result = flydocs.extract(req) ``` diff --git a/sdks/python/examples/01_first_extraction.py b/sdks/python/examples/01_first_extraction.py index 6ab2975..df56fc6 100644 --- a/sdks/python/examples/01_first_extraction.py +++ b/sdks/python/examples/01_first_extraction.py @@ -20,7 +20,7 @@ * Walk the new response shape (``documents[*].field_groups[*].fields``). Run from the repo root, with a flydocs service reachable at -``http://localhost:8400`` (e.g. via ``task docker:up:test``):: +``http://localhost:8080`` (e.g. via ``task docker:up:test``):: uv run python sdks/python/examples/01_first_extraction.py path/to/invoice.pdf """ @@ -56,7 +56,7 @@ async def main(path: Path) -> int: ], ) - async with AsyncClient("http://localhost:8400") as flydocs: + async with AsyncClient("http://localhost:8080") as flydocs: result = await flydocs.extract( ExtractionRequest( files=[FileInput.from_path(path)], diff --git a/sdks/python/examples/02_typed_schema_and_rules.py b/sdks/python/examples/02_typed_schema_and_rules.py index dbee84b..1ada44a 100644 --- a/sdks/python/examples/02_typed_schema_and_rules.py +++ b/sdks/python/examples/02_typed_schema_and_rules.py @@ -60,7 +60,7 @@ async def main(path: Path) -> int: ), ), ) - async with AsyncClient("http://localhost:8400") as flydocs: + async with AsyncClient("http://localhost:8080") as flydocs: report = await flydocs.validate(req) if not report.ok: print("semantic validation failed:") diff --git a/sdks/python/examples/03_async_extraction_with_wait.py b/sdks/python/examples/03_async_extraction_with_wait.py index 851cdf9..8a743e3 100644 --- a/sdks/python/examples/03_async_extraction_with_wait.py +++ b/sdks/python/examples/03_async_extraction_with_wait.py @@ -51,7 +51,7 @@ async def main(path: Path) -> int: - async with AsyncClient("http://localhost:8400", timeout=30.0) as flydocs: + async with AsyncClient("http://localhost:8080", timeout=30.0) as flydocs: ext = await flydocs.extractions.create( SubmitExtractionRequest( files=[FileInput.from_path(path)], diff --git a/sdks/python/examples/05_error_handling.py b/sdks/python/examples/05_error_handling.py index dfe775d..119a340 100644 --- a/sdks/python/examples/05_error_handling.py +++ b/sdks/python/examples/05_error_handling.py @@ -56,7 +56,7 @@ async def main(path: Path) -> int: files=[FileInput.from_path(path)], document_types=[INVOICE_DOCUMENT_TYPE], ) - async with AsyncClient("http://localhost:8400") as flydocs: + async with AsyncClient("http://localhost:8080") as flydocs: try: result = await flydocs.extract(req) print(f"extracted in sync: latency={result.pipeline.latency_ms}ms") diff --git a/sdks/python/examples/06_sync_facade.py b/sdks/python/examples/06_sync_facade.py index d677347..6b525e5 100644 --- a/sdks/python/examples/06_sync_facade.py +++ b/sdks/python/examples/06_sync_facade.py @@ -46,7 +46,7 @@ def main(path: Path) -> int: - with Client("http://localhost:8400") as flydocs: + with Client("http://localhost:8080") as flydocs: result = flydocs.extract( ExtractionRequest( files=[FileInput.from_path(path)], diff --git a/sdks/python/examples/README.md b/sdks/python/examples/README.md index 6c61c4d..8240c43 100644 --- a/sdks/python/examples/README.md +++ b/sdks/python/examples/README.md @@ -14,7 +14,7 @@ Runnable async-first scripts exercising every capability from the [TUTORIAL](../ ## Running ```bash -task docker:up:test # spin up flydocs + mock-llm at http://localhost:8400 +task docker:up:test # spin up flydocs + mock-llm at http://localhost:8080 # Then run any example. Examples 2/3/5/6 share fixtures via PYTHONPATH: uv run python sdks/python/examples/01_first_extraction.py path/to/invoice.pdf diff --git a/sdks/python/src/flydocs_sdk/__init__.py b/sdks/python/src/flydocs_sdk/__init__.py index 2363d66..f7f5797 100644 --- a/sdks/python/src/flydocs_sdk/__init__.py +++ b/sdks/python/src/flydocs_sdk/__init__.py @@ -50,7 +50,7 @@ ], ) - with Client("http://localhost:8400") as flydocs: + with Client("http://localhost:8080") as flydocs: result = flydocs.extract( ExtractionRequest( files=[FileInput.from_path("invoice.pdf")], diff --git a/sdks/python/src/flydocs_sdk/async_client.py b/sdks/python/src/flydocs_sdk/async_client.py index 118fd0f..5c6b705 100644 --- a/sdks/python/src/flydocs_sdk/async_client.py +++ b/sdks/python/src/flydocs_sdk/async_client.py @@ -80,7 +80,7 @@ class AsyncClient: share a connection pool with the rest of your app -- the SDK will not close transports it did not create. - async with AsyncClient("http://localhost:8400") as flydocs: + async with AsyncClient("http://localhost:8080") as flydocs: result = await flydocs.extract(ExtractionRequest(...)) """ @@ -89,12 +89,18 @@ def __init__( base_url: str, *, api_key: str | None = None, + management_url: str | None = None, timeout: float = DEFAULT_TIMEOUT_S, default_headers: dict[str, str] | None = None, transport: httpx.AsyncBaseTransport | None = None, http_client: httpx.AsyncClient | None = None, ) -> None: self._base_url = base_url.rstrip("/") + # The actuator (/actuator/*) and admin endpoints run on pyfly's separate + # management port (default 9090), not the business API port. Set + # ``management_url`` (e.g. "http://host:9090") so ``health()`` targets it; + # when unset it falls back to ``base_url`` for single-port deployments. + self._management_url = management_url.rstrip("/") if management_url else None self._api_key = api_key self._default_headers = dict(default_headers or {}) if http_client is not None: @@ -143,9 +149,13 @@ async def health(self, probe: str = "readiness") -> dict[str, Any]: ``probe`` is typically ``readiness`` or ``liveness``. Returns the raw actuator JSON since the shape is owned by pyfly, not - the flydocs DTOs. + the flydocs DTOs. When ``management_url`` was supplied to the client the + request targets the management port; otherwise it uses ``base_url``. """ - data = await self._request_json("GET", f"/actuator/health/{probe}") + path = f"/actuator/health/{probe}" + if self._management_url is not None: + path = f"{self._management_url}{path}" + data = await self._request_json("GET", path) if not isinstance(data, dict): raise FlydocsClientError(f"unexpected /actuator/health/{probe} response: {data!r}") return data @@ -228,7 +238,7 @@ async def wait_for_completion( :class:`TimeoutError` if the deadline elapses while the worker is still in flight. - async with AsyncClient("http://localhost:8400") as flydocs: + async with AsyncClient("http://localhost:8080") as flydocs: ext = await flydocs.extractions.create(req) final = await flydocs.wait_for_completion(ext.id) if final.status == ExtractionStatus.SUCCEEDED: diff --git a/sdks/python/src/flydocs_sdk/client.py b/sdks/python/src/flydocs_sdk/client.py index 376caf9..7934a27 100644 --- a/sdks/python/src/flydocs_sdk/client.py +++ b/sdks/python/src/flydocs_sdk/client.py @@ -70,7 +70,7 @@ class Client: """Synchronous client over the same endpoint set as :class:`AsyncClient`. - with Client("http://localhost:8400") as flydocs: + with Client("http://localhost:8080") as flydocs: result = flydocs.extract(request) Calling :meth:`close` (or using the context manager) shuts the @@ -83,6 +83,7 @@ def __init__( base_url: str, *, api_key: str | None = None, + management_url: str | None = None, timeout: float = DEFAULT_TIMEOUT_S, default_headers: dict[str, str] | None = None, transport: httpx.AsyncBaseTransport | None = None, @@ -92,6 +93,7 @@ def __init__( self._inner = AsyncClient( base_url, api_key=api_key, + management_url=management_url, timeout=timeout, default_headers=default_headers, transport=transport, diff --git a/src/flydocs/__init__.py b/src/flydocs/__init__.py index 3ceeffa..c4e06f6 100644 --- a/src/flydocs/__init__.py +++ b/src/flydocs/__init__.py @@ -24,4 +24,4 @@ `PromptRegistry`). """ -__version__ = "26.6.9" +__version__ = "26.6.10" diff --git a/src/flydocs/config.py b/src/flydocs/config.py index fbac7f4..b2ef094 100644 --- a/src/flydocs/config.py +++ b/src/flydocs/config.py @@ -41,13 +41,16 @@ class IDPSettings(BaseSettings): # -- Service -------------------------------------------------------- log_level: str = "INFO" - port: int = 8400 + # Business API port (pyfly serves the app here; pyfly.server.port == 8080). + port: int = 8080 # Port for the HTTP health server the worker CLI modes (``flydocs # worker`` / ``flydocs bbox-worker``) run next to their asyncio tasks - # so Kubernetes can probe ``/actuator/health/*`` over httpGet. Unset - # reuses ``port``; ``0`` disables the server (dev setups running - # ``serve`` and ``worker`` on the same host). - worker_health_port: int | None = Field(default=None, ge=0, le=65535) + # so Kubernetes can probe ``/actuator/health/*`` over httpGet. Defaults to + # the management port (9090) so it matches where the ``serve`` mode exposes + # the actuator (pyfly.management.server.port). ``0`` disables the server + # (dev setups running ``serve`` and ``worker`` on the same host, where the + # serve mode already owns 9090). + worker_health_port: int | None = Field(default=9090, ge=0, le=65535) # -- Persistence ---------------------------------------------------- database_url: str = "postgresql+asyncpg://idp:idp@localhost:5432/flydocs" diff --git a/src/flydocs/main.py b/src/flydocs/main.py index f50f709..be19fc7 100644 --- a/src/flydocs/main.py +++ b/src/flydocs/main.py @@ -72,8 +72,8 @@ async def _lifespan(app: Any): # escape hatch for legitimate dynamic attribute writes. setattr(_pyfly, "_route_metadata", getattr(app.state, "pyfly_route_metadata", [])) # noqa: B010 setattr(_pyfly, "_docs_enabled", getattr(app.state, "pyfly_docs_enabled", False)) # noqa: B010 - setattr(_pyfly, "_host", str(_pyfly.config.get("pyfly.web.host", "0.0.0.0"))) # noqa: B010 - setattr(_pyfly, "_port", int(_pyfly.config.get("pyfly.server.port", 8400))) # noqa: B010 + setattr(_pyfly, "_host", str(_pyfly.config.get("pyfly.server.host", "0.0.0.0"))) # noqa: B010 + setattr(_pyfly, "_port", int(_pyfly.config.get("pyfly.server.port", 8080))) # noqa: B010 await _pyfly.startup() # Re-scan HealthIndicator beans now that the container has built # every singleton (the eager scan inside ``create_app`` runs BEFORE diff --git a/tests/unit/test_worker_health.py b/tests/unit/test_worker_health.py index b1adf93..8c30fde 100644 --- a/tests/unit/test_worker_health.py +++ b/tests/unit/test_worker_health.py @@ -157,15 +157,15 @@ def test_resolve_health_port_defaults_to_service_port() -> None: # worker_health_port passed explicitly: IDPSettings is a BaseSettings, so # a bare constructor would absorb ambient FLYDOCS_WORKER_HEALTH_PORT from # the developer's shell or .env and flake this assertion. - assert resolve_health_port(IDPSettings(port=8400, worker_health_port=None)) == 8400 + assert resolve_health_port(IDPSettings(port=8080, worker_health_port=None)) == 8080 def test_resolve_health_port_override() -> None: - assert resolve_health_port(IDPSettings(port=8400, worker_health_port=9090)) == 9090 + assert resolve_health_port(IDPSettings(port=8080, worker_health_port=9090)) == 9090 def test_resolve_health_port_zero_disables() -> None: - assert resolve_health_port(IDPSettings(port=8400, worker_health_port=0)) == 0 + assert resolve_health_port(IDPSettings(port=8080, worker_health_port=0)) == 0 # --------------------------------------------------------------------------- @@ -174,7 +174,7 @@ def test_resolve_health_port_zero_disables() -> None: def test_make_health_server_config() -> None: - settings = IDPSettings(port=8400, worker_health_port=9090) + settings = IDPSettings(port=8080, worker_health_port=9090) server = make_health_server(build_health_app(), settings=settings) assert server.config.host == "0.0.0.0" assert server.config.port == 9090 @@ -183,17 +183,17 @@ def test_make_health_server_config() -> None: def test_make_health_server_rejects_disabled_port() -> None: - settings = IDPSettings(port=8400, worker_health_port=0) + settings = IDPSettings(port=8080, worker_health_port=0) with pytest.raises(ValueError, match="disabled"): make_health_server(build_health_app(), settings=settings) def test_build_worker_health_server_none_when_disabled() -> None: - assert build_worker_health_server(None, IDPSettings(port=8400, worker_health_port=0)) is None + assert build_worker_health_server(None, IDPSettings(port=8080, worker_health_port=0)) is None def test_build_worker_health_server_enabled() -> None: - server = build_worker_health_server(None, IDPSettings(port=8400, worker_health_port=9090)) + server = build_worker_health_server(None, IDPSettings(port=8080, worker_health_port=9090)) assert server is not None assert server.config.port == 9090 diff --git a/uv.lock b/uv.lock index 15f73a5..53a1388 100644 --- a/uv.lock +++ b/uv.lock @@ -1483,7 +1483,7 @@ security = [ [[package]] name = "flydocs" -version = "26.6.9" +version = "26.6.10" source = { editable = "." } dependencies = [ { name = "aiosqlite" }, @@ -1539,7 +1539,7 @@ requires-dist = [ { name = "pydantic", specifier = ">=2.10.0" }, { name = "pydantic-ai-slim", extras = ["anthropic", "openai", "bedrock"], specifier = ">=1.56.0" }, { name = "pydantic-settings", specifier = ">=2.7.0" }, - { name = "pyfly", extras = ["fastapi", "web", "observability", "security", "data-relational", "postgresql", "eda", "redis", "client", "scheduling", "cli"], editable = "../../fireflyframework/fireflyframework-pyfly" }, + { name = "pyfly", extras = ["fastapi", "web", "observability", "security", "data-relational", "postgresql", "eda", "redis", "client", "scheduling", "cli"], git = "https://github.com/fireflyframework/fireflyframework-pyfly.git?tag=v26.06.103" }, { name = "pymupdf", specifier = ">=1.24" }, { name = "pypdf", specifier = ">=4.3.0" }, { name = "pyright", marker = "extra == 'dev'", specifier = ">=1.1" }, @@ -3942,8 +3942,8 @@ wheels = [ [[package]] name = "pyfly" -version = "26.6.101" -source = { editable = "../../fireflyframework/fireflyframework-pyfly" } +version = "26.6.103" +source = { git = "https://github.com/fireflyframework/fireflyframework-pyfly.git?tag=v26.06.103#67244e4fc23ce8bee1f40b0f819c2c8858161804" } dependencies = [ { name = "pydantic" }, { name = "pyyaml" }, @@ -3999,79 +3999,6 @@ web = [ { name = "uvicorn", extra = ["standard"] }, ] -[package.metadata] -requires-dist = [ - { name = "aio-pika", marker = "extra == 'eda'", specifier = ">=9.6.2" }, - { name = "aio-pika", marker = "extra == 'rabbitmq'", specifier = ">=9.6.2" }, - { name = "aiokafka", marker = "extra == 'eda'", specifier = ">=0.14.0" }, - { name = "aiokafka", marker = "extra == 'kafka'", specifier = ">=0.14.0" }, - { name = "aiosqlite", marker = "extra == 'data-relational'", specifier = ">=0.22.1" }, - { name = "alembic", marker = "extra == 'data-relational'", specifier = ">=1.18.4" }, - { name = "asyncpg", marker = "extra == 'postgresql'", specifier = ">=0.31.0" }, - { name = "azure-storage-blob", marker = "extra == 'ecm-azure'", specifier = ">=12.19.0" }, - { name = "bcrypt", marker = "extra == 'security'", specifier = ">=5.0.0" }, - { name = "beanie", marker = "extra == 'data-document'", specifier = ">=2.1.0" }, - { name = "boto3", marker = "extra == 'ecm-aws'", specifier = ">=1.34.0" }, - { name = "boto3", marker = "extra == 'idp-cognito'", specifier = ">=1.34.0" }, - { name = "click", marker = "extra == 'cli'", specifier = ">=8.3.3" }, - { name = "click", marker = "extra == 'shell'", specifier = ">=8.3.3" }, - { name = "croniter", marker = "extra == 'scheduling'", specifier = ">=6.2.2" }, - { name = "cryptography", marker = "extra == 'security'", specifier = ">=48.0.0" }, - { name = "fastapi", marker = "extra == 'fastapi'", specifier = ">=0.136.1" }, - { name = "gitpython", marker = "extra == 'config-server-git'", specifier = ">=3.1" }, - { name = "granian", marker = "extra == 'granian'", specifier = ">=2.7.4" }, - { name = "grpcio", marker = "extra == 'grpc'", specifier = ">=1.60.0" }, - { name = "httpx", marker = "extra == 'client'", specifier = ">=0.28.1" }, - { name = "httpx", marker = "extra == 'idp-azure'", specifier = ">=0.28.1" }, - { name = "httpx", marker = "extra == 'idp-keycloak'", specifier = ">=0.28.1" }, - { name = "hypercorn", marker = "extra == 'hypercorn'", specifier = ">=0.18.0" }, - { name = "jinja2", marker = "extra == 'cli'", specifier = ">=3.1.6" }, - { name = "jinja2", marker = "extra == 'notifications'", specifier = ">=3.1.6" }, - { name = "jsonpath-ng", marker = "extra == 'testing'", specifier = ">=1.8.0" }, - { name = "opentelemetry-api", marker = "extra == 'observability'", specifier = ">=1.41.1" }, - { name = "opentelemetry-instrumentation-starlette", marker = "extra == 'observability'", specifier = ">=0.62b1" }, - { name = "opentelemetry-sdk", marker = "extra == 'observability'", specifier = ">=1.41.1" }, - { name = "pika", marker = "extra == 'testcontainers'", specifier = ">=1.3.0" }, - { name = "presidio-analyzer", marker = "extra == 'pii'", specifier = ">=2.2" }, - { name = "presidio-anonymizer", marker = "extra == 'pii'", specifier = ">=2.2" }, - { name = "prometheus-client", marker = "extra == 'observability'", specifier = ">=0.25.0" }, - { name = "pydantic", specifier = ">=2.13.3" }, - { name = "pyfly", extras = ["fastapi", "granian"], marker = "extra == 'web-fastapi'" }, - { name = "pyfly", extras = ["web", "data-relational", "data-document", "postgresql", "eda", "cache", "client", "grpc", "websocket", "ecm-aws", "ecm-azure", "observability", "security", "scheduling", "cli", "shell", "kafka", "rabbitmq", "redis", "granian", "fastapi", "hypercorn", "idp-azure", "idp-keycloak", "idp-cognito", "notifications", "config-server-git"], marker = "extra == 'full'" }, - { name = "pyfly", extras = ["web", "granian"], marker = "extra == 'web-fast'" }, - { name = "pyjwt", extras = ["crypto"], marker = "extra == 'security'", specifier = ">=2.12.1" }, - { name = "pyotp", marker = "extra == 'security'", specifier = ">=2.9.0" }, - { name = "python-multipart", marker = "extra == 'web'", specifier = ">=0.0.27" }, - { name = "pyyaml", specifier = ">=6.0.3" }, - { name = "questionary", marker = "extra == 'cli'", specifier = ">=2.1.1" }, - { name = "redis", extras = ["hiredis"], marker = "extra == 'cache'", specifier = ">=7.4.0" }, - { name = "redis", extras = ["hiredis"], marker = "extra == 'redis'", specifier = ">=7.4.0" }, - { name = "rich", marker = "extra == 'cli'", specifier = ">=15.0.0" }, - { name = "sqlalchemy", extras = ["asyncio"], marker = "extra == 'data-relational'", specifier = ">=2.0.49" }, - { name = "starlette", marker = "extra == 'web'", specifier = ">=1.0.0" }, - { name = "structlog", marker = "extra == 'observability'", specifier = ">=25.5.0" }, - { name = "testcontainers", marker = "extra == 'testcontainers'", specifier = ">=4.0.0" }, - { name = "uvicorn", extras = ["standard"], marker = "extra == 'web'", specifier = ">=0.46.0" }, - { name = "uvloop", marker = "sys_platform != 'win32' and extra == 'web-fast'", specifier = ">=0.22.1" }, - { name = "uvloop", marker = "sys_platform != 'win32' and extra == 'web-fastapi'", specifier = ">=0.22.1" }, - { name = "websockets", marker = "extra == 'websocket'", specifier = ">=12.0" }, -] -provides-extras = ["web", "data-relational", "testing", "testcontainers", "data-document", "postgresql", "eda", "fastapi", "granian", "hypercorn", "kafka", "rabbitmq", "redis", "cache", "client", "config-server-git", "grpc", "websocket", "idp-azure", "idp-keycloak", "idp-cognito", "ecm-aws", "ecm-azure", "observability", "scheduling", "pii", "security", "notifications", "cli", "shell", "web-fast", "web-fastapi", "full"] - -[package.metadata.requires-dev] -dev = [ - { name = "aiosmtpd", specifier = ">=1.4" }, - { name = "coverage", extras = ["toml"], specifier = ">=7.13.5" }, - { name = "jsonpath-ng", specifier = ">=1.8.0" }, - { name = "mongomock-motor", specifier = ">=0.0.36" }, - { name = "mypy", specifier = ">=1.20.2" }, - { name = "pytest", specifier = ">=9.0.3" }, - { name = "pytest-asyncio", specifier = ">=1.3.0" }, - { name = "pytest-cov", specifier = ">=7.1.0" }, - { name = "respx", specifier = ">=0.21.0" }, - { name = "ruff", specifier = ">=0.15.12" }, -] - [[package]] name = "pygments" version = "2.20.0"