From 60d7ec81c04459784a79e5c1f119da8f6a20f7a3 Mon Sep 17 00:00:00 2001 From: stroblme Date: Fri, 21 Aug 2026 21:48:05 +0200 Subject: [PATCH] Rename the import package app to fluksio A wheel whose top-level module is `app` collides with anything else in a user's venv, so the package that is about to be published takes the name it is published under. Only the Python package moves; the repo, the Docker WORKDIR and the compose project keep theirs. Co-Authored-By: Claude Opus 5 (1M context) --- .gitea/workflows/pre-commit.yml | 2 +- .pre-commit-config.yaml | 2 +- Makefile | 4 +- README.md | 2 +- ROADMAP.md | 4 +- backend/.dockerignore | 2 +- backend/.gitignore | 2 +- backend/Dockerfile | 8 +- backend/alembic.ini | 2 +- backend/app/flow/__init__.py | 26 --- backend/app/flow/nodes/__init__.py | 37 ----- backend/{app => fluksio}/__init__.py | 0 backend/{app => fluksio}/alembic/README | 0 backend/{app => fluksio}/alembic/env.py | 4 +- .../{app => fluksio}/alembic/script.py.mako | 0 .../{app => fluksio}/alembic/versions/.keep | 0 ...4_add_observability_rollups_events_and_.py | 0 ...608336_add_cascade_delete_relationships.py | 0 ...add_oauth_client_code_and_refresh_token.py | 0 ...4c78_add_max_length_for_string_varchar_.py | 0 .../versions/a3f1c07b52d9_add_run_tables.py | 0 .../versions/b7c41d2f8a30_drop_item_table.py | 0 .../c4e8b2170f93_add_portal_sub_to_user.py | 0 ...edit_replace_id_integers_in_all_models_.py | 0 .../e2412789c190_initialize_models.py | 0 ...a70289e_add_created_at_to_user_and_item.py | 0 backend/{app => fluksio}/api/__init__.py | 0 backend/{app => fluksio}/api/deps.py | 18 +-- backend/{app => fluksio}/api/main.py | 2 +- .../{app => fluksio}/api/routes/__init__.py | 0 backend/{app => fluksio}/api/routes/alerts.py | 8 +- .../{app => fluksio}/api/routes/artifacts.py | 8 +- backend/{app => fluksio}/api/routes/cloud.py | 12 +- .../{app => fluksio}/api/routes/dashboards.py | 10 +- backend/{app => fluksio}/api/routes/flows.py | 28 ++-- backend/{app => fluksio}/api/routes/login.py | 12 +- .../{app => fluksio}/api/routes/messages.py | 8 +- .../{app => fluksio}/api/routes/modules.py | 8 +- backend/{app => fluksio}/api/routes/oauth.py | 8 +- .../api/routes/observability.py | 8 +- backend/{app => fluksio}/api/routes/panels.py | 16 +- .../{app => fluksio}/api/routes/private.py | 8 +- backend/{app => fluksio}/api/routes/runs.py | 8 +- .../{app => fluksio}/api/routes/secrets.py | 6 +- backend/{app => fluksio}/api/routes/users.py | 14 +- backend/{app => fluksio}/api/routes/utils.py | 8 +- .../{app => fluksio}/api/routes/workers.py | 8 +- backend/{app => fluksio}/backend_pre_start.py | 2 +- backend/{app => fluksio}/cloud/__init__.py | 0 backend/{app => fluksio}/cloud/config.py | 2 +- backend/{app => fluksio}/cloud/connector.py | 18 +-- backend/{app => fluksio}/core/__init__.py | 0 backend/{app => fluksio}/core/config.py | 0 backend/{app => fluksio}/core/db.py | 6 +- backend/{app => fluksio}/core/security.py | 4 +- backend/{app => fluksio}/crud.py | 4 +- .../email-templates/build/new_account.html | 0 .../email-templates/build/reset_password.html | 0 .../email-templates/build/test_email.html | 0 .../email-templates/src/new_account.mjml | 0 .../email-templates/src/reset_password.mjml | 0 .../email-templates/src/test_email.mjml | 0 backend/fluksio/flow/__init__.py | 26 +++ backend/{app => fluksio}/flow/alerts.py | 8 +- backend/{app => fluksio}/flow/artifacts.py | 0 backend/{app => fluksio}/flow/connector.py | 2 +- backend/{app => fluksio}/flow/controller.py | 32 ++-- backend/{app => fluksio}/flow/dashboards.py | 4 +- backend/{app => fluksio}/flow/events.py | 0 backend/{app => fluksio}/flow/executor.py | 6 +- backend/{app => fluksio}/flow/logs.py | 0 backend/{app => fluksio}/flow/messages.py | 0 backend/{app => fluksio}/flow/metrics.py | 8 +- backend/{app => fluksio}/flow/modules.py | 6 +- backend/fluksio/flow/nodes/__init__.py | 37 +++++ backend/{app => fluksio}/flow/nodes/base.py | 10 +- backend/{app => fluksio}/flow/nodes/delay.py | 2 +- backend/{app => fluksio}/flow/nodes/exec.py | 4 +- backend/{app => fluksio}/flow/nodes/file.py | 6 +- backend/{app => fluksio}/flow/nodes/http.py | 4 +- backend/{app => fluksio}/flow/nodes/influx.py | 4 +- backend/{app => fluksio}/flow/nodes/inject.py | 4 +- backend/{app => fluksio}/flow/nodes/logic.py | 4 +- backend/{app => fluksio}/flow/nodes/mlp.py | 4 +- backend/{app => fluksio}/flow/nodes/mqtt.py | 4 +- backend/{app => fluksio}/flow/nodes/ntfy.py | 6 +- .../{app => fluksio}/flow/nodes/trigger.py | 4 +- backend/{app => fluksio}/flow/panels.py | 6 +- backend/{app => fluksio}/flow/pipeline.py | 18 +-- backend/{app => fluksio}/flow/plugins.py | 4 +- backend/{app => fluksio}/flow/queue.py | 0 backend/{app => fluksio}/flow/remote.py | 2 +- backend/{app => fluksio}/flow/runs.py | 16 +- backend/{app => fluksio}/flow/schemas.py | 2 +- backend/{app => fluksio}/flow/secrets.py | 0 backend/{app => fluksio}/flow/state.py | 0 backend/{app => fluksio}/flow/store.py | 2 +- backend/{app => fluksio}/flow/supervision.py | 2 +- backend/{app => fluksio}/flow/watchdog.py | 2 +- backend/{app => fluksio}/flow/worker_main.py | 2 +- backend/{app => fluksio}/flow/workers.py | 2 +- backend/{app => fluksio}/initial_data.py | 2 +- backend/{app => fluksio}/main.py | 58 +++---- backend/{app => fluksio}/mcp/__init__.py | 0 backend/{app => fluksio}/mcp/http.py | 6 +- backend/{app => fluksio}/mcp/server.py | 0 backend/{app => fluksio}/models.py | 0 backend/{app => fluksio}/tests_pre_start.py | 2 +- backend/{app => fluksio}/utils.py | 4 +- backend/{app => fluksio}/worker/__init__.py | 0 .../{app => fluksio}/worker/fluksio_worker.py | 0 backend/pyproject.toml | 17 +- backend/scripts/bench_startup.py | 2 +- backend/scripts/format.sh | 4 +- backend/scripts/lint.sh | 6 +- backend/scripts/prestart.sh | 4 +- backend/scripts/rotate_secret_key.py | 4 +- backend/scripts/soak.py | 4 +- backend/scripts/tests-start.sh | 2 +- backend/tests/api/routes/test_alerts.py | 4 +- backend/tests/api/routes/test_flows.py | 4 +- backend/tests/api/routes/test_login.py | 14 +- backend/tests/api/routes/test_oauth.py | 2 +- .../tests/api/routes/test_observability.py | 6 +- backend/tests/api/routes/test_panels.py | 8 +- backend/tests/api/routes/test_private.py | 4 +- backend/tests/api/routes/test_session.py | 4 +- backend/tests/api/routes/test_users.py | 14 +- backend/tests/conftest.py | 10 +- backend/tests/crud/test_user.py | 6 +- backend/tests/flow/test_alerts.py | 4 +- backend/tests/flow/test_brain_graph.py | 12 +- backend/tests/flow/test_connector.py | 18 +-- backend/tests/flow/test_dashboards.py | 12 +- backend/tests/flow/test_discretization.py | 10 +- backend/tests/flow/test_drafts.py | 6 +- backend/tests/flow/test_emit_counts.py | 14 +- backend/tests/flow/test_history.py | 8 +- backend/tests/flow/test_http_hook_secret.py | 14 +- backend/tests/flow/test_library.py | 6 +- backend/tests/flow/test_logs.py | 10 +- backend/tests/flow/test_messages.py | 2 +- backend/tests/flow/test_modules.py | 4 +- backend/tests/flow/test_node_settings.py | 12 +- backend/tests/flow/test_node_types.py | 10 +- backend/tests/flow/test_pipeline.py | 8 +- backend/tests/flow/test_provenance.py | 14 +- backend/tests/flow/test_queue.py | 12 +- backend/tests/flow/test_remote.py | 4 +- backend/tests/flow/test_runs.py | 12 +- backend/tests/flow/test_runtime_control.py | 14 +- backend/tests/flow/test_senders.py | 6 +- backend/tests/flow/test_state_edges.py | 8 +- backend/tests/flow/test_store.py | 6 +- backend/tests/flow/test_supervision.py | 6 +- backend/tests/flow/test_synchronous_nodes.py | 8 +- backend/tests/flow/test_vocabulary.py | 17 +- backend/tests/flow/test_watchdog.py | 6 +- backend/tests/flow/test_workers.py | 8 +- backend/tests/mcp/test_mcp_http.py | 10 +- .../tests/scripts/test_backend_pre_start.py | 6 +- backend/tests/scripts/test_test_pre_start.py | 6 +- backend/tests/test_cloud.py | 20 +-- backend/tests/test_metrics.py | 6 +- backend/tests/utils/user.py | 6 +- backend/tests/utils/utils.py | 2 +- development.md | 2 +- docker/compose.dev.yml | 4 +- scripts/generate-client.sh | 2 +- uv.lock | 150 +++++++++--------- 170 files changed, 629 insertions(+), 619 deletions(-) delete mode 100644 backend/app/flow/__init__.py delete mode 100644 backend/app/flow/nodes/__init__.py rename backend/{app => fluksio}/__init__.py (100%) rename backend/{app => fluksio}/alembic/README (100%) rename backend/{app => fluksio}/alembic/env.py (95%) rename backend/{app => fluksio}/alembic/script.py.mako (100%) rename backend/{app => fluksio}/alembic/versions/.keep (100%) rename backend/{app => fluksio}/alembic/versions/087c44e16304_add_observability_rollups_events_and_.py (100%) rename backend/{app => fluksio}/alembic/versions/1a31ce608336_add_cascade_delete_relationships.py (100%) rename backend/{app => fluksio}/alembic/versions/59e2606ce144_add_oauth_client_code_and_refresh_token.py (100%) rename backend/{app => fluksio}/alembic/versions/9c0a54914c78_add_max_length_for_string_varchar_.py (100%) rename backend/{app => fluksio}/alembic/versions/a3f1c07b52d9_add_run_tables.py (100%) rename backend/{app => fluksio}/alembic/versions/b7c41d2f8a30_drop_item_table.py (100%) rename backend/{app => fluksio}/alembic/versions/c4e8b2170f93_add_portal_sub_to_user.py (100%) rename backend/{app => fluksio}/alembic/versions/d98dd8ec85a3_edit_replace_id_integers_in_all_models_.py (100%) rename backend/{app => fluksio}/alembic/versions/e2412789c190_initialize_models.py (100%) rename backend/{app => fluksio}/alembic/versions/fe56fa70289e_add_created_at_to_user_and_item.py (100%) rename backend/{app => fluksio}/api/__init__.py (100%) rename backend/{app => fluksio}/api/deps.py (96%) rename backend/{app => fluksio}/api/main.py (97%) rename backend/{app => fluksio}/api/routes/__init__.py (100%) rename backend/{app => fluksio}/api/routes/alerts.py (93%) rename backend/{app => fluksio}/api/routes/artifacts.py (94%) rename backend/{app => fluksio}/api/routes/cloud.py (97%) rename backend/{app => fluksio}/api/routes/dashboards.py (96%) rename backend/{app => fluksio}/api/routes/flows.py (98%) rename backend/{app => fluksio}/api/routes/login.py (92%) rename backend/{app => fluksio}/api/routes/messages.py (94%) rename backend/{app => fluksio}/api/routes/modules.py (92%) rename backend/{app => fluksio}/api/routes/oauth.py (99%) rename backend/{app => fluksio}/api/routes/observability.py (98%) rename backend/{app => fluksio}/api/routes/panels.py (96%) rename backend/{app => fluksio}/api/routes/private.py (84%) rename backend/{app => fluksio}/api/routes/runs.py (97%) rename backend/{app => fluksio}/api/routes/secrets.py (90%) rename backend/{app => fluksio}/api/routes/users.py (95%) rename backend/{app => fluksio}/api/routes/utils.py (94%) rename backend/{app => fluksio}/api/routes/workers.py (95%) rename backend/{app => fluksio}/backend_pre_start.py (96%) rename backend/{app => fluksio}/cloud/__init__.py (100%) rename backend/{app => fluksio}/cloud/config.py (99%) rename backend/{app => fluksio}/cloud/connector.py (97%) rename backend/{app => fluksio}/core/__init__.py (100%) rename backend/{app => fluksio}/core/config.py (100%) rename backend/{app => fluksio}/core/db.py (91%) rename backend/{app => fluksio}/core/security.py (98%) rename backend/{app => fluksio}/crud.py (94%) rename backend/{app => fluksio}/email-templates/build/new_account.html (100%) rename backend/{app => fluksio}/email-templates/build/reset_password.html (100%) rename backend/{app => fluksio}/email-templates/build/test_email.html (100%) rename backend/{app => fluksio}/email-templates/src/new_account.mjml (100%) rename backend/{app => fluksio}/email-templates/src/reset_password.mjml (100%) rename backend/{app => fluksio}/email-templates/src/test_email.mjml (100%) create mode 100644 backend/fluksio/flow/__init__.py rename backend/{app => fluksio}/flow/alerts.py (98%) rename backend/{app => fluksio}/flow/artifacts.py (100%) rename backend/{app => fluksio}/flow/connector.py (99%) rename backend/{app => fluksio}/flow/controller.py (98%) rename backend/{app => fluksio}/flow/dashboards.py (99%) rename backend/{app => fluksio}/flow/events.py (100%) rename backend/{app => fluksio}/flow/executor.py (98%) rename backend/{app => fluksio}/flow/logs.py (100%) rename backend/{app => fluksio}/flow/messages.py (100%) rename backend/{app => fluksio}/flow/metrics.py (98%) rename backend/{app => fluksio}/flow/modules.py (97%) create mode 100644 backend/fluksio/flow/nodes/__init__.py rename backend/{app => fluksio}/flow/nodes/base.py (98%) rename backend/{app => fluksio}/flow/nodes/delay.py (99%) rename backend/{app => fluksio}/flow/nodes/exec.py (97%) rename backend/{app => fluksio}/flow/nodes/file.py (95%) rename backend/{app => fluksio}/flow/nodes/http.py (99%) rename backend/{app => fluksio}/flow/nodes/influx.py (99%) rename backend/{app => fluksio}/flow/nodes/inject.py (98%) rename backend/{app => fluksio}/flow/nodes/logic.py (99%) rename backend/{app => fluksio}/flow/nodes/mlp.py (97%) rename backend/{app => fluksio}/flow/nodes/mqtt.py (99%) rename backend/{app => fluksio}/flow/nodes/ntfy.py (95%) rename backend/{app => fluksio}/flow/nodes/trigger.py (97%) rename backend/{app => fluksio}/flow/panels.py (95%) rename backend/{app => fluksio}/flow/pipeline.py (99%) rename backend/{app => fluksio}/flow/plugins.py (95%) rename backend/{app => fluksio}/flow/queue.py (100%) rename backend/{app => fluksio}/flow/remote.py (99%) rename backend/{app => fluksio}/flow/runs.py (98%) rename backend/{app => fluksio}/flow/schemas.py (99%) rename backend/{app => fluksio}/flow/secrets.py (100%) rename backend/{app => fluksio}/flow/state.py (100%) rename backend/{app => fluksio}/flow/store.py (99%) rename backend/{app => fluksio}/flow/supervision.py (99%) rename backend/{app => fluksio}/flow/watchdog.py (98%) rename backend/{app => fluksio}/flow/worker_main.py (99%) rename backend/{app => fluksio}/flow/workers.py (99%) rename backend/{app => fluksio}/initial_data.py (89%) rename backend/{app => fluksio}/main.py (86%) rename backend/{app => fluksio}/mcp/__init__.py (100%) rename backend/{app => fluksio}/mcp/http.py (97%) rename backend/{app => fluksio}/mcp/server.py (100%) rename backend/{app => fluksio}/models.py (100%) rename backend/{app => fluksio}/tests_pre_start.py (96%) rename backend/{app => fluksio}/utils.py (98%) rename backend/{app => fluksio}/worker/__init__.py (100%) rename backend/{app => fluksio}/worker/fluksio_worker.py (100%) diff --git a/.gitea/workflows/pre-commit.yml b/.gitea/workflows/pre-commit.yml index b1b9a14..9545b10 100644 --- a/.gitea/workflows/pre-commit.yml +++ b/.gitea/workflows/pre-commit.yml @@ -49,5 +49,5 @@ jobs: # The hooks cover ruff and biome but not mypy, which `make lint-backend` # runs and which is strict for everything outside the flow prototypes. - name: Typecheck backend - run: uv run mypy app + run: uv run mypy fluksio working-directory: backend diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c60fc43..de742ba 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: exclude: | (?x)^( frontend/src/client/.*| - backend/app/email-templates/build/.* + backend/fluksio/email-templates/build/.* )$ - id: trailing-whitespace exclude: ^frontend/src/client/.* diff --git a/Makefile b/Makefile index b9a38b1..1e8ecab 100644 --- a/Makefile +++ b/Makefile @@ -83,7 +83,7 @@ install: ## Install all dependencies (backend + frontend) cd frontend && bun install dev-backend: ## Start the FastAPI backend with hot-reload (local) - cd backend && uv run fastapi dev app/main.py + cd backend && uv run fastapi dev fluksio/main.py dev-frontend: ## Start the Vite dev server (local) cd frontend && bun dev @@ -169,7 +169,7 @@ lint: lint-backend lint-frontend ## Run all linters lint-backend: ## Lint backend with ruff + mypy cd backend && uv run ruff check . cd backend && uv run ruff format --check . - cd backend && uv run mypy app + cd backend && uv run mypy fluksio lint-frontend: ## Lint frontend with biome cd frontend && bun run lint diff --git a/README.md b/README.md index 8b2aec8..ce4d6ea 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ repo holds the FastAPI backend, the flow engine, and the dashboard SPA. It is se ```text backend/ FastAPI + SQLModel + Alembic + Postgres - app/flow/ the flow engine (nodes, pipeline, state backends, controller) + fluksio/flow/ the flow engine (nodes, pipeline, state backends, controller) frontend/ React 19 + TanStack Router + Tailwind 4 + shadcn/ui docker/ compose.yml → compose.dev.yml → compose.local.yml (+ compose.traefik.yml) scripts/ generate-client.sh, test.sh diff --git a/ROADMAP.md b/ROADMAP.md index 5edbab1..1bcdd6b 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -44,14 +44,14 @@ Python, optimised for development speed. Owns the graph structure, persistence a external interfaces. See `docs/architecture/structure.canvas` → *Backend – Management*. - [x] FastAPI + SQLModel + Alembic + Postgres base with JWT auth and user management -- [x] Flow engine in `backend/app/flow/`: `Node` / `Pipeline` / `StateBackend` +- [x] Flow engine in `backend/fluksio/flow/`: `Node` / `Pipeline` / `StateBackend` (memory + Redis) / `FlowController` - [x] Node types: HTTP, MQTT, InfluxDB, Delay, MLP - [x] Flow-logic vocabulary as node types rather than repeated code: inject (manual, interval, cron or at startup), switch, change, filter-unchanged, join, trigger, command, file and ntfy. Each is configured by filling in a form the editor generates from its parameter schema -- [x] `app/flow` is an importable package with absolute `app.flow.*` imports +- [x] `fluksio/flow` is an importable package with absolute `fluksio.flow.*` imports - [x] Typed, serializable node I/O: every port declares a `DType`, messages are JSON on the wire and in Redis, no pickle anywhere. Binary codecs are still open — `DType.JSON` carries everything non-scalar for now diff --git a/backend/.dockerignore b/backend/.dockerignore index c0de4ab..df6b845 100644 --- a/backend/.dockerignore +++ b/backend/.dockerignore @@ -1,6 +1,6 @@ # Python __pycache__ -app.egg-info +fluksio.egg-info *.pyc .mypy_cache .coverage diff --git a/backend/.gitignore b/backend/.gitignore index adc0f1a..46f9e22 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -1,6 +1,6 @@ *.png __pycache__ -app.egg-info +fluksio.egg-info *.pyc .mypy_cache .coverage diff --git a/backend/Dockerfile b/backend/Dockerfile index 661ff24..ad59a54 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -25,20 +25,20 @@ ENV PATH="/app/.venv/bin:$PATH" RUN --mount=type=cache,target=/root/.cache/uv \ --mount=type=bind,source=uv.lock,target=uv.lock \ --mount=type=bind,source=pyproject.toml,target=pyproject.toml \ - uv sync --frozen --no-install-workspace --package app + uv sync --frozen --no-install-workspace --package fluksio COPY ./backend/scripts /app/backend/scripts COPY ./backend/pyproject.toml ./backend/alembic.ini /app/backend/ -COPY ./backend/app /app/backend/app +COPY ./backend/fluksio /app/backend/fluksio # Sync the project # Ref: https://docs.astral.sh/uv/guides/integration/docker/#intermediate-layers RUN --mount=type=cache,target=/root/.cache/uv \ --mount=type=bind,source=uv.lock,target=uv.lock \ --mount=type=bind,source=pyproject.toml,target=pyproject.toml \ - uv sync --frozen --package app + uv sync --frozen --package fluksio # Connectors are ordinary installed packages found through the # `fluksio.node_types` entry point. `make connectors` builds them into here; @@ -54,4 +54,4 @@ WORKDIR /app/backend/ # Single worker on purpose: the process hosts the flow engine, and a second # worker would be a second engine — duplicated subscriptions, cron ticks and # webhooks. Scaling out is the M5 worker split, not more uvicorn processes. -CMD ["fastapi", "run", "app/main.py"] +CMD ["fastapi", "run", "fluksio/main.py"] diff --git a/backend/alembic.ini b/backend/alembic.ini index 24841c2..2088a5f 100755 --- a/backend/alembic.ini +++ b/backend/alembic.ini @@ -2,7 +2,7 @@ [alembic] # path to migration scripts -script_location = app/alembic +script_location = fluksio/alembic # template used to generate migration files # file_template = %%(rev)s_%%(slug)s diff --git a/backend/app/flow/__init__.py b/backend/app/flow/__init__.py deleted file mode 100644 index 66dd1d9..0000000 --- a/backend/app/flow/__init__.py +++ /dev/null @@ -1,26 +0,0 @@ -"""The flow engine: nodes, the pipeline that runs them, and their storage.""" - -from app.flow.controller import FlowController, NodeStatus -from app.flow.events import EventBus, event_bus -from app.flow.messages import DType, MessageSpec, qualify -from app.flow.nodes import Node -from app.flow.pipeline import Pipeline, ValidationIssue -from app.flow.state import MemoryState, RedisState, StateBackend -from app.flow.store import FlowStore - -__all__ = [ - "DType", - "EventBus", - "FlowController", - "FlowStore", - "MemoryState", - "MessageSpec", - "Node", - "NodeStatus", - "Pipeline", - "RedisState", - "StateBackend", - "ValidationIssue", - "event_bus", - "qualify", -] diff --git a/backend/app/flow/nodes/__init__.py b/backend/app/flow/nodes/__init__.py deleted file mode 100644 index 8aa5a52..0000000 --- a/backend/app/flow/nodes/__init__.py +++ /dev/null @@ -1,37 +0,0 @@ -"""Built-in node types. - -Split by the outside world each one talks to. Importing from -``app.flow.nodes`` keeps working, which is what every caller does. -""" - -from app.flow.nodes.base import RESERVED_SETTINGS, Node -from app.flow.nodes.delay import DelayNode -from app.flow.nodes.exec import ExecNode -from app.flow.nodes.file import FileNode -from app.flow.nodes.http import HttpNode -from app.flow.nodes.influx import InfluxDbNode -from app.flow.nodes.inject import InjectNode -from app.flow.nodes.logic import ChangeNode, JoinNode, RbeNode, SwitchNode -from app.flow.nodes.mlp import MLPNode -from app.flow.nodes.mqtt import MqttNode -from app.flow.nodes.ntfy import NtfyNode -from app.flow.nodes.trigger import TriggerNode - -__all__ = [ - "ChangeNode", - "DelayNode", - "ExecNode", - "FileNode", - "HttpNode", - "InfluxDbNode", - "InjectNode", - "JoinNode", - "MLPNode", - "MqttNode", - "Node", - "NtfyNode", - "RESERVED_SETTINGS", - "RbeNode", - "SwitchNode", - "TriggerNode", -] diff --git a/backend/app/__init__.py b/backend/fluksio/__init__.py similarity index 100% rename from backend/app/__init__.py rename to backend/fluksio/__init__.py diff --git a/backend/app/alembic/README b/backend/fluksio/alembic/README similarity index 100% rename from backend/app/alembic/README rename to backend/fluksio/alembic/README diff --git a/backend/app/alembic/env.py b/backend/fluksio/alembic/env.py similarity index 95% rename from backend/app/alembic/env.py rename to backend/fluksio/alembic/env.py index 7f29c04..46e7e5a 100755 --- a/backend/app/alembic/env.py +++ b/backend/fluksio/alembic/env.py @@ -18,8 +18,8 @@ fileConfig(config.config_file_name) # target_metadata = mymodel.Base.metadata # target_metadata = None -from app.models import SQLModel # noqa -from app.core.config import settings # noqa +from fluksio.models import SQLModel # noqa +from fluksio.core.config import settings # noqa target_metadata = SQLModel.metadata diff --git a/backend/app/alembic/script.py.mako b/backend/fluksio/alembic/script.py.mako similarity index 100% rename from backend/app/alembic/script.py.mako rename to backend/fluksio/alembic/script.py.mako diff --git a/backend/app/alembic/versions/.keep b/backend/fluksio/alembic/versions/.keep similarity index 100% rename from backend/app/alembic/versions/.keep rename to backend/fluksio/alembic/versions/.keep diff --git a/backend/app/alembic/versions/087c44e16304_add_observability_rollups_events_and_.py b/backend/fluksio/alembic/versions/087c44e16304_add_observability_rollups_events_and_.py similarity index 100% rename from backend/app/alembic/versions/087c44e16304_add_observability_rollups_events_and_.py rename to backend/fluksio/alembic/versions/087c44e16304_add_observability_rollups_events_and_.py diff --git a/backend/app/alembic/versions/1a31ce608336_add_cascade_delete_relationships.py b/backend/fluksio/alembic/versions/1a31ce608336_add_cascade_delete_relationships.py similarity index 100% rename from backend/app/alembic/versions/1a31ce608336_add_cascade_delete_relationships.py rename to backend/fluksio/alembic/versions/1a31ce608336_add_cascade_delete_relationships.py diff --git a/backend/app/alembic/versions/59e2606ce144_add_oauth_client_code_and_refresh_token.py b/backend/fluksio/alembic/versions/59e2606ce144_add_oauth_client_code_and_refresh_token.py similarity index 100% rename from backend/app/alembic/versions/59e2606ce144_add_oauth_client_code_and_refresh_token.py rename to backend/fluksio/alembic/versions/59e2606ce144_add_oauth_client_code_and_refresh_token.py diff --git a/backend/app/alembic/versions/9c0a54914c78_add_max_length_for_string_varchar_.py b/backend/fluksio/alembic/versions/9c0a54914c78_add_max_length_for_string_varchar_.py similarity index 100% rename from backend/app/alembic/versions/9c0a54914c78_add_max_length_for_string_varchar_.py rename to backend/fluksio/alembic/versions/9c0a54914c78_add_max_length_for_string_varchar_.py diff --git a/backend/app/alembic/versions/a3f1c07b52d9_add_run_tables.py b/backend/fluksio/alembic/versions/a3f1c07b52d9_add_run_tables.py similarity index 100% rename from backend/app/alembic/versions/a3f1c07b52d9_add_run_tables.py rename to backend/fluksio/alembic/versions/a3f1c07b52d9_add_run_tables.py diff --git a/backend/app/alembic/versions/b7c41d2f8a30_drop_item_table.py b/backend/fluksio/alembic/versions/b7c41d2f8a30_drop_item_table.py similarity index 100% rename from backend/app/alembic/versions/b7c41d2f8a30_drop_item_table.py rename to backend/fluksio/alembic/versions/b7c41d2f8a30_drop_item_table.py diff --git a/backend/app/alembic/versions/c4e8b2170f93_add_portal_sub_to_user.py b/backend/fluksio/alembic/versions/c4e8b2170f93_add_portal_sub_to_user.py similarity index 100% rename from backend/app/alembic/versions/c4e8b2170f93_add_portal_sub_to_user.py rename to backend/fluksio/alembic/versions/c4e8b2170f93_add_portal_sub_to_user.py diff --git a/backend/app/alembic/versions/d98dd8ec85a3_edit_replace_id_integers_in_all_models_.py b/backend/fluksio/alembic/versions/d98dd8ec85a3_edit_replace_id_integers_in_all_models_.py similarity index 100% rename from backend/app/alembic/versions/d98dd8ec85a3_edit_replace_id_integers_in_all_models_.py rename to backend/fluksio/alembic/versions/d98dd8ec85a3_edit_replace_id_integers_in_all_models_.py diff --git a/backend/app/alembic/versions/e2412789c190_initialize_models.py b/backend/fluksio/alembic/versions/e2412789c190_initialize_models.py similarity index 100% rename from backend/app/alembic/versions/e2412789c190_initialize_models.py rename to backend/fluksio/alembic/versions/e2412789c190_initialize_models.py diff --git a/backend/app/alembic/versions/fe56fa70289e_add_created_at_to_user_and_item.py b/backend/fluksio/alembic/versions/fe56fa70289e_add_created_at_to_user_and_item.py similarity index 100% rename from backend/app/alembic/versions/fe56fa70289e_add_created_at_to_user_and_item.py rename to backend/fluksio/alembic/versions/fe56fa70289e_add_created_at_to_user_and_item.py diff --git a/backend/app/api/__init__.py b/backend/fluksio/api/__init__.py similarity index 100% rename from backend/app/api/__init__.py rename to backend/fluksio/api/__init__.py diff --git a/backend/app/api/deps.py b/backend/fluksio/api/deps.py similarity index 96% rename from backend/app/api/deps.py rename to backend/fluksio/api/deps.py index a8cc262..96af00e 100644 --- a/backend/app/api/deps.py +++ b/backend/fluksio/api/deps.py @@ -8,15 +8,15 @@ from jwt.exceptions import InvalidTokenError from pydantic import ValidationError from sqlmodel import Session, select -from app.cloud import config as cloud_config -from app.core import security -from app.core.config import settings -from app.core.db import engine -from app.flow import panels -from app.flow.controller import FlowController -from app.flow.dashboards import DashboardStore -from app.flow.workers import PythonWorkerPool -from app.models import TokenPayload, User +from fluksio.cloud import config as cloud_config +from fluksio.core import security +from fluksio.core.config import settings +from fluksio.core.db import engine +from fluksio.flow import panels +from fluksio.flow.controller import FlowController +from fluksio.flow.dashboards import DashboardStore +from fluksio.flow.workers import PythonWorkerPool +from fluksio.models import TokenPayload, User reusable_oauth2 = OAuth2PasswordBearer( tokenUrl=f"{settings.API_V1_STR}/login/access-token" diff --git a/backend/app/api/main.py b/backend/fluksio/api/main.py similarity index 97% rename from backend/app/api/main.py rename to backend/fluksio/api/main.py index c8ba553..0efef1d 100644 --- a/backend/app/api/main.py +++ b/backend/fluksio/api/main.py @@ -1,6 +1,6 @@ from fastapi import APIRouter -from app.api.routes import ( +from fluksio.api.routes import ( alerts, artifacts, cloud, diff --git a/backend/app/api/routes/__init__.py b/backend/fluksio/api/routes/__init__.py similarity index 100% rename from backend/app/api/routes/__init__.py rename to backend/fluksio/api/routes/__init__.py diff --git a/backend/app/api/routes/alerts.py b/backend/fluksio/api/routes/alerts.py similarity index 93% rename from backend/app/api/routes/alerts.py rename to backend/fluksio/api/routes/alerts.py index 0293fc3..97410fd 100644 --- a/backend/app/api/routes/alerts.py +++ b/backend/fluksio/api/routes/alerts.py @@ -6,10 +6,10 @@ from typing import Any from fastapi import APIRouter, Depends, HTTPException from fastapi.concurrency import run_in_threadpool -from app.api.deps import FlowControllerDep, get_current_user -from app.core.config import settings -from app.flow.alerts import Alert, AlertsConfig -from app.models import Message +from fluksio.api.deps import FlowControllerDep, get_current_user +from fluksio.core.config import settings +from fluksio.flow.alerts import Alert, AlertsConfig +from fluksio.models import Message router = APIRouter( prefix="/alerts", tags=["alerts"], dependencies=[Depends(get_current_user)] diff --git a/backend/app/api/routes/artifacts.py b/backend/fluksio/api/routes/artifacts.py similarity index 94% rename from backend/app/api/routes/artifacts.py rename to backend/fluksio/api/routes/artifacts.py index 874318a..7cb98e9 100644 --- a/backend/app/api/routes/artifacts.py +++ b/backend/fluksio/api/routes/artifacts.py @@ -13,10 +13,10 @@ from jwt.exceptions import InvalidTokenError from pydantic import BaseModel from sqlmodel import Session -from app.api.deps import user_from_token -from app.core import security -from app.core.db import engine -from app.flow.artifacts import ArtifactStore +from fluksio.api.deps import user_from_token +from fluksio.core import security +from fluksio.core.db import engine +from fluksio.flow.artifacts import ArtifactStore def artifact_caller(request: Request) -> str: diff --git a/backend/app/api/routes/cloud.py b/backend/fluksio/api/routes/cloud.py similarity index 97% rename from backend/app/api/routes/cloud.py rename to backend/fluksio/api/routes/cloud.py index f537855..4a85a4d 100644 --- a/backend/app/api/routes/cloud.py +++ b/backend/fluksio/api/routes/cloud.py @@ -23,16 +23,16 @@ from fastapi import APIRouter, Depends, HTTPException, Request from pydantic import BaseModel, Field from sqlmodel import select -from app import crud -from app.api.deps import ( +from fluksio import crud +from fluksio.api.deps import ( CurrentUser, SessionDep, get_current_active_superuser, get_current_user, ) -from app.cloud import config as cloud_config -from app.core.security import get_password_hash -from app.models import Message, User, UserPublic +from fluksio.cloud import config as cloud_config +from fluksio.core.security import get_password_hash +from fluksio.models import Message, User, UserPublic logger = logging.getLogger(__name__) @@ -279,7 +279,7 @@ def disconnect(request: Request) -> Message: def _start_connector(app: Any) -> None: - from app.cloud.connector import CloudConnector + from fluksio.cloud.connector import CloudConnector existing = getattr(app.state, "cloud_task", None) if existing is not None: diff --git a/backend/app/api/routes/dashboards.py b/backend/fluksio/api/routes/dashboards.py similarity index 96% rename from backend/app/api/routes/dashboards.py rename to backend/fluksio/api/routes/dashboards.py index 960f488..396d8dc 100644 --- a/backend/app/api/routes/dashboards.py +++ b/backend/fluksio/api/routes/dashboards.py @@ -7,17 +7,17 @@ from fastapi import APIRouter, Depends, HTTPException from fastapi.concurrency import run_in_threadpool from pydantic import BaseModel -from app.api.deps import DashboardStoreDep, FlowControllerDep, get_current_user -from app.flow.dashboards import ( +from fluksio.api.deps import DashboardStoreDep, FlowControllerDep, get_current_user +from fluksio.flow.dashboards import ( DashboardDef, DashboardExists, DashboardNotFound, DashboardsPublic, default_dashboard, ) -from app.flow.events import event_bus -from app.flow.store import StaleVersion -from app.models import Message +from fluksio.flow.events import event_bus +from fluksio.flow.store import StaleVersion +from fluksio.models import Message router = APIRouter( prefix="/dashboards", tags=["dashboards"], dependencies=[Depends(get_current_user)] diff --git a/backend/app/api/routes/flows.py b/backend/fluksio/api/routes/flows.py similarity index 98% rename from backend/app/api/routes/flows.py rename to backend/fluksio/api/routes/flows.py index 0109b9f..325ec52 100644 --- a/backend/app/api/routes/flows.py +++ b/backend/fluksio/api/routes/flows.py @@ -18,7 +18,7 @@ from pydantic import BaseModel from sqlalchemy import delete from sqlmodel import Session, col, select -from app.api.deps import ( +from fluksio.api.deps import ( CurrentUser, FlowControllerDep, SessionDep, @@ -26,15 +26,15 @@ from app.api.deps import ( get_current_user, user_from_token, ) -from app.core.db import engine -from app.flow.controller import FlowController -from app.flow.dashboards import DashboardStore -from app.flow.events import event_bus -from app.flow.messages import qualify -from app.flow.panels import messages_for -from app.flow.pipeline import ValidationIssue -from app.flow.runs import RunRejected -from app.flow.schemas import ( +from fluksio.core.db import engine +from fluksio.flow.controller import FlowController +from fluksio.flow.dashboards import DashboardStore +from fluksio.flow.events import event_bus +from fluksio.flow.messages import qualify +from fluksio.flow.panels import messages_for +from fluksio.flow.pipeline import ValidationIssue +from fluksio.flow.runs import RunRejected +from fluksio.flow.schemas import ( NAME_PATTERN, BrainGraph, FlowDef, @@ -49,15 +49,15 @@ from app.flow.schemas import ( NodeStatusPublic, NodeTypeInfo, ) -from app.flow.state import as_number -from app.flow.store import ( +from fluksio.flow.state import as_number +from fluksio.flow.store import ( FlowExists, FlowNotFound, LibExists, LibNotFound, StaleVersion, ) -from app.models import Message, Run, RunArtifact, RunMetric, RunNode +from fluksio.models import Message, Run, RunArtifact, RunMetric, RunNode router = APIRouter( prefix="/flows", tags=["flows"], dependencies=[Depends(get_current_user)] @@ -287,7 +287,7 @@ def read_flows(controller: FlowControllerDep) -> Any: @router.get("/node-types", response_model=list[NodeTypeInfo]) def read_node_types() -> Any: """The node types that can be placed on a canvas.""" - from app.flow.controller import node_type_info + from fluksio.flow.controller import node_type_info return node_type_info() diff --git a/backend/app/api/routes/login.py b/backend/fluksio/api/routes/login.py similarity index 92% rename from backend/app/api/routes/login.py rename to backend/fluksio/api/routes/login.py index 58441e3..2fec5b2 100644 --- a/backend/app/api/routes/login.py +++ b/backend/fluksio/api/routes/login.py @@ -5,12 +5,12 @@ from fastapi import APIRouter, Depends, HTTPException from fastapi.responses import HTMLResponse from fastapi.security import OAuth2PasswordRequestForm -from app import crud -from app.api.deps import CurrentUser, SessionDep, get_current_active_superuser -from app.core import security -from app.core.config import settings -from app.models import Message, NewPassword, Token, UserPublic, UserUpdate -from app.utils import ( +from fluksio import crud +from fluksio.api.deps import CurrentUser, SessionDep, get_current_active_superuser +from fluksio.core import security +from fluksio.core.config import settings +from fluksio.models import Message, NewPassword, Token, UserPublic, UserUpdate +from fluksio.utils import ( generate_password_reset_token, generate_reset_password_email, send_email, diff --git a/backend/app/api/routes/messages.py b/backend/fluksio/api/routes/messages.py similarity index 94% rename from backend/app/api/routes/messages.py rename to backend/fluksio/api/routes/messages.py index a4e488a..ee3d068 100644 --- a/backend/app/api/routes/messages.py +++ b/backend/fluksio/api/routes/messages.py @@ -12,10 +12,10 @@ from fastapi import APIRouter, Depends, HTTPException from fastapi.concurrency import run_in_threadpool from pydantic import BaseModel -from app.api.deps import FlowControllerDep, get_current_user -from app.flow.messages import flow_of -from app.flow.pipeline import ValueSource -from app.flow.state import as_number +from fluksio.api.deps import FlowControllerDep, get_current_user +from fluksio.flow.messages import flow_of +from fluksio.flow.pipeline import ValueSource +from fluksio.flow.state import as_number router = APIRouter( prefix="/messages", tags=["messages"], dependencies=[Depends(get_current_user)] diff --git a/backend/app/api/routes/modules.py b/backend/fluksio/api/routes/modules.py similarity index 92% rename from backend/app/api/routes/modules.py rename to backend/fluksio/api/routes/modules.py index 43aa363..81836e1 100644 --- a/backend/app/api/routes/modules.py +++ b/backend/fluksio/api/routes/modules.py @@ -12,15 +12,15 @@ from typing import Any from fastapi import APIRouter, Depends, HTTPException from fastapi.concurrency import run_in_threadpool -from app.api.deps import ( +from fluksio.api.deps import ( CurrentUser, FlowControllerDep, WorkerPoolDep, get_current_user, ) -from app.flow import modules -from app.flow.events import event_bus -from app.flow.schemas import ApplyRequest, ApplyResult, ModulesInfo +from fluksio.flow import modules +from fluksio.flow.events import event_bus +from fluksio.flow.schemas import ApplyRequest, ApplyResult, ModulesInfo router = APIRouter( prefix="/modules", tags=["modules"], dependencies=[Depends(get_current_user)] diff --git a/backend/app/api/routes/oauth.py b/backend/fluksio/api/routes/oauth.py similarity index 99% rename from backend/app/api/routes/oauth.py rename to backend/fluksio/api/routes/oauth.py index 80e9895..e3128c2 100644 --- a/backend/app/api/routes/oauth.py +++ b/backend/fluksio/api/routes/oauth.py @@ -36,15 +36,15 @@ from fastapi.responses import JSONResponse from pydantic import BaseModel from sqlmodel import col, select -from app.api.deps import ( +from fluksio.api.deps import ( CurrentUser, SessionDep, get_current_active_superuser, get_current_user, ) -from app.core import security -from app.core.config import settings -from app.models import ( +from fluksio.core import security +from fluksio.core.config import settings +from fluksio.models import ( Message, OAuthAuthorizationCode, OAuthAuthorizeInfo, diff --git a/backend/app/api/routes/observability.py b/backend/fluksio/api/routes/observability.py similarity index 98% rename from backend/app/api/routes/observability.py rename to backend/fluksio/api/routes/observability.py index 5e3b6f8..feb26e1 100644 --- a/backend/app/api/routes/observability.py +++ b/backend/fluksio/api/routes/observability.py @@ -17,10 +17,10 @@ from sqlalchemy import ColumnElement, DateTime, Interval, cast, func, literal from sqlalchemy import select as sa_select from sqlmodel import col, select -from app.api.deps import FlowControllerDep, SessionDep, get_current_user -from app.core.config import settings -from app.flow.controller import ADVISORY_ISSUES, NodeStatus -from app.models import EngineEvent, FlowRun, MetricBucket +from fluksio.api.deps import FlowControllerDep, SessionDep, get_current_user +from fluksio.core.config import settings +from fluksio.flow.controller import ADVISORY_ISSUES, NodeStatus +from fluksio.models import EngineEvent, FlowRun, MetricBucket router = APIRouter( prefix="/observability", diff --git a/backend/app/api/routes/panels.py b/backend/fluksio/api/routes/panels.py similarity index 96% rename from backend/app/api/routes/panels.py rename to backend/fluksio/api/routes/panels.py index e1906bc..2bce03c 100644 --- a/backend/app/api/routes/panels.py +++ b/backend/fluksio/api/routes/panels.py @@ -11,7 +11,7 @@ without a session — a device with no credential is the whole point of them — and mints the credential itself when the approval comes. Which side minted it changes nothing about what it may do: the scope check is here either way. -The credential is scoped: ``app.api.deps`` lets it reach the dashboards that +The credential is scoped: ``fluksio.api.deps`` lets it reach the dashboards that panel was assigned and nothing else. Removing the panel revokes it. """ @@ -27,13 +27,13 @@ from fastapi import APIRouter, Depends, HTTPException, Request from fastapi.concurrency import run_in_threadpool from pydantic import BaseModel, Field -from app.api.deps import CurrentUser, get_current_active_superuser, get_current_user -from app.cloud import config as cloud_config -from app.core import security -from app.core.config import settings -from app.flow.events import event_bus -from app.flow.panels import PanelDef, PanelsConfig, find, read_config, write_config -from app.models import Message +from fluksio.api.deps import CurrentUser, get_current_active_superuser, get_current_user +from fluksio.cloud import config as cloud_config +from fluksio.core import security +from fluksio.core.config import settings +from fluksio.flow.events import event_bus +from fluksio.flow.panels import PanelDef, PanelsConfig, find, read_config, write_config +from fluksio.models import Message #: Gated per route rather than on the router: the two pairing endpoints are the #: only unauthenticated ones in the app, because a device with no credential is diff --git a/backend/app/api/routes/private.py b/backend/fluksio/api/routes/private.py similarity index 84% rename from backend/app/api/routes/private.py rename to backend/fluksio/api/routes/private.py index f7a8abd..c7612cd 100644 --- a/backend/app/api/routes/private.py +++ b/backend/fluksio/api/routes/private.py @@ -3,10 +3,10 @@ from typing import Any from fastapi import APIRouter, HTTPException from pydantic import BaseModel -from app.api.deps import SessionDep -from app.core.config import settings -from app.core.security import get_password_hash -from app.models import ( +from fluksio.api.deps import SessionDep +from fluksio.core.config import settings +from fluksio.core.security import get_password_hash +from fluksio.models import ( User, UserPublic, ) diff --git a/backend/app/api/routes/runs.py b/backend/fluksio/api/routes/runs.py similarity index 97% rename from backend/app/api/routes/runs.py rename to backend/fluksio/api/routes/runs.py index 6bf2f22..3b8f6c0 100644 --- a/backend/app/api/routes/runs.py +++ b/backend/fluksio/api/routes/runs.py @@ -12,10 +12,10 @@ from fastapi.concurrency import run_in_threadpool from pydantic import BaseModel, Field from sqlmodel import col, select -from app.api.deps import CurrentUser, SessionDep, get_current_user -from app.flow.runs import RunRejected, RunService, new_run_id -from app.flow.store import FlowNotFound -from app.models import Run, RunArtifact, RunMetric, RunNode +from fluksio.api.deps import CurrentUser, SessionDep, get_current_user +from fluksio.flow.runs import RunRejected, RunService, new_run_id +from fluksio.flow.store import FlowNotFound +from fluksio.models import Run, RunArtifact, RunMetric, RunNode router = APIRouter( prefix="/runs", tags=["runs"], dependencies=[Depends(get_current_user)] diff --git a/backend/app/api/routes/secrets.py b/backend/fluksio/api/routes/secrets.py similarity index 90% rename from backend/app/api/routes/secrets.py rename to backend/fluksio/api/routes/secrets.py index 0ddc0a2..7584d12 100644 --- a/backend/app/api/routes/secrets.py +++ b/backend/fluksio/api/routes/secrets.py @@ -9,9 +9,9 @@ from fastapi import APIRouter, Depends, HTTPException from fastapi.concurrency import run_in_threadpool from pydantic import BaseModel -from app.api.deps import get_current_user -from app.flow.secrets import SecretNotFound, get_secrets -from app.models import Message +from fluksio.api.deps import get_current_user +from fluksio.flow.secrets import SecretNotFound, get_secrets +from fluksio.models import Message router = APIRouter( prefix="/secrets", tags=["secrets"], dependencies=[Depends(get_current_user)] diff --git a/backend/app/api/routes/users.py b/backend/fluksio/api/routes/users.py similarity index 95% rename from backend/app/api/routes/users.py rename to backend/fluksio/api/routes/users.py index 82385f8..4e5f743 100644 --- a/backend/app/api/routes/users.py +++ b/backend/fluksio/api/routes/users.py @@ -4,16 +4,16 @@ from typing import Any from fastapi import APIRouter, Depends, HTTPException from sqlmodel import col, func, select -from app import crud -from app.api.deps import ( +from fluksio import crud +from fluksio.api.deps import ( CurrentUser, SessionDep, get_current_active_superuser, ) -from app.api.routes.cloud import forget_remote_user -from app.core.config import settings -from app.core.security import get_password_hash, verify_password -from app.models import ( +from fluksio.api.routes.cloud import forget_remote_user +from fluksio.core.config import settings +from fluksio.core.security import get_password_hash, verify_password +from fluksio.models import ( Message, UpdatePassword, User, @@ -24,7 +24,7 @@ from app.models import ( UserUpdate, UserUpdateMe, ) -from app.utils import generate_new_account_email, send_email +from fluksio.utils import generate_new_account_email, send_email router = APIRouter(prefix="/users", tags=["users"]) diff --git a/backend/app/api/routes/utils.py b/backend/fluksio/api/routes/utils.py similarity index 94% rename from backend/app/api/routes/utils.py rename to backend/fluksio/api/routes/utils.py index fa2ba54..b7b23b7 100644 --- a/backend/app/api/routes/utils.py +++ b/backend/fluksio/api/routes/utils.py @@ -5,10 +5,10 @@ from fastapi import APIRouter, Depends, Request, Response from fastapi.concurrency import run_in_threadpool from pydantic.networks import EmailStr -from app.api.deps import get_current_active_superuser -from app.flow.state import RedisState -from app.models import Message -from app.utils import generate_test_email, send_email +from fluksio.api.deps import get_current_active_superuser +from fluksio.flow.state import RedisState +from fluksio.models import Message +from fluksio.utils import generate_test_email, send_email router = APIRouter(prefix="/utils", tags=["utils"]) diff --git a/backend/app/api/routes/workers.py b/backend/fluksio/api/routes/workers.py similarity index 95% rename from backend/app/api/routes/workers.py rename to backend/fluksio/api/routes/workers.py index 7939710..d628c51 100644 --- a/backend/app/api/routes/workers.py +++ b/backend/fluksio/api/routes/workers.py @@ -18,10 +18,10 @@ from fastapi.responses import PlainTextResponse from jwt.exceptions import InvalidTokenError from pydantic import BaseModel, Field -from app.api.deps import get_current_active_superuser, get_current_user -from app.core import security -from app.flow import worker_main -from app.flow.remote import PROTOCOL, RemoteWorker, RemoteWorkerHub +from fluksio.api.deps import get_current_active_superuser, get_current_user +from fluksio.core import security +from fluksio.flow import worker_main +from fluksio.flow.remote import PROTOCOL, RemoteWorker, RemoteWorkerHub logger = logging.getLogger(__name__) diff --git a/backend/app/backend_pre_start.py b/backend/fluksio/backend_pre_start.py similarity index 96% rename from backend/app/backend_pre_start.py rename to backend/fluksio/backend_pre_start.py index c2f8e29..8f68fd1 100644 --- a/backend/app/backend_pre_start.py +++ b/backend/fluksio/backend_pre_start.py @@ -4,7 +4,7 @@ from sqlalchemy import Engine from sqlmodel import Session, select from tenacity import after_log, before_log, retry, stop_after_attempt, wait_fixed -from app.core.db import engine +from fluksio.core.db import engine logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) diff --git a/backend/app/cloud/__init__.py b/backend/fluksio/cloud/__init__.py similarity index 100% rename from backend/app/cloud/__init__.py rename to backend/fluksio/cloud/__init__.py diff --git a/backend/app/cloud/config.py b/backend/fluksio/cloud/config.py similarity index 99% rename from backend/app/cloud/config.py rename to backend/fluksio/cloud/config.py index 540837b..f70d7cb 100644 --- a/backend/app/cloud/config.py +++ b/backend/fluksio/cloud/config.py @@ -24,7 +24,7 @@ from typing import Any import jwt from jwt.exceptions import InvalidTokenError -from app.core.config import settings +from fluksio.core.config import settings logger = logging.getLogger(__name__) diff --git a/backend/app/cloud/connector.py b/backend/fluksio/cloud/connector.py similarity index 97% rename from backend/app/cloud/connector.py rename to backend/fluksio/cloud/connector.py index db165a9..e98a7ba 100644 --- a/backend/app/cloud/connector.py +++ b/backend/fluksio/cloud/connector.py @@ -26,8 +26,8 @@ from typing import Any import httpx from fastapi import FastAPI -from app.cloud import config as cloud_config -from app.core.config import settings +from fluksio.cloud import config as cloud_config +from fluksio.core.config import settings logger = logging.getLogger(__name__) @@ -158,8 +158,8 @@ class CloudConnector: """ from sqlmodel import Session, select - from app.core.db import engine - from app.models import User + from fluksio.core.db import engine + from fluksio.models import User if not owner: return @@ -208,7 +208,7 @@ class CloudConnector: a short-lived local token: the observability API is authenticated, and this process is entitled to mint one for the enrolling user. """ - from app.core import security + from fluksio.core import security token = security.create_access_token(config.local_user_id, timedelta(minutes=5)) try: @@ -339,14 +339,14 @@ class CloudConnector: """ from sqlmodel import Session - from app.api.deps import user_from_token - from app.api.routes.flows import ( + from fluksio.api.deps import user_from_token + from fluksio.api.routes.flows import ( event_for_panel, panel_scope, snapshot_payload, ) - from app.core.db import engine - from app.flow.events import event_bus + from fluksio.core.db import engine + from fluksio.flow.events import event_bus stream_id = str(frame["id"]) path = str(frame.get("path") or "") diff --git a/backend/app/core/__init__.py b/backend/fluksio/core/__init__.py similarity index 100% rename from backend/app/core/__init__.py rename to backend/fluksio/core/__init__.py diff --git a/backend/app/core/config.py b/backend/fluksio/core/config.py similarity index 100% rename from backend/app/core/config.py rename to backend/fluksio/core/config.py diff --git a/backend/app/core/db.py b/backend/fluksio/core/db.py similarity index 91% rename from backend/app/core/db.py rename to backend/fluksio/core/db.py index 3692575..b06deb3 100644 --- a/backend/app/core/db.py +++ b/backend/fluksio/core/db.py @@ -1,8 +1,8 @@ from sqlmodel import Session, create_engine, select -from app import crud -from app.core.config import settings -from app.models import User, UserCreate +from fluksio import crud +from fluksio.core.config import settings +from fluksio.models import User, UserCreate # A connection idle across a Postgres restart is dead but still pooled; the # pre-ping spends a round trip to find out instead of failing the request. diff --git a/backend/app/core/security.py b/backend/fluksio/core/security.py similarity index 98% rename from backend/app/core/security.py rename to backend/fluksio/core/security.py index c271c60..a5b636a 100644 --- a/backend/app/core/security.py +++ b/backend/fluksio/core/security.py @@ -11,7 +11,7 @@ from pwdlib import PasswordHash from pwdlib.hashers.argon2 import Argon2Hasher from pwdlib.hashers.bcrypt import BcryptHasher -from app.core.config import settings +from fluksio.core.config import settings password_hash = PasswordHash( ( @@ -182,7 +182,7 @@ def create_panel_token( person in exactly the same way: ``sub`` is the account that approved the pairing, so everything the panel does is attributable to them. What keeps it from being a full session is the ``panel`` claim — the request filter in - ``app.api.deps`` lets it reach only that panel's dashboards and the message + ``fluksio.api.deps`` lets it reach only that panel's dashboards and the message endpoints its widgets need. Long-lived on purpose: a wall tablet is set up once and left running, and diff --git a/backend/app/crud.py b/backend/fluksio/crud.py similarity index 94% rename from backend/app/crud.py rename to backend/fluksio/crud.py index 434dff2..36fd424 100644 --- a/backend/app/crud.py +++ b/backend/fluksio/crud.py @@ -2,8 +2,8 @@ from typing import Any from sqlmodel import Session, select -from app.core.security import get_password_hash, verify_password -from app.models import User, UserCreate, UserUpdate +from fluksio.core.security import get_password_hash, verify_password +from fluksio.models import User, UserCreate, UserUpdate def create_user(*, session: Session, user_create: UserCreate) -> User: diff --git a/backend/app/email-templates/build/new_account.html b/backend/fluksio/email-templates/build/new_account.html similarity index 100% rename from backend/app/email-templates/build/new_account.html rename to backend/fluksio/email-templates/build/new_account.html diff --git a/backend/app/email-templates/build/reset_password.html b/backend/fluksio/email-templates/build/reset_password.html similarity index 100% rename from backend/app/email-templates/build/reset_password.html rename to backend/fluksio/email-templates/build/reset_password.html diff --git a/backend/app/email-templates/build/test_email.html b/backend/fluksio/email-templates/build/test_email.html similarity index 100% rename from backend/app/email-templates/build/test_email.html rename to backend/fluksio/email-templates/build/test_email.html diff --git a/backend/app/email-templates/src/new_account.mjml b/backend/fluksio/email-templates/src/new_account.mjml similarity index 100% rename from backend/app/email-templates/src/new_account.mjml rename to backend/fluksio/email-templates/src/new_account.mjml diff --git a/backend/app/email-templates/src/reset_password.mjml b/backend/fluksio/email-templates/src/reset_password.mjml similarity index 100% rename from backend/app/email-templates/src/reset_password.mjml rename to backend/fluksio/email-templates/src/reset_password.mjml diff --git a/backend/app/email-templates/src/test_email.mjml b/backend/fluksio/email-templates/src/test_email.mjml similarity index 100% rename from backend/app/email-templates/src/test_email.mjml rename to backend/fluksio/email-templates/src/test_email.mjml diff --git a/backend/fluksio/flow/__init__.py b/backend/fluksio/flow/__init__.py new file mode 100644 index 0000000..277ff9a --- /dev/null +++ b/backend/fluksio/flow/__init__.py @@ -0,0 +1,26 @@ +"""The flow engine: nodes, the pipeline that runs them, and their storage.""" + +from fluksio.flow.controller import FlowController, NodeStatus +from fluksio.flow.events import EventBus, event_bus +from fluksio.flow.messages import DType, MessageSpec, qualify +from fluksio.flow.nodes import Node +from fluksio.flow.pipeline import Pipeline, ValidationIssue +from fluksio.flow.state import MemoryState, RedisState, StateBackend +from fluksio.flow.store import FlowStore + +__all__ = [ + "DType", + "EventBus", + "FlowController", + "FlowStore", + "MemoryState", + "MessageSpec", + "Node", + "NodeStatus", + "Pipeline", + "RedisState", + "StateBackend", + "ValidationIssue", + "event_bus", + "qualify", +] diff --git a/backend/app/flow/alerts.py b/backend/fluksio/flow/alerts.py similarity index 98% rename from backend/app/flow/alerts.py rename to backend/fluksio/flow/alerts.py index 137f228..e614d8c 100644 --- a/backend/app/flow/alerts.py +++ b/backend/fluksio/flow/alerts.py @@ -22,8 +22,8 @@ from typing import Any, Literal import httpx from pydantic import BaseModel, Field -from app.flow.events import EventBus -from app.flow.secrets import resolve_params +from fluksio.flow.events import EventBus +from fluksio.flow.secrets import resolve_params logger = logging.getLogger(__name__) @@ -351,8 +351,8 @@ class AlertManager: response.raise_for_status() async def _send_email(self, config: dict[str, Any], alert: Alert) -> None: - from app.core.config import settings - from app.utils import send_email + from fluksio.core.config import settings + from fluksio.utils import send_email recipient = config.get("to") if not recipient: diff --git a/backend/app/flow/artifacts.py b/backend/fluksio/flow/artifacts.py similarity index 100% rename from backend/app/flow/artifacts.py rename to backend/fluksio/flow/artifacts.py diff --git a/backend/app/flow/connector.py b/backend/fluksio/flow/connector.py similarity index 99% rename from backend/app/flow/connector.py rename to backend/fluksio/flow/connector.py index 4f1a59d..b55c23e 100644 --- a/backend/app/flow/connector.py +++ b/backend/fluksio/flow/connector.py @@ -32,7 +32,7 @@ from typing import TYPE_CHECKING, Any, ClassVar from pydantic import BaseModel, Field -from app.flow.nodes import Node +from fluksio.flow.nodes import Node if TYPE_CHECKING: from fastapi import FastAPI diff --git a/backend/app/flow/controller.py b/backend/fluksio/flow/controller.py similarity index 98% rename from backend/app/flow/controller.py rename to backend/fluksio/flow/controller.py index d427f3d..f362a79 100644 --- a/backend/app/flow/controller.py +++ b/backend/fluksio/flow/controller.py @@ -21,12 +21,12 @@ from typing import Any, cast from fastapi import FastAPI from fastapi.concurrency import run_in_threadpool -from app.core.config import settings -from app.flow.alerts import AlertManager -from app.flow.events import EventBus -from app.flow.executor import ExecutionService -from app.flow.messages import MessageSpec, flow_of, qualify -from app.flow.nodes import ( +from fluksio.core.config import settings +from fluksio.flow.alerts import AlertManager +from fluksio.flow.events import EventBus +from fluksio.flow.executor import ExecutionService +from fluksio.flow.messages import MessageSpec, flow_of, qualify +from fluksio.flow.nodes import ( RESERVED_SETTINGS, ChangeNode, DelayNode, @@ -44,9 +44,9 @@ from app.flow.nodes import ( SwitchNode, TriggerNode, ) -from app.flow.pipeline import NodeOutcome, Pipeline, ValidationIssue, ValueSource -from app.flow.remote import RemoteWorkerHub -from app.flow.schemas import ( +from fluksio.flow.pipeline import NodeOutcome, Pipeline, ValidationIssue, ValueSource +from fluksio.flow.remote import RemoteWorkerHub +from fluksio.flow.schemas import ( BrainEdge, BrainGraph, BrainNode, @@ -56,12 +56,12 @@ from app.flow.schemas import ( NodeStatusPublic, NodeTypeInfo, ) -from app.flow.secrets import SecretNotFound, resolve_params -from app.flow.state import MemoryState, StateBackend -from app.flow.store import LIB_DIR, FlowNotFound, FlowStore, LibNotFound -from app.flow.supervision import Supervisor -from app.flow.worker_main import load_function -from app.flow.workers import PythonWorkerPool +from fluksio.flow.secrets import SecretNotFound, resolve_params +from fluksio.flow.state import MemoryState, StateBackend +from fluksio.flow.store import LIB_DIR, FlowNotFound, FlowStore, LibNotFound +from fluksio.flow.supervision import Supervisor +from fluksio.flow.worker_main import load_function +from fluksio.flow.workers import PythonWorkerPool logger = logging.getLogger(__name__) @@ -807,7 +807,7 @@ class FlowController: What a dashboard picks from, so it spans flows rather than sitting inside one. """ - from app.api.routes.messages import MessageInfo + from fluksio.api.routes.messages import MessageInfo specs: dict[str, MessageSpec] = {} providers: dict[str, list[str]] = {} diff --git a/backend/app/flow/dashboards.py b/backend/fluksio/flow/dashboards.py similarity index 99% rename from backend/app/flow/dashboards.py rename to backend/fluksio/flow/dashboards.py index 940980f..21d8bf0 100644 --- a/backend/app/flow/dashboards.py +++ b/backend/fluksio/flow/dashboards.py @@ -23,8 +23,8 @@ from typing import Any, Literal from pydantic import BaseModel, Field, field_validator, model_validator -from app.flow.schemas import _validate_name -from app.flow.store import FlowStore, StaleVersion +from fluksio.flow.schemas import _validate_name +from fluksio.flow.store import FlowStore, StaleVersion #: Sibling of the shared-node library, and likewise not a flow. DASHBOARD_DIR = "_dashboards" diff --git a/backend/app/flow/events.py b/backend/fluksio/flow/events.py similarity index 100% rename from backend/app/flow/events.py rename to backend/fluksio/flow/events.py diff --git a/backend/app/flow/executor.py b/backend/fluksio/flow/executor.py similarity index 98% rename from backend/app/flow/executor.py rename to backend/fluksio/flow/executor.py index 2909658..284133c 100644 --- a/backend/app/flow/executor.py +++ b/backend/fluksio/flow/executor.py @@ -18,11 +18,11 @@ import time from concurrent.futures import ThreadPoolExecutor from typing import TYPE_CHECKING, Any -from app.flow.queue import MAX_DELIVERIES, WorkItem, WorkQueue +from fluksio.flow.queue import MAX_DELIVERIES, WorkItem, WorkQueue if TYPE_CHECKING: - from app.flow.events import EventBus - from app.flow.pipeline import Pipeline + from fluksio.flow.events import EventBus + from fluksio.flow.pipeline import Pipeline logger = logging.getLogger(__name__) diff --git a/backend/app/flow/logs.py b/backend/fluksio/flow/logs.py similarity index 100% rename from backend/app/flow/logs.py rename to backend/fluksio/flow/logs.py diff --git a/backend/app/flow/messages.py b/backend/fluksio/flow/messages.py similarity index 100% rename from backend/app/flow/messages.py rename to backend/fluksio/flow/messages.py diff --git a/backend/app/flow/metrics.py b/backend/fluksio/flow/metrics.py similarity index 98% rename from backend/app/flow/metrics.py rename to backend/fluksio/flow/metrics.py index db8a31d..7319d27 100644 --- a/backend/app/flow/metrics.py +++ b/backend/fluksio/flow/metrics.py @@ -22,10 +22,10 @@ from sqlalchemy import delete, func, update from sqlalchemy.dialects.postgresql import insert from sqlmodel import Session, col -from app.core.config import settings -from app.core.db import engine -from app.flow.events import EventBus -from app.models import EngineEvent, FlowRun, MetricBucket +from fluksio.core.config import settings +from fluksio.core.db import engine +from fluksio.flow.events import EventBus +from fluksio.models import EngineEvent, FlowRun, MetricBucket logger = logging.getLogger(__name__) diff --git a/backend/app/flow/modules.py b/backend/fluksio/flow/modules.py similarity index 97% rename from backend/app/flow/modules.py rename to backend/fluksio/flow/modules.py index 3a54526..46a2c8d 100644 --- a/backend/app/flow/modules.py +++ b/backend/fluksio/flow/modules.py @@ -22,11 +22,11 @@ import tempfile from pathlib import Path from typing import TYPE_CHECKING -from app.core.config import settings -from app.flow.schemas import ModulePackage, ModulesInfo +from fluksio.core.config import settings +from fluksio.flow.schemas import ModulePackage, ModulesInfo if TYPE_CHECKING: - from app.flow.store import FlowStore + from fluksio.flow.store import FlowStore logger = logging.getLogger(__name__) diff --git a/backend/fluksio/flow/nodes/__init__.py b/backend/fluksio/flow/nodes/__init__.py new file mode 100644 index 0000000..6237ae9 --- /dev/null +++ b/backend/fluksio/flow/nodes/__init__.py @@ -0,0 +1,37 @@ +"""Built-in node types. + +Split by the outside world each one talks to. Importing from +``fluksio.flow.nodes`` keeps working, which is what every caller does. +""" + +from fluksio.flow.nodes.base import RESERVED_SETTINGS, Node +from fluksio.flow.nodes.delay import DelayNode +from fluksio.flow.nodes.exec import ExecNode +from fluksio.flow.nodes.file import FileNode +from fluksio.flow.nodes.http import HttpNode +from fluksio.flow.nodes.influx import InfluxDbNode +from fluksio.flow.nodes.inject import InjectNode +from fluksio.flow.nodes.logic import ChangeNode, JoinNode, RbeNode, SwitchNode +from fluksio.flow.nodes.mlp import MLPNode +from fluksio.flow.nodes.mqtt import MqttNode +from fluksio.flow.nodes.ntfy import NtfyNode +from fluksio.flow.nodes.trigger import TriggerNode + +__all__ = [ + "ChangeNode", + "DelayNode", + "ExecNode", + "FileNode", + "HttpNode", + "InfluxDbNode", + "InjectNode", + "JoinNode", + "MLPNode", + "MqttNode", + "Node", + "NtfyNode", + "RESERVED_SETTINGS", + "RbeNode", + "SwitchNode", + "TriggerNode", +] diff --git a/backend/app/flow/nodes/base.py b/backend/fluksio/flow/nodes/base.py similarity index 98% rename from backend/app/flow/nodes/base.py rename to backend/fluksio/flow/nodes/base.py index 0964f05..5601815 100644 --- a/backend/app/flow/nodes/base.py +++ b/backend/fluksio/flow/nodes/base.py @@ -12,15 +12,15 @@ import logging from collections.abc import Callable, Coroutine, Iterable, Iterator from typing import TYPE_CHECKING, Any, TypeAlias -from app.flow import logs -from app.flow.messages import MessageSpec, qualify -from app.flow.supervision import Supervisor +from fluksio.flow import logs +from fluksio.flow.messages import MessageSpec, qualify +from fluksio.flow.supervision import Supervisor if TYPE_CHECKING: from fastapi import FastAPI - from app.flow.pipeline import Pipeline - from app.flow.state import StateBackend + from fluksio.flow.pipeline import Pipeline + from fluksio.flow.state import StateBackend logger = logging.getLogger(__name__) diff --git a/backend/app/flow/nodes/delay.py b/backend/fluksio/flow/nodes/delay.py similarity index 99% rename from backend/app/flow/nodes/delay.py rename to backend/fluksio/flow/nodes/delay.py index ba62a2e..fc86531 100644 --- a/backend/app/flow/nodes/delay.py +++ b/backend/fluksio/flow/nodes/delay.py @@ -9,7 +9,7 @@ from typing import TYPE_CHECKING, Any from pydantic import BaseModel, ConfigDict -from app.flow.nodes.base import Node +from fluksio.flow.nodes.base import Node if TYPE_CHECKING: from fastapi import FastAPI diff --git a/backend/app/flow/nodes/exec.py b/backend/fluksio/flow/nodes/exec.py similarity index 97% rename from backend/app/flow/nodes/exec.py rename to backend/fluksio/flow/nodes/exec.py index 4129bd1..5b47f08 100644 --- a/backend/app/flow/nodes/exec.py +++ b/backend/fluksio/flow/nodes/exec.py @@ -15,8 +15,8 @@ from typing import Any from pydantic import BaseModel, ConfigDict, Field -from app.flow.messages import MessageSpec -from app.flow.nodes.base import Node +from fluksio.flow.messages import MessageSpec +from fluksio.flow.nodes.base import Node logger = logging.getLogger(__name__) diff --git a/backend/app/flow/nodes/file.py b/backend/fluksio/flow/nodes/file.py similarity index 95% rename from backend/app/flow/nodes/file.py rename to backend/fluksio/flow/nodes/file.py index 0b2dc9a..f8f0342 100644 --- a/backend/app/flow/nodes/file.py +++ b/backend/fluksio/flow/nodes/file.py @@ -14,15 +14,15 @@ from typing import Any, Literal from pydantic import BaseModel, ConfigDict, Field -from app.flow.messages import MessageSpec -from app.flow.nodes.base import Node +from fluksio.flow.messages import MessageSpec +from fluksio.flow.nodes.base import Node logger = logging.getLogger(__name__) def files_root() -> Path: """Where flow-readable files live: beside the flow store, not inside it.""" - from app.core.config import settings + from fluksio.core.config import settings return settings.FLOWS_DIR.parent / "files" diff --git a/backend/app/flow/nodes/http.py b/backend/fluksio/flow/nodes/http.py similarity index 99% rename from backend/app/flow/nodes/http.py rename to backend/fluksio/flow/nodes/http.py index b4a39a8..7d0cca8 100644 --- a/backend/app/flow/nodes/http.py +++ b/backend/fluksio/flow/nodes/http.py @@ -13,8 +13,8 @@ from urllib.parse import urlsplit, urlunsplit import httpx from pydantic import BaseModel, ConfigDict, Field -from app.flow.messages import MessageSpec -from app.flow.nodes.base import Node +from fluksio.flow.messages import MessageSpec +from fluksio.flow.nodes.base import Node if TYPE_CHECKING: from fastapi import FastAPI diff --git a/backend/app/flow/nodes/influx.py b/backend/fluksio/flow/nodes/influx.py similarity index 99% rename from backend/app/flow/nodes/influx.py rename to backend/fluksio/flow/nodes/influx.py index cbb6a44..3b192a0 100644 --- a/backend/app/flow/nodes/influx.py +++ b/backend/fluksio/flow/nodes/influx.py @@ -8,8 +8,8 @@ from typing import Any from pydantic import BaseModel, ConfigDict, Field -from app.flow.messages import MessageSpec -from app.flow.nodes.base import Node, NodeResult +from fluksio.flow.messages import MessageSpec +from fluksio.flow.nodes.base import Node, NodeResult logger = logging.getLogger(__name__) diff --git a/backend/app/flow/nodes/inject.py b/backend/fluksio/flow/nodes/inject.py similarity index 98% rename from backend/app/flow/nodes/inject.py rename to backend/fluksio/flow/nodes/inject.py index 4e2d6c4..d4c2a85 100644 --- a/backend/app/flow/nodes/inject.py +++ b/backend/fluksio/flow/nodes/inject.py @@ -15,8 +15,8 @@ from typing import TYPE_CHECKING, Any from pydantic import BaseModel, ConfigDict, Field -from app.flow.messages import MessageSpec -from app.flow.nodes.base import Node +from fluksio.flow.messages import MessageSpec +from fluksio.flow.nodes.base import Node if TYPE_CHECKING: from fastapi import FastAPI diff --git a/backend/app/flow/nodes/logic.py b/backend/fluksio/flow/nodes/logic.py similarity index 99% rename from backend/app/flow/nodes/logic.py rename to backend/fluksio/flow/nodes/logic.py index 898f0ec..3857575 100644 --- a/backend/app/flow/nodes/logic.py +++ b/backend/fluksio/flow/nodes/logic.py @@ -14,8 +14,8 @@ from typing import Any, Literal from pydantic import BaseModel, ConfigDict, Field -from app.flow.messages import MessageSpec -from app.flow.nodes.base import Node +from fluksio.flow.messages import MessageSpec +from fluksio.flow.nodes.base import Node logger = logging.getLogger(__name__) diff --git a/backend/app/flow/nodes/mlp.py b/backend/fluksio/flow/nodes/mlp.py similarity index 97% rename from backend/app/flow/nodes/mlp.py rename to backend/fluksio/flow/nodes/mlp.py index e58bcda..75a9346 100644 --- a/backend/app/flow/nodes/mlp.py +++ b/backend/fluksio/flow/nodes/mlp.py @@ -10,8 +10,8 @@ from typing import Any import numpy as np from pydantic import BaseModel, ConfigDict -from app.flow.messages import MessageSpec -from app.flow.nodes.base import Node +from fluksio.flow.messages import MessageSpec +from fluksio.flow.nodes.base import Node logger = logging.getLogger(__name__) diff --git a/backend/app/flow/nodes/mqtt.py b/backend/fluksio/flow/nodes/mqtt.py similarity index 99% rename from backend/app/flow/nodes/mqtt.py rename to backend/fluksio/flow/nodes/mqtt.py index fa154ed..bcd8dd5 100644 --- a/backend/app/flow/nodes/mqtt.py +++ b/backend/fluksio/flow/nodes/mqtt.py @@ -11,8 +11,8 @@ from typing import TYPE_CHECKING, Any from pydantic import BaseModel, ConfigDict, Field -from app.flow.messages import MessageSpec -from app.flow.nodes.base import Node +from fluksio.flow.messages import MessageSpec +from fluksio.flow.nodes.base import Node if TYPE_CHECKING: from fastapi import FastAPI diff --git a/backend/app/flow/nodes/ntfy.py b/backend/fluksio/flow/nodes/ntfy.py similarity index 95% rename from backend/app/flow/nodes/ntfy.py rename to backend/fluksio/flow/nodes/ntfy.py index dc7c175..94e5bbf 100644 --- a/backend/app/flow/nodes/ntfy.py +++ b/backend/fluksio/flow/nodes/ntfy.py @@ -13,9 +13,9 @@ from typing import Any from pydantic import BaseModel, ConfigDict, Field -from app.flow.messages import MessageSpec -from app.flow.nodes.base import Node -from app.flow.nodes.http import shared_client +from fluksio.flow.messages import MessageSpec +from fluksio.flow.nodes.base import Node +from fluksio.flow.nodes.http import shared_client logger = logging.getLogger(__name__) diff --git a/backend/app/flow/nodes/trigger.py b/backend/fluksio/flow/nodes/trigger.py similarity index 97% rename from backend/app/flow/nodes/trigger.py rename to backend/fluksio/flow/nodes/trigger.py index 2f69ff0..29e5aa8 100644 --- a/backend/app/flow/nodes/trigger.py +++ b/backend/fluksio/flow/nodes/trigger.py @@ -13,8 +13,8 @@ from typing import Any from pydantic import BaseModel, ConfigDict, Field -from app.flow.messages import MessageSpec -from app.flow.nodes.base import Node +from fluksio.flow.messages import MessageSpec +from fluksio.flow.nodes.base import Node logger = logging.getLogger(__name__) diff --git a/backend/app/flow/panels.py b/backend/fluksio/flow/panels.py similarity index 95% rename from backend/app/flow/panels.py rename to backend/fluksio/flow/panels.py index 5667695..a4e185e 100644 --- a/backend/app/flow/panels.py +++ b/backend/fluksio/flow/panels.py @@ -16,9 +16,9 @@ from pathlib import Path from pydantic import BaseModel, Field, field_validator -from app.core.config import settings -from app.flow.dashboards import DashboardNotFound, DashboardStore -from app.flow.schemas import _validate_name +from fluksio.core.config import settings +from fluksio.flow.dashboards import DashboardNotFound, DashboardStore +from fluksio.flow.schemas import _validate_name class PanelDef(BaseModel): diff --git a/backend/app/flow/pipeline.py b/backend/fluksio/flow/pipeline.py similarity index 99% rename from backend/app/flow/pipeline.py rename to backend/fluksio/flow/pipeline.py index 456943c..1ee8497 100644 --- a/backend/app/flow/pipeline.py +++ b/backend/fluksio/flow/pipeline.py @@ -20,13 +20,13 @@ from typing import Any, Literal from pydantic import BaseModel -from app.flow import logs -from app.flow.artifacts import is_reference -from app.flow.events import EventBus -from app.flow.messages import flow_of -from app.flow.nodes import Node -from app.flow.queue import WorkQueue -from app.flow.state import MemoryState, StateBackend +from fluksio.flow import logs +from fluksio.flow.artifacts import is_reference +from fluksio.flow.events import EventBus +from fluksio.flow.messages import flow_of +from fluksio.flow.nodes import Node +from fluksio.flow.queue import WorkQueue +from fluksio.flow.state import MemoryState, StateBackend logger = logging.getLogger(__name__) @@ -1136,7 +1136,7 @@ class Pipeline: Returns False when there is no queue to hold the item, in which case the caller has to wait however it waited before. """ - from app.flow.queue import WorkItem + from fluksio.flow.queue import WorkItem if self._queue is None or seconds <= 0: return False @@ -1158,7 +1158,7 @@ class Pipeline: def _enqueue_cascade(self, node: Node, outputs: dict[str, Any] | None) -> None: """Journal a trigger, or fall back to running it here if that fails.""" - from app.flow.queue import WorkItem + from fluksio.flow.queue import WorkItem item = WorkItem( kind="cascade", diff --git a/backend/app/flow/plugins.py b/backend/fluksio/flow/plugins.py similarity index 95% rename from backend/app/flow/plugins.py rename to backend/fluksio/flow/plugins.py index 91e929b..072b517 100644 --- a/backend/app/flow/plugins.py +++ b/backend/fluksio/flow/plugins.py @@ -15,7 +15,7 @@ from __future__ import annotations import logging from importlib.metadata import entry_points -from app.flow.connector import CONTRACT_VERSION, ConnectorNode +from fluksio.flow.connector import CONTRACT_VERSION, ConnectorNode logger = logging.getLogger(__name__) @@ -24,7 +24,7 @@ ENTRY_POINT_GROUP = "fluksio.node_types" def load_plugins() -> list[str]: """Register every installed connector. Returns the type names it added.""" - from app.flow.controller import NODE_TYPES, NodeType, _schema_of + from fluksio.flow.controller import NODE_TYPES, NodeType, _schema_of added: list[str] = [] for entry in entry_points(group=ENTRY_POINT_GROUP): diff --git a/backend/app/flow/queue.py b/backend/fluksio/flow/queue.py similarity index 100% rename from backend/app/flow/queue.py rename to backend/fluksio/flow/queue.py diff --git a/backend/app/flow/remote.py b/backend/fluksio/flow/remote.py similarity index 99% rename from backend/app/flow/remote.py rename to backend/fluksio/flow/remote.py index 91f9ef0..b2768e6 100644 --- a/backend/app/flow/remote.py +++ b/backend/fluksio/flow/remote.py @@ -30,7 +30,7 @@ import time from collections.abc import Callable from typing import Any -from app.flow.workers import NodeTimeout, RemoteError, _remote_class +from fluksio.flow.workers import NodeTimeout, RemoteError, _remote_class logger = logging.getLogger(__name__) diff --git a/backend/app/flow/runs.py b/backend/fluksio/flow/runs.py similarity index 98% rename from backend/app/flow/runs.py rename to backend/fluksio/flow/runs.py index d3cac98..c6efa64 100644 --- a/backend/app/flow/runs.py +++ b/backend/fluksio/flow/runs.py @@ -42,14 +42,14 @@ from sqlalchemy import update from sqlalchemy.dialects.postgresql import insert as pg_insert from sqlmodel import Session, col, select -from app.core.db import engine as db_engine -from app.flow.controller import FlowController, RunContext -from app.flow.messages import qualify -from app.flow.pipeline import NodeOutcome, Pipeline -from app.flow.queue import WorkItem, WorkQueue -from app.flow.schemas import FlowDef -from app.flow.state import MemoryState, StateBackend -from app.models import Run, RunArtifact, RunMetric, RunNode +from fluksio.core.db import engine as db_engine +from fluksio.flow.controller import FlowController, RunContext +from fluksio.flow.messages import qualify +from fluksio.flow.pipeline import NodeOutcome, Pipeline +from fluksio.flow.queue import WorkItem, WorkQueue +from fluksio.flow.schemas import FlowDef +from fluksio.flow.state import MemoryState, StateBackend +from fluksio.models import Run, RunArtifact, RunMetric, RunNode logger = logging.getLogger(__name__) diff --git a/backend/app/flow/schemas.py b/backend/fluksio/flow/schemas.py similarity index 99% rename from backend/app/flow/schemas.py rename to backend/fluksio/flow/schemas.py index c0ace0c..5721cbb 100644 --- a/backend/app/flow/schemas.py +++ b/backend/fluksio/flow/schemas.py @@ -11,7 +11,7 @@ from typing import Any, Literal from pydantic import BaseModel, Field, field_validator -from app.flow.messages import MessageSpec +from fluksio.flow.messages import MessageSpec NAME_PATTERN = re.compile(r"^[a-z][a-z0-9_]*$") diff --git a/backend/app/flow/secrets.py b/backend/fluksio/flow/secrets.py similarity index 100% rename from backend/app/flow/secrets.py rename to backend/fluksio/flow/secrets.py diff --git a/backend/app/flow/state.py b/backend/fluksio/flow/state.py similarity index 100% rename from backend/app/flow/state.py rename to backend/fluksio/flow/state.py diff --git a/backend/app/flow/store.py b/backend/fluksio/flow/store.py similarity index 99% rename from backend/app/flow/store.py rename to backend/fluksio/flow/store.py index cf4f354..5ef120f 100644 --- a/backend/app/flow/store.py +++ b/backend/fluksio/flow/store.py @@ -21,7 +21,7 @@ import subprocess import threading from pathlib import Path -from app.flow.schemas import FlowDef +from fluksio.flow.schemas import FlowDef logger = logging.getLogger(__name__) diff --git a/backend/app/flow/supervision.py b/backend/fluksio/flow/supervision.py similarity index 99% rename from backend/app/flow/supervision.py rename to backend/fluksio/flow/supervision.py index 63ffd2b..6d7f92b 100644 --- a/backend/app/flow/supervision.py +++ b/backend/fluksio/flow/supervision.py @@ -20,7 +20,7 @@ from collections import deque from collections.abc import Callable, Coroutine from typing import Any -from app.flow.events import EventBus +from fluksio.flow.events import EventBus logger = logging.getLogger(__name__) diff --git a/backend/app/flow/watchdog.py b/backend/fluksio/flow/watchdog.py similarity index 98% rename from backend/app/flow/watchdog.py rename to backend/fluksio/flow/watchdog.py index 9c2e700..25bb970 100644 --- a/backend/app/flow/watchdog.py +++ b/backend/fluksio/flow/watchdog.py @@ -15,7 +15,7 @@ import os import time from collections import deque -from app.flow.events import EventBus +from fluksio.flow.events import EventBus logger = logging.getLogger(__name__) diff --git a/backend/app/flow/worker_main.py b/backend/fluksio/flow/worker_main.py similarity index 99% rename from backend/app/flow/worker_main.py rename to backend/fluksio/flow/worker_main.py index d6f1659..e52a04a 100644 --- a/backend/app/flow/worker_main.py +++ b/backend/fluksio/flow/worker_main.py @@ -131,7 +131,7 @@ def _store_bytes(data: bytes, name: str, media_type: str) -> dict[str, Any]: A worker in the engine's own container writes the file; one on another host puts it over HTTP. Node code cannot tell the difference, which is the point — the same flow runs either place. The hashing is repeated from - ``app.flow.artifacts`` rather than imported, because nothing of the app is + ``fluksio.flow.artifacts`` rather than imported, because nothing of the app is importable here. """ digest = "sha256:" + hashlib.sha256(data).hexdigest() diff --git a/backend/app/flow/workers.py b/backend/fluksio/flow/workers.py similarity index 99% rename from backend/app/flow/workers.py rename to backend/fluksio/flow/workers.py index c2b8d8a..aac81fa 100644 --- a/backend/app/flow/workers.py +++ b/backend/fluksio/flow/workers.py @@ -26,7 +26,7 @@ from collections.abc import Callable from pathlib import Path from typing import Any -from app.flow.events import EventBus +from fluksio.flow.events import EventBus logger = logging.getLogger(__name__) diff --git a/backend/app/initial_data.py b/backend/fluksio/initial_data.py similarity index 89% rename from backend/app/initial_data.py rename to backend/fluksio/initial_data.py index d806c3d..0609d24 100644 --- a/backend/app/initial_data.py +++ b/backend/fluksio/initial_data.py @@ -2,7 +2,7 @@ import logging from sqlmodel import Session -from app.core.db import engine, init_db +from fluksio.core.db import engine, init_db logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) diff --git a/backend/app/main.py b/backend/fluksio/main.py similarity index 86% rename from backend/app/main.py rename to backend/fluksio/main.py index 9d0134e..6d0967c 100644 --- a/backend/app/main.py +++ b/backend/fluksio/main.py @@ -10,31 +10,31 @@ from fastapi.responses import JSONResponse from fastapi.routing import APIRoute from starlette.middleware.cors import CORSMiddleware -from app.api.main import api_router -from app.api.routes.alerts import read_config as read_alerts_config -from app.cloud import config as cloud_config -from app.core import security -from app.core.config import settings -from app.flow import logs, modules -from app.flow.alerts import AlertManager -from app.flow.artifacts import ArtifactStore -from app.flow.controller import FlowController -from app.flow.dashboards import DashboardStore -from app.flow.events import event_bus -from app.flow.executor import ExecutionService -from app.flow.metrics import MetricsCollector -from app.flow.nodes.http import close_shared_client -from app.flow.pipeline import ValueSource -from app.flow.plugins import load_plugins -from app.flow.queue import MemoryWorkQueue, RedisWorkQueue, WorkQueue -from app.flow.remote import RemoteWorkerHub -from app.flow.runs import RUN_STATE_TTL, RunService -from app.flow.secrets import init_secrets -from app.flow.state import MemoryState, RedisState, StateBackend -from app.flow.store import FlowStore -from app.flow.watchdog import LoopWatchdog -from app.flow.worker_main import ARTIFACT_DIR_ENV -from app.flow.workers import PythonWorkerPool +from fluksio.api.main import api_router +from fluksio.api.routes.alerts import read_config as read_alerts_config +from fluksio.cloud import config as cloud_config +from fluksio.core import security +from fluksio.core.config import settings +from fluksio.flow import logs, modules +from fluksio.flow.alerts import AlertManager +from fluksio.flow.artifacts import ArtifactStore +from fluksio.flow.controller import FlowController +from fluksio.flow.dashboards import DashboardStore +from fluksio.flow.events import event_bus +from fluksio.flow.executor import ExecutionService +from fluksio.flow.metrics import MetricsCollector +from fluksio.flow.nodes.http import close_shared_client +from fluksio.flow.pipeline import ValueSource +from fluksio.flow.plugins import load_plugins +from fluksio.flow.queue import MemoryWorkQueue, RedisWorkQueue, WorkQueue +from fluksio.flow.remote import RemoteWorkerHub +from fluksio.flow.runs import RUN_STATE_TTL, RunService +from fluksio.flow.secrets import init_secrets +from fluksio.flow.state import MemoryState, RedisState, StateBackend +from fluksio.flow.store import FlowStore +from fluksio.flow.watchdog import LoopWatchdog +from fluksio.flow.worker_main import ARTIFACT_DIR_ENV +from fluksio.flow.workers import PythonWorkerPool def custom_generate_unique_id(route: APIRoute) -> str: @@ -80,7 +80,7 @@ def _mcp_sessions() -> AbstractAsyncContextManager[None]: """The MCP session manager's run scope, or nothing when MCP is off.""" if not settings.MCP_ENABLED: return contextlib.nullcontext() - from app.mcp.server import mcp as mcp_server + from fluksio.mcp.server import mcp as mcp_server return mcp_server.session_manager.run() @@ -167,7 +167,7 @@ async def lifespan(app: FastAPI) -> AsyncIterator[None]: app.state.cloud_connector = None app.state.cloud_task = None if cloud_config.exists(): - from app.cloud.connector import CloudConnector + from fluksio.cloud.connector import CloudConnector connector = CloudConnector(app) app.state.cloud_connector = connector @@ -193,7 +193,7 @@ async def lifespan(app: FastAPI) -> AsyncIterator[None]: pool.stop() close_shared_client() if settings.MCP_ENABLED: - from app.mcp.http import aclose + from fluksio.mcp.http import aclose await aclose() @@ -273,6 +273,6 @@ def jwks() -> JSONResponse: # resource metadata that has to sit beside it, so mounting under /mcp would put # that metadata somewhere no client looks for it. if settings.MCP_ENABLED: - from app.mcp.http import build_http_app + from fluksio.mcp.http import build_http_app app.mount("/", build_http_app(app)) diff --git a/backend/app/mcp/__init__.py b/backend/fluksio/mcp/__init__.py similarity index 100% rename from backend/app/mcp/__init__.py rename to backend/fluksio/mcp/__init__.py diff --git a/backend/app/mcp/http.py b/backend/fluksio/mcp/http.py similarity index 97% rename from backend/app/mcp/http.py rename to backend/fluksio/mcp/http.py index cb61f19..4a3800e 100644 --- a/backend/app/mcp/http.py +++ b/backend/fluksio/mcp/http.py @@ -19,9 +19,9 @@ from mcp.server.transport_security import TransportSecuritySettings from pydantic import AnyHttpUrl from starlette.applications import Starlette -from app.core import security -from app.core.config import settings -from app.mcp import server +from fluksio.core import security +from fluksio.core.config import settings +from fluksio.mcp import server logger = logging.getLogger(__name__) diff --git a/backend/app/mcp/server.py b/backend/fluksio/mcp/server.py similarity index 100% rename from backend/app/mcp/server.py rename to backend/fluksio/mcp/server.py diff --git a/backend/app/models.py b/backend/fluksio/models.py similarity index 100% rename from backend/app/models.py rename to backend/fluksio/models.py diff --git a/backend/app/tests_pre_start.py b/backend/fluksio/tests_pre_start.py similarity index 96% rename from backend/app/tests_pre_start.py rename to backend/fluksio/tests_pre_start.py index 0ce6045..5994154 100644 --- a/backend/app/tests_pre_start.py +++ b/backend/fluksio/tests_pre_start.py @@ -4,7 +4,7 @@ from sqlalchemy import Engine from sqlmodel import Session, select from tenacity import after_log, before_log, retry, stop_after_attempt, wait_fixed -from app.core.db import engine +from fluksio.core.db import engine logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) diff --git a/backend/app/utils.py b/backend/fluksio/utils.py similarity index 98% rename from backend/app/utils.py rename to backend/fluksio/utils.py index ac029f6..80a79c0 100644 --- a/backend/app/utils.py +++ b/backend/fluksio/utils.py @@ -9,8 +9,8 @@ import jwt from jinja2 import Template from jwt.exceptions import InvalidTokenError -from app.core import security -from app.core.config import settings +from fluksio.core import security +from fluksio.core.config import settings logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) diff --git a/backend/app/worker/__init__.py b/backend/fluksio/worker/__init__.py similarity index 100% rename from backend/app/worker/__init__.py rename to backend/fluksio/worker/__init__.py diff --git a/backend/app/worker/fluksio_worker.py b/backend/fluksio/worker/fluksio_worker.py similarity index 100% rename from backend/app/worker/fluksio_worker.py rename to backend/fluksio/worker/fluksio_worker.py diff --git a/backend/pyproject.toml b/backend/pyproject.toml index eb72a9d..dad5a35 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -1,7 +1,7 @@ [project] -name = "app" +name = "fluksio" version = "0.1.0" -description = "" +description = "Node-based automation engine: flows, dashboards, batch runs" # Capped below 3.14: the MCP SDK wants a newer starlette there than the # pinned sentry-sdk allows. Lift it when sentry-sdk moves to 2.x. requires-python = ">=3.10,<3.14" @@ -39,6 +39,9 @@ dev = [ "coverage<8.0.0,>=7.4.3", ] +[tool.hatch.build.targets.wheel] +packages = ["fluksio"] + [build-system] requires = ["hatchling"] build-backend = "hatchling.build" @@ -47,7 +50,7 @@ build-backend = "hatchling.build" strict = true exclude = ["venv", ".venv", "alembic"] # influxdb_client's Point builder carries no annotations; calling it is not a -# reason to stop checking the caller (app/flow/nodes/influx.py). +# reason to stop checking the caller (fluksio/flow/nodes/influx.py). untyped_calls_exclude = ["influxdb_client"] # Every module is checked strictly. influxdb_client is typed but re-exports its @@ -84,24 +87,24 @@ ignore = [ [tool.ruff.lint.per-file-ignores] # Node functions take `params` whether or not they use it — that is the # contract the engine calls them with. -"app/flow/nodes.py" = ["ARG001", "ARG002"] +"fluksio/flow/nodes.py" = ["ARG001", "ARG002"] "tests/flow/*" = ["ARG001"] # Printing is what this one is about: node code is user code, and `print` is # how it says things. "tests/flow/test_logs.py" = ["ARG001", "T201"] # This one takes its own directory off sys.path before the rest of its imports # run, which is the whole point of doing it there. -"app/flow/worker_main.py" = ["E402"] +"fluksio/flow/worker_main.py" = ["E402"] # A standalone script copied onto another machine: it has no logger configured # before it tells you the one dependency it is missing. -"app/worker/fluksio_worker.py" = ["T201"] +"fluksio/worker/fluksio_worker.py" = ["T201"] [tool.ruff.lint.pyupgrade] # Preserve types, even if a file imports `from __future__ import annotations`. keep-runtime-typing = true [tool.coverage.run] -source = ["app"] +source = ["fluksio"] dynamic_context = "test_function" [tool.coverage.report] diff --git a/backend/scripts/bench_startup.py b/backend/scripts/bench_startup.py index e1b1ec3..d2f95bf 100644 --- a/backend/scripts/bench_startup.py +++ b/backend/scripts/bench_startup.py @@ -31,7 +31,7 @@ from typing import Any sys.path.insert(0, str(__import__("pathlib").Path(__file__).resolve().parents[1])) -from app.core.config import settings # noqa: E402 +from fluksio.core.config import settings # noqa: E402 log = logging.getLogger("bench") diff --git a/backend/scripts/format.sh b/backend/scripts/format.sh index 7be2f81..f01bdd9 100755 --- a/backend/scripts/format.sh +++ b/backend/scripts/format.sh @@ -1,5 +1,5 @@ #!/bin/sh -e set -x -ruff check app scripts --fix -ruff format app scripts +ruff check fluksio scripts --fix +ruff format fluksio scripts diff --git a/backend/scripts/lint.sh b/backend/scripts/lint.sh index b3b2b4e..e350f78 100644 --- a/backend/scripts/lint.sh +++ b/backend/scripts/lint.sh @@ -3,6 +3,6 @@ set -e set -x -mypy app -ruff check app -ruff format app --check +mypy fluksio +ruff check fluksio +ruff format fluksio --check diff --git a/backend/scripts/prestart.sh b/backend/scripts/prestart.sh index 1b395d5..75770d6 100644 --- a/backend/scripts/prestart.sh +++ b/backend/scripts/prestart.sh @@ -4,10 +4,10 @@ set -e set -x # Let the DB start -python app/backend_pre_start.py +python -m fluksio.backend_pre_start # Run migrations alembic upgrade head # Create initial data in DB -python app/initial_data.py +python -m fluksio.initial_data diff --git a/backend/scripts/rotate_secret_key.py b/backend/scripts/rotate_secret_key.py index 05b70d0..f4cc2e1 100644 --- a/backend/scripts/rotate_secret_key.py +++ b/backend/scripts/rotate_secret_key.py @@ -10,8 +10,8 @@ script the previous one: import logging import sys -from app.core.config import settings -from app.flow.secrets import SecretsStore, SecretsUnreadable +from fluksio.core.config import settings +from fluksio.flow.secrets import SecretsStore, SecretsUnreadable logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) diff --git a/backend/scripts/soak.py b/backend/scripts/soak.py index d093d29..45acc65 100644 --- a/backend/scripts/soak.py +++ b/backend/scripts/soak.py @@ -35,8 +35,8 @@ from typing import Any import httpx -from app.core.config import settings -from app.flow.metrics import FLUSH_INTERVAL_S +from fluksio.core.config import settings +from fluksio.flow.metrics import FLUSH_INTERVAL_S log = logging.getLogger("soak") diff --git a/backend/scripts/tests-start.sh b/backend/scripts/tests-start.sh index 89dcb0d..55f241a 100644 --- a/backend/scripts/tests-start.sh +++ b/backend/scripts/tests-start.sh @@ -2,6 +2,6 @@ set -e set -x -python app/tests_pre_start.py +python -m fluksio.tests_pre_start bash scripts/test.sh "$@" diff --git a/backend/tests/api/routes/test_alerts.py b/backend/tests/api/routes/test_alerts.py index b41df61..6e749d7 100644 --- a/backend/tests/api/routes/test_alerts.py +++ b/backend/tests/api/routes/test_alerts.py @@ -5,8 +5,8 @@ from typing import Any import pytest from fastapi.testclient import TestClient -from app.core.config import settings -from app.flow.alerts import AlertsConfig, Channel +from fluksio.core.config import settings +from fluksio.flow.alerts import AlertsConfig, Channel PREFIX = f"{settings.API_V1_STR}/alerts" diff --git a/backend/tests/api/routes/test_flows.py b/backend/tests/api/routes/test_flows.py index eb77632..179ed09 100644 --- a/backend/tests/api/routes/test_flows.py +++ b/backend/tests/api/routes/test_flows.py @@ -4,8 +4,8 @@ from fastapi.testclient import TestClient from sqlalchemy import func from sqlmodel import Session, select -from app.core.config import settings -from app.models import Run, RunArtifact, RunMetric, RunNode +from fluksio.core.config import settings +from fluksio.models import Run, RunArtifact, RunMetric, RunNode PREFIX = f"{settings.API_V1_STR}/flows" diff --git a/backend/tests/api/routes/test_login.py b/backend/tests/api/routes/test_login.py index 96677a2..cc6290c 100644 --- a/backend/tests/api/routes/test_login.py +++ b/backend/tests/api/routes/test_login.py @@ -4,11 +4,11 @@ from fastapi.testclient import TestClient from pwdlib.hashers.bcrypt import BcryptHasher from sqlmodel import Session -from app.core.config import settings -from app.core.security import get_password_hash, verify_password -from app.crud import create_user -from app.models import User, UserCreate -from app.utils import generate_password_reset_token +from fluksio.core.config import settings +from fluksio.core.security import get_password_hash, verify_password +from fluksio.crud import create_user +from fluksio.models import User, UserCreate +from fluksio.utils import generate_password_reset_token from tests.utils.user import user_authentication_headers from tests.utils.utils import random_email, random_lower_string @@ -50,8 +50,8 @@ def test_recovery_password( client: TestClient, normal_user_token_headers: dict[str, str] ) -> None: with ( - patch("app.core.config.settings.SMTP_HOST", "smtp.example.com"), - patch("app.core.config.settings.SMTP_USER", "admin@example.com"), + patch("fluksio.core.config.settings.SMTP_HOST", "smtp.example.com"), + patch("fluksio.core.config.settings.SMTP_USER", "admin@example.com"), ): email = "test@example.com" r = client.post( diff --git a/backend/tests/api/routes/test_oauth.py b/backend/tests/api/routes/test_oauth.py index 9051576..10d6f3c 100644 --- a/backend/tests/api/routes/test_oauth.py +++ b/backend/tests/api/routes/test_oauth.py @@ -8,7 +8,7 @@ from urllib.parse import parse_qs, urlparse import pytest from fastapi.testclient import TestClient -from app.core.config import settings +from fluksio.core.config import settings PREFIX = f"{settings.API_V1_STR}/oauth" REDIRECT = "http://127.0.0.1:41234/callback" diff --git a/backend/tests/api/routes/test_observability.py b/backend/tests/api/routes/test_observability.py index ec03183..fc48e23 100644 --- a/backend/tests/api/routes/test_observability.py +++ b/backend/tests/api/routes/test_observability.py @@ -3,8 +3,8 @@ from datetime import datetime, timedelta, timezone from fastapi.testclient import TestClient from sqlmodel import Session -from app.core.config import settings -from app.models import EngineEvent, FlowRun, MetricBucket +from fluksio.core.config import settings +from fluksio.models import EngineEvent, FlowRun, MetricBucket PREFIX = f"{settings.API_V1_STR}/observability" FLOW = "observability-test" @@ -90,7 +90,7 @@ def test_a_flow_that_cannot_run_makes_the_summary_degraded( knows is what this reports — and reporting it is the whole point: a flow with a dependency loop cannot run, and the screen used to say "ok". """ - from app.flow.pipeline import ValidationIssue + from fluksio.flow.pipeline import ValidationIssue controller = client.app.state.flow_controller before = controller.issues diff --git a/backend/tests/api/routes/test_panels.py b/backend/tests/api/routes/test_panels.py index b73f908..74df6a6 100644 --- a/backend/tests/api/routes/test_panels.py +++ b/backend/tests/api/routes/test_panels.py @@ -2,8 +2,8 @@ from fastapi.testclient import TestClient -from app.cloud import config as cloud_config -from app.core.config import settings +from fluksio.cloud import config as cloud_config +from fluksio.core.config import settings PREFIX = f"{settings.API_V1_STR}/panels" DASHBOARDS = f"{settings.API_V1_STR}/dashboards" @@ -276,7 +276,7 @@ def test_a_remote_device_is_paired_at_the_portal( """ import httpx - from app.api.routes import panels as panels_route + from fluksio.api.routes import panels as panels_route calls: list[dict[str, object]] = [] @@ -378,7 +378,7 @@ def test_a_panels_socket_carries_only_what_it_draws( applied to what goes out: no node status, no logs, no shape of the graph, and no value belonging to a dashboard this panel does not show. """ - from app.api.routes.flows import ( + from fluksio.api.routes.flows import ( event_for_panel, panel_scope, snapshot_payload, diff --git a/backend/tests/api/routes/test_private.py b/backend/tests/api/routes/test_private.py index c49ea0d..179dee9 100644 --- a/backend/tests/api/routes/test_private.py +++ b/backend/tests/api/routes/test_private.py @@ -2,8 +2,8 @@ import pytest from fastapi.testclient import TestClient from sqlmodel import Session, select -from app.core.config import settings -from app.models import User +from fluksio.core.config import settings +from fluksio.models import User def test_create_user(client: TestClient, db: Session) -> None: diff --git a/backend/tests/api/routes/test_session.py b/backend/tests/api/routes/test_session.py index 9d482bf..f59d211 100644 --- a/backend/tests/api/routes/test_session.py +++ b/backend/tests/api/routes/test_session.py @@ -10,8 +10,8 @@ from datetime import timedelta from fastapi.testclient import TestClient -from app.core import security -from app.core.config import settings +from fluksio.core import security +from fluksio.core.config import settings PROTECTED = [ f"{settings.API_V1_STR}/users/me", diff --git a/backend/tests/api/routes/test_users.py b/backend/tests/api/routes/test_users.py index 9c4cdd5..57cc8da 100644 --- a/backend/tests/api/routes/test_users.py +++ b/backend/tests/api/routes/test_users.py @@ -4,10 +4,10 @@ from unittest.mock import patch from fastapi.testclient import TestClient from sqlmodel import Session, select -from app import crud -from app.core.config import settings -from app.core.security import verify_password -from app.models import User, UserCreate +from fluksio import crud +from fluksio.core.config import settings +from fluksio.core.security import verify_password +from fluksio.models import User, UserCreate from tests.utils.user import create_random_user from tests.utils.utils import random_email, random_lower_string @@ -38,9 +38,9 @@ def test_create_user_new_email( client: TestClient, superuser_token_headers: dict[str, str], db: Session ) -> None: with ( - patch("app.utils.send_email", return_value=None), - patch("app.core.config.settings.SMTP_HOST", "smtp.example.com"), - patch("app.core.config.settings.SMTP_USER", "admin@example.com"), + patch("fluksio.utils.send_email", return_value=None), + patch("fluksio.core.config.settings.SMTP_HOST", "smtp.example.com"), + patch("fluksio.core.config.settings.SMTP_USER", "admin@example.com"), ): username = random_email() password = random_lower_string() diff --git a/backend/tests/conftest.py b/backend/tests/conftest.py index 748fb1c..0ee26cf 100644 --- a/backend/tests/conftest.py +++ b/backend/tests/conftest.py @@ -10,11 +10,11 @@ from sqlalchemy import create_engine, text from sqlalchemy.engine import make_url from sqlmodel import Session, SQLModel, select -from app.cloud import config as cloud_config -from app.core.config import settings -from app.core.db import engine, init_db -from app.main import app -from app.models import User +from fluksio.cloud import config as cloud_config +from fluksio.core.config import settings +from fluksio.core.db import engine, init_db +from fluksio.main import app +from fluksio.models import User from tests.utils.portal import INSTALLATION_ID, ISSUER, jwks from tests.utils.user import authentication_token_from_email from tests.utils.utils import get_superuser_token_headers diff --git a/backend/tests/crud/test_user.py b/backend/tests/crud/test_user.py index 3db77ef..c292ebe 100644 --- a/backend/tests/crud/test_user.py +++ b/backend/tests/crud/test_user.py @@ -2,9 +2,9 @@ from fastapi.encoders import jsonable_encoder from pwdlib.hashers.bcrypt import BcryptHasher from sqlmodel import Session -from app import crud -from app.core.security import verify_password -from app.models import User, UserCreate, UserUpdate +from fluksio import crud +from fluksio.core.security import verify_password +from fluksio.models import User, UserCreate, UserUpdate from tests.utils.utils import random_email, random_lower_string diff --git a/backend/tests/flow/test_alerts.py b/backend/tests/flow/test_alerts.py index 2197073..a310b33 100644 --- a/backend/tests/flow/test_alerts.py +++ b/backend/tests/flow/test_alerts.py @@ -6,7 +6,7 @@ from pathlib import Path import pytest -from app.flow.alerts import ( +from fluksio.flow.alerts import ( ALERTING_EVENTS, FLAP_THRESHOLD, RATE_LIMIT, @@ -17,7 +17,7 @@ from app.flow.alerts import ( Rule, describe, ) -from app.flow.events import EventBus +from fluksio.flow.events import EventBus class Clock: diff --git a/backend/tests/flow/test_brain_graph.py b/backend/tests/flow/test_brain_graph.py index fbd81d0..3be0868 100644 --- a/backend/tests/flow/test_brain_graph.py +++ b/backend/tests/flow/test_brain_graph.py @@ -9,12 +9,12 @@ from pathlib import Path import pytest -from app.flow.controller import FlowController -from app.flow.messages import DType, MessageSpec -from app.flow.nodes import MqttNode -from app.flow.pipeline import ValidationIssue -from app.flow.schemas import FlowDef, NodeDef -from app.flow.store import FlowStore +from fluksio.flow.controller import FlowController +from fluksio.flow.messages import DType, MessageSpec +from fluksio.flow.nodes import MqttNode +from fluksio.flow.pipeline import ValidationIssue +from fluksio.flow.schemas import FlowDef, NodeDef +from fluksio.flow.store import FlowStore BROKER = {"broker_host": "mosquitto", "broker_port": 1883, "topic": "sensors/temp"} diff --git a/backend/tests/flow/test_connector.py b/backend/tests/flow/test_connector.py index 38822df..af385ca 100644 --- a/backend/tests/flow/test_connector.py +++ b/backend/tests/flow/test_connector.py @@ -3,12 +3,12 @@ import asyncio from typing import Any -from app.flow.connector import CONTRACT_VERSION, ConnectorNode -from app.flow.controller import NODE_TYPES -from app.flow.messages import DType, MessageSpec -from app.flow.nodes import Node -from app.flow.pipeline import Pipeline -from app.flow.plugins import load_plugins +from fluksio.flow.connector import CONTRACT_VERSION, ConnectorNode +from fluksio.flow.controller import NODE_TYPES +from fluksio.flow.messages import DType, MessageSpec +from fluksio.flow.nodes import Node +from fluksio.flow.pipeline import Pipeline +from fluksio.flow.plugins import load_plugins class Sensor(ConnectorNode): @@ -146,7 +146,7 @@ def test_a_connector_is_discovered_from_its_entry_point(monkeypatch): def load(self): return Sensor - monkeypatch.setattr("app.flow.plugins.entry_points", lambda group: [FakeEntry()]) + monkeypatch.setattr("fluksio.flow.plugins.entry_points", lambda group: [FakeEntry()]) try: assert load_plugins() == ["test_sensor"] assert NODE_TYPES["test_sensor"].plugin == "fluksio-connector-test 0.1.0" @@ -166,7 +166,7 @@ def test_a_connector_written_for_another_contract_is_refused(monkeypatch): def load(self): return Outdated - monkeypatch.setattr("app.flow.plugins.entry_points", lambda group: [FakeEntry()]) + monkeypatch.setattr("fluksio.flow.plugins.entry_points", lambda group: [FakeEntry()]) assert load_plugins() == [] assert "outdated" not in NODE_TYPES @@ -179,6 +179,6 @@ def test_a_connector_may_not_take_over_a_built_in_type(monkeypatch): def load(self): # pragma: no cover - never reached raise AssertionError("should not be loaded") - monkeypatch.setattr("app.flow.plugins.entry_points", lambda group: [FakeEntry()]) + monkeypatch.setattr("fluksio.flow.plugins.entry_points", lambda group: [FakeEntry()]) assert load_plugins() == [] assert NODE_TYPES["mqtt"].plugin is None diff --git a/backend/tests/flow/test_dashboards.py b/backend/tests/flow/test_dashboards.py index 5d51cd4..60b8b6d 100644 --- a/backend/tests/flow/test_dashboards.py +++ b/backend/tests/flow/test_dashboards.py @@ -2,7 +2,7 @@ import pytest -from app.flow.dashboards import ( +from fluksio.flow.dashboards import ( DashboardDef, DashboardNotFound, DashboardStore, @@ -11,11 +11,11 @@ from app.flow.dashboards import ( WidgetDef, default_dashboard, ) -from app.flow.messages import DType, MessageSpec -from app.flow.nodes import Node -from app.flow.pipeline import Pipeline -from app.flow.state import MemoryState -from app.flow.store import FlowStore, StaleVersion +from fluksio.flow.messages import DType, MessageSpec +from fluksio.flow.nodes import Node +from fluksio.flow.pipeline import Pipeline +from fluksio.flow.state import MemoryState +from fluksio.flow.store import FlowStore, StaleVersion @pytest.fixture diff --git a/backend/tests/flow/test_discretization.py b/backend/tests/flow/test_discretization.py index d61366c..8670179 100644 --- a/backend/tests/flow/test_discretization.py +++ b/backend/tests/flow/test_discretization.py @@ -2,11 +2,11 @@ import time -from app.flow.executor import ExecutionService -from app.flow.messages import DType, MessageSpec -from app.flow.nodes import Node -from app.flow.pipeline import Pipeline -from app.flow.queue import MemoryWorkQueue +from fluksio.flow.executor import ExecutionService +from fluksio.flow.messages import DType, MessageSpec +from fluksio.flow.nodes import Node +from fluksio.flow.pipeline import Pipeline +from fluksio.flow.queue import MemoryWorkQueue # Short enough to wait out in a test, long enough not to race the engine. WINDOW = 0.05 diff --git a/backend/tests/flow/test_drafts.py b/backend/tests/flow/test_drafts.py index 32e3c11..2dbafde 100644 --- a/backend/tests/flow/test_drafts.py +++ b/backend/tests/flow/test_drafts.py @@ -4,9 +4,9 @@ from pathlib import Path import pytest -from app.flow.messages import MessageSpec -from app.flow.schemas import FlowDef, NodeDef -from app.flow.store import FlowStore, StaleVersion +from fluksio.flow.messages import MessageSpec +from fluksio.flow.schemas import FlowDef, NodeDef +from fluksio.flow.store import FlowStore, StaleVersion SOURCE = "def process():\n return {}\n" EDITED = "def process():\n return {'temp': 1}\n" diff --git a/backend/tests/flow/test_emit_counts.py b/backend/tests/flow/test_emit_counts.py index 20da311..b758983 100644 --- a/backend/tests/flow/test_emit_counts.py +++ b/backend/tests/flow/test_emit_counts.py @@ -9,13 +9,13 @@ from pathlib import Path import pytest -from app.api.routes.flows import snapshot_payload -from app.flow.controller import FlowController -from app.flow.events import EventBus, event_bus -from app.flow.messages import DType, MessageSpec -from app.flow.nodes import Node -from app.flow.pipeline import Pipeline -from app.flow.store import FlowStore +from fluksio.api.routes.flows import snapshot_payload +from fluksio.flow.controller import FlowController +from fluksio.flow.events import EventBus, event_bus +from fluksio.flow.messages import DType, MessageSpec +from fluksio.flow.nodes import Node +from fluksio.flow.pipeline import Pipeline +from fluksio.flow.store import FlowStore def make_node(node_id: str, f, provides=()) -> Node: diff --git a/backend/tests/flow/test_history.py b/backend/tests/flow/test_history.py index 8650dbb..9b18af0 100644 --- a/backend/tests/flow/test_history.py +++ b/backend/tests/flow/test_history.py @@ -1,9 +1,9 @@ """Message history: the series a node panel draws as a sparkline.""" -from app.flow.messages import MessageSpec -from app.flow.nodes import Node -from app.flow.pipeline import Pipeline -from app.flow.state import HISTORY_LIMIT, MemoryState +from fluksio.flow.messages import MessageSpec +from fluksio.flow.nodes import Node +from fluksio.flow.pipeline import Pipeline +from fluksio.flow.state import HISTORY_LIMIT, MemoryState def emitter(values) -> Node: diff --git a/backend/tests/flow/test_http_hook_secret.py b/backend/tests/flow/test_http_hook_secret.py index a33103b..2dba63a 100644 --- a/backend/tests/flow/test_http_hook_secret.py +++ b/backend/tests/flow/test_http_hook_secret.py @@ -11,13 +11,13 @@ import pytest from fastapi import FastAPI from fastapi.testclient import TestClient -from app.flow.controller import HOOK_PREFIX, FlowController -from app.flow.messages import DType, MessageSpec -from app.flow.nodes import HttpNode -from app.flow.pipeline import Pipeline -from app.flow.schemas import FlowDef, NodeDef -from app.flow.secrets import init_secrets, resolve_params -from app.flow.store import FlowStore +from fluksio.flow.controller import HOOK_PREFIX, FlowController +from fluksio.flow.messages import DType, MessageSpec +from fluksio.flow.nodes import HttpNode +from fluksio.flow.pipeline import Pipeline +from fluksio.flow.schemas import FlowDef, NodeDef +from fluksio.flow.secrets import init_secrets, resolve_params +from fluksio.flow.store import FlowStore SECRET = "d3vice-key" HOOK = f"{HOOK_PREFIX}/house/sensor" diff --git a/backend/tests/flow/test_library.py b/backend/tests/flow/test_library.py index 30a71b3..03eb208 100644 --- a/backend/tests/flow/test_library.py +++ b/backend/tests/flow/test_library.py @@ -4,9 +4,9 @@ from pathlib import Path import pytest -from app.flow.messages import MessageSpec -from app.flow.schemas import FlowDef, NodeDef -from app.flow.store import FlowStore, LibExists, LibNotFound +from fluksio.flow.messages import MessageSpec +from fluksio.flow.schemas import FlowDef, NodeDef +from fluksio.flow.store import FlowStore, LibExists, LibNotFound SOURCE = "def process():\n return {'temp': 1}\n" diff --git a/backend/tests/flow/test_logs.py b/backend/tests/flow/test_logs.py index db1e011..1611919 100644 --- a/backend/tests/flow/test_logs.py +++ b/backend/tests/flow/test_logs.py @@ -3,11 +3,11 @@ import sys from typing import Any -from app.flow import logs -from app.flow.controller import with_settings -from app.flow.messages import DType, MessageSpec -from app.flow.nodes import Node -from app.flow.pipeline import Pipeline +from fluksio.flow import logs +from fluksio.flow.controller import with_settings +from fluksio.flow.messages import DType, MessageSpec +from fluksio.flow.nodes import Node +from fluksio.flow.pipeline import Pipeline def run_with_capture(nodes: list[Node], bus: "RecordingBus") -> None: diff --git a/backend/tests/flow/test_messages.py b/backend/tests/flow/test_messages.py index d3aab24..a366f13 100644 --- a/backend/tests/flow/test_messages.py +++ b/backend/tests/flow/test_messages.py @@ -2,7 +2,7 @@ import json import pytest -from app.flow.messages import DType, MessageSpec, flow_of, qualify +from fluksio.flow.messages import DType, MessageSpec, flow_of, qualify def test_port_defaults_to_last_name_segment(): diff --git a/backend/tests/flow/test_modules.py b/backend/tests/flow/test_modules.py index 1b0a139..6c7fdda 100644 --- a/backend/tests/flow/test_modules.py +++ b/backend/tests/flow/test_modules.py @@ -6,8 +6,8 @@ from pathlib import Path import pytest -from app.flow import modules -from app.flow.store import FlowStore +from fluksio.flow import modules +from fluksio.flow.store import FlowStore pytestmark = pytest.mark.skipif( shutil.which("uv") is None, reason="module management needs uv" diff --git a/backend/tests/flow/test_node_settings.py b/backend/tests/flow/test_node_settings.py index 80f4ded..2dabc91 100644 --- a/backend/tests/flow/test_node_settings.py +++ b/backend/tests/flow/test_node_settings.py @@ -9,12 +9,12 @@ from pathlib import Path import pytest -from app.flow.controller import FlowController -from app.flow.messages import DType, MessageSpec -from app.flow.pipeline import Pipeline -from app.flow.schemas import FlowDef, NodeDef -from app.flow.state import MemoryState -from app.flow.store import FlowStore +from fluksio.flow.controller import FlowController +from fluksio.flow.messages import DType, MessageSpec +from fluksio.flow.pipeline import Pipeline +from fluksio.flow.schemas import FlowDef, NodeDef +from fluksio.flow.state import MemoryState +from fluksio.flow.store import FlowStore SOURCE = "def process(reading, factor):\n return {'scaled': reading * factor}\n" diff --git a/backend/tests/flow/test_node_types.py b/backend/tests/flow/test_node_types.py index 789ffc5..7cc1dc1 100644 --- a/backend/tests/flow/test_node_types.py +++ b/backend/tests/flow/test_node_types.py @@ -6,8 +6,8 @@ only fails when someone places that node. Building one of each catches it. import pytest -from app.flow.controller import NODE_TYPES, node_type_info -from app.flow.messages import DType, MessageSpec +from fluksio.flow.controller import NODE_TYPES, node_type_info +from fluksio.flow.messages import DType, MessageSpec # Enough configuration for each type to construct; the ports decide the # direction, so every type gets both an input and an output where it allows it. @@ -160,7 +160,7 @@ def test_every_offered_type_has_a_fixture(): def test_a_flux_request_is_run_rather_than_written(monkeypatch): """A query passing through is not a point to store.""" - from app.flow.nodes import InfluxDbNode + from fluksio.flow.nodes import InfluxDbNode node = InfluxDbNode( requires=[ @@ -199,8 +199,8 @@ def test_a_flux_request_is_run_rather_than_written(monkeypatch): def test_a_falsy_return_is_a_mistake_not_silence(): """Only None means "nothing to publish".""" - from app.flow.nodes import Node - from app.flow.nodes.base import NodeOutputError + from fluksio.flow.nodes import Node + from fluksio.flow.nodes.base import NodeOutputError def make(retval): return Node( diff --git a/backend/tests/flow/test_pipeline.py b/backend/tests/flow/test_pipeline.py index d39f35a..952a09d 100644 --- a/backend/tests/flow/test_pipeline.py +++ b/backend/tests/flow/test_pipeline.py @@ -1,9 +1,9 @@ """The wiring fundamentals: name binding, fan-in, namespaces, validation.""" -from app.flow.events import EventBus -from app.flow.messages import DType, MessageSpec -from app.flow.nodes import Node -from app.flow.pipeline import Pipeline +from fluksio.flow.events import EventBus +from fluksio.flow.messages import DType, MessageSpec +from fluksio.flow.nodes import Node +from fluksio.flow.pipeline import Pipeline def spec(name: str, dtype: DType = DType.FLOAT, port: str = "") -> MessageSpec: diff --git a/backend/tests/flow/test_provenance.py b/backend/tests/flow/test_provenance.py index 15d4207..4a2ccfc 100644 --- a/backend/tests/flow/test_provenance.py +++ b/backend/tests/flow/test_provenance.py @@ -5,19 +5,19 @@ actually published, it pulses all of them — and when the cause is a dashboard control or another flow, it pulses a node that did nothing at all. """ -from app.flow.dashboards import ( +from fluksio.flow.dashboards import ( DashboardDef, DashboardStore, PageDef, SectionDef, WidgetDef, ) -from app.flow.events import EventBus -from app.flow.messages import DType, MessageSpec -from app.flow.nodes import Node -from app.flow.pipeline import Pipeline, ValueSource -from app.flow.state import MemoryState -from app.flow.store import FlowStore +from fluksio.flow.events import EventBus +from fluksio.flow.messages import DType, MessageSpec +from fluksio.flow.nodes import Node +from fluksio.flow.pipeline import Pipeline, ValueSource +from fluksio.flow.state import MemoryState +from fluksio.flow.store import FlowStore def collect(bus: EventBus) -> list[dict]: diff --git a/backend/tests/flow/test_queue.py b/backend/tests/flow/test_queue.py index 4f61d56..b157698 100644 --- a/backend/tests/flow/test_queue.py +++ b/backend/tests/flow/test_queue.py @@ -2,12 +2,12 @@ import time -from app.flow.executor import ExecutionService -from app.flow.messages import DType, MessageSpec -from app.flow.nodes import Node -from app.flow.pipeline import Pipeline -from app.flow.queue import MemoryWorkQueue, WorkItem -from app.flow.state import MemoryState +from fluksio.flow.executor import ExecutionService +from fluksio.flow.messages import DType, MessageSpec +from fluksio.flow.nodes import Node +from fluksio.flow.pipeline import Pipeline +from fluksio.flow.queue import MemoryWorkQueue, WorkItem +from fluksio.flow.state import MemoryState def test_items_come_back_in_the_order_they_went_in(): diff --git a/backend/tests/flow/test_remote.py b/backend/tests/flow/test_remote.py index 3b27743..8a09997 100644 --- a/backend/tests/flow/test_remote.py +++ b/backend/tests/flow/test_remote.py @@ -13,8 +13,8 @@ from collections.abc import Iterator import pytest -from app.flow.remote import NoWorker, RemoteWorker, RemoteWorkerHub -from app.flow.workers import NodeTimeout, RemoteError +from fluksio.flow.remote import NoWorker, RemoteWorker, RemoteWorkerHub +from fluksio.flow.workers import NodeTimeout, RemoteError @pytest.fixture diff --git a/backend/tests/flow/test_runs.py b/backend/tests/flow/test_runs.py index 92dd664..5892cc6 100644 --- a/backend/tests/flow/test_runs.py +++ b/backend/tests/flow/test_runs.py @@ -8,10 +8,10 @@ parameters a caller sends are refused before anything runs if they are wrong. import pytest -from app.flow.messages import DType, MessageSpec -from app.flow.nodes import Node -from app.flow.pipeline import NodeOutcome, Pipeline -from app.flow.runs import ( +from fluksio.flow.messages import DType, MessageSpec +from fluksio.flow.nodes import Node +from fluksio.flow.pipeline import NodeOutcome, Pipeline +from fluksio.flow.runs import ( MetricSink, RunRejected, batch_issues, @@ -20,8 +20,8 @@ from app.flow.runs import ( required_labels, seed_values, ) -from app.flow.schemas import FlowDef, FlowInput, NodeDef -from app.flow.state import MemoryState +from fluksio.flow.schemas import FlowDef, FlowInput, NodeDef +from fluksio.flow.state import MemoryState def spec(name: str, dtype: DType = DType.FLOAT, **kwargs) -> MessageSpec: diff --git a/backend/tests/flow/test_runtime_control.py b/backend/tests/flow/test_runtime_control.py index 6014509..73c4bce 100644 --- a/backend/tests/flow/test_runtime_control.py +++ b/backend/tests/flow/test_runtime_control.py @@ -6,13 +6,13 @@ from typing import Any from fastapi import FastAPI from fastapi.testclient import TestClient -from app.api.deps import get_current_user -from app.api.routes.flows import router -from app.flow.messages import DType, MessageSpec -from app.flow.nodes import Node -from app.flow.pipeline import Pipeline -from app.flow.schemas import FlowDef -from app.flow.store import FlowStore +from fluksio.api.deps import get_current_user +from fluksio.api.routes.flows import router +from fluksio.flow.messages import DType, MessageSpec +from fluksio.flow.nodes import Node +from fluksio.flow.pipeline import Pipeline +from fluksio.flow.schemas import FlowDef +from fluksio.flow.store import FlowStore def spec(name: str) -> MessageSpec: diff --git a/backend/tests/flow/test_senders.py b/backend/tests/flow/test_senders.py index ce2c40a..0f1ee11 100644 --- a/backend/tests/flow/test_senders.py +++ b/backend/tests/flow/test_senders.py @@ -2,9 +2,9 @@ import asyncio -from app.flow.messages import DType, MessageSpec -from app.flow.nodes import MqttNode -from app.flow.nodes.http import close_shared_client, shared_client +from fluksio.flow.messages import DType, MessageSpec +from fluksio.flow.nodes import MqttNode +from fluksio.flow.nodes.http import close_shared_client, shared_client def test_http_senders_share_one_pooled_client(): diff --git a/backend/tests/flow/test_state_edges.py b/backend/tests/flow/test_state_edges.py index 6307aca..c1094b5 100644 --- a/backend/tests/flow/test_state_edges.py +++ b/backend/tests/flow/test_state_edges.py @@ -5,10 +5,10 @@ running value in a message it both reads and writes. That is the sanctioned shape, and these tests pin what it means. """ -from app.flow.messages import DType, MessageSpec -from app.flow.nodes import Node -from app.flow.pipeline import Pipeline -from app.flow.state import MemoryState +from fluksio.flow.messages import DType, MessageSpec +from fluksio.flow.nodes import Node +from fluksio.flow.pipeline import Pipeline +from fluksio.flow.state import MemoryState def counter() -> Node: diff --git a/backend/tests/flow/test_store.py b/backend/tests/flow/test_store.py index f3883c9..5af699e 100644 --- a/backend/tests/flow/test_store.py +++ b/backend/tests/flow/test_store.py @@ -3,9 +3,9 @@ from pathlib import Path import pytest -from app.flow.messages import MessageSpec -from app.flow.schemas import FlowDef, NodeDef -from app.flow.store import FlowExists, FlowNotFound, FlowStore +from fluksio.flow.messages import MessageSpec +from fluksio.flow.schemas import FlowDef, NodeDef +from fluksio.flow.store import FlowExists, FlowNotFound, FlowStore @pytest.fixture diff --git a/backend/tests/flow/test_supervision.py b/backend/tests/flow/test_supervision.py index 7aa9550..c0b9e00 100644 --- a/backend/tests/flow/test_supervision.py +++ b/backend/tests/flow/test_supervision.py @@ -4,9 +4,9 @@ import asyncio import pytest -from app.flow import supervision -from app.flow.events import EventBus -from app.flow.supervision import FAILURE_BUDGET, Supervisor +from fluksio.flow import supervision +from fluksio.flow.events import EventBus +from fluksio.flow.supervision import FAILURE_BUDGET, Supervisor @pytest.fixture(autouse=True) diff --git a/backend/tests/flow/test_synchronous_nodes.py b/backend/tests/flow/test_synchronous_nodes.py index f0dfd55..403d126 100644 --- a/backend/tests/flow/test_synchronous_nodes.py +++ b/backend/tests/flow/test_synchronous_nodes.py @@ -6,10 +6,10 @@ atomic operations, so those are covered here too. from concurrent.futures import ThreadPoolExecutor -from app.flow.messages import DType, MessageSpec -from app.flow.nodes import Node -from app.flow.pipeline import Pipeline -from app.flow.state import MemoryState +from fluksio.flow.messages import DType, MessageSpec +from fluksio.flow.nodes import Node +from fluksio.flow.pipeline import Pipeline +from fluksio.flow.state import MemoryState def spec(name: str) -> MessageSpec: diff --git a/backend/tests/flow/test_vocabulary.py b/backend/tests/flow/test_vocabulary.py index 173b829..e36f352 100644 --- a/backend/tests/flow/test_vocabulary.py +++ b/backend/tests/flow/test_vocabulary.py @@ -4,10 +4,17 @@ These are the shapes a Node-RED installation is mostly made of, so their behaviour is worth pinning rather than just their construction. """ -from app.flow.messages import DType, MessageSpec -from app.flow.nodes import ChangeNode, ExecNode, FileNode, JoinNode, RbeNode, SwitchNode -from app.flow.pipeline import Pipeline -from app.flow.state import MemoryState +from fluksio.flow.messages import DType, MessageSpec +from fluksio.flow.nodes import ( + ChangeNode, + ExecNode, + FileNode, + JoinNode, + RbeNode, + SwitchNode, +) +from fluksio.flow.pipeline import Pipeline +from fluksio.flow.state import MemoryState def spec(name: str, dtype: DType = DType.FLOAT) -> MessageSpec: @@ -233,7 +240,7 @@ def test_a_file_node_refuses_to_leave_its_directory(): def test_a_file_round_trips_through_the_sandbox(tmp_path, monkeypatch): - from app.core.config import settings + from fluksio.core.config import settings monkeypatch.setattr(settings, "FLOWS_DIR", tmp_path / "flows") diff --git a/backend/tests/flow/test_watchdog.py b/backend/tests/flow/test_watchdog.py index f68ee18..15a447a 100644 --- a/backend/tests/flow/test_watchdog.py +++ b/backend/tests/flow/test_watchdog.py @@ -2,9 +2,9 @@ from fastapi.testclient import TestClient -from app.flow.events import EventBus -from app.flow.watchdog import DEGRADED_STRIKES, LoopWatchdog -from app.main import app +from fluksio.flow.events import EventBus +from fluksio.flow.watchdog import DEGRADED_STRIKES, LoopWatchdog +from fluksio.main import app def test_a_responsive_loop_stays_healthy(): diff --git a/backend/tests/flow/test_workers.py b/backend/tests/flow/test_workers.py index 8722506..8f389b8 100644 --- a/backend/tests/flow/test_workers.py +++ b/backend/tests/flow/test_workers.py @@ -7,10 +7,10 @@ from collections.abc import Iterator import pytest -from app.flow.artifacts import ArtifactStore -from app.flow.messages import DType, MessageSpec -from app.flow.worker_main import ARTIFACT_DIR_ENV -from app.flow.workers import NodeCancelled, NodeTimeout, PythonWorkerPool +from fluksio.flow.artifacts import ArtifactStore +from fluksio.flow.messages import DType, MessageSpec +from fluksio.flow.worker_main import ARTIFACT_DIR_ENV +from fluksio.flow.workers import NodeCancelled, NodeTimeout, PythonWorkerPool @pytest.fixture diff --git a/backend/tests/mcp/test_mcp_http.py b/backend/tests/mcp/test_mcp_http.py index 1127350..da04760 100644 --- a/backend/tests/mcp/test_mcp_http.py +++ b/backend/tests/mcp/test_mcp_http.py @@ -10,9 +10,9 @@ import httpx import pytest from fastapi.testclient import TestClient -from app.core import security -from app.core.config import settings -from app.main import app +from fluksio.core import security +from fluksio.core.config import settings +from fluksio.main import app MCP_HEADERS = {"Accept": "application/json, text/event-stream"} @@ -31,8 +31,8 @@ def over_mcp(monkeypatch: pytest.MonkeyPatch): whole exchange happens inside a single ``asyncio.run``. """ monkeypatch.setattr(settings, "MCP_ENABLED", True) - from app.mcp import http as mcp_http - from app.mcp import server as mcp_server + from fluksio.mcp import http as mcp_http + from fluksio.mcp import server as mcp_server # A FastMCP instance enters its session manager once, and another test in # the run may have built one already. diff --git a/backend/tests/scripts/test_backend_pre_start.py b/backend/tests/scripts/test_backend_pre_start.py index e8f35c6..8c55be5 100644 --- a/backend/tests/scripts/test_backend_pre_start.py +++ b/backend/tests/scripts/test_backend_pre_start.py @@ -2,7 +2,7 @@ from unittest.mock import MagicMock, patch from sqlmodel import select -from app.backend_pre_start import init, logger +from fluksio.backend_pre_start import init, logger def test_init_successful_connection() -> None: @@ -14,8 +14,8 @@ def test_init_successful_connection() -> None: select1 = select(1) with ( - patch("app.backend_pre_start.Session", return_value=session_mock), - patch("app.backend_pre_start.select", return_value=select1), + patch("fluksio.backend_pre_start.Session", return_value=session_mock), + patch("fluksio.backend_pre_start.select", return_value=select1), patch.object(logger, "info"), patch.object(logger, "error"), patch.object(logger, "warn"), diff --git a/backend/tests/scripts/test_test_pre_start.py b/backend/tests/scripts/test_test_pre_start.py index 180bdd5..a86f3ce 100644 --- a/backend/tests/scripts/test_test_pre_start.py +++ b/backend/tests/scripts/test_test_pre_start.py @@ -2,7 +2,7 @@ from unittest.mock import MagicMock, patch from sqlmodel import select -from app.tests_pre_start import init, logger +from fluksio.tests_pre_start import init, logger def test_init_successful_connection() -> None: @@ -14,8 +14,8 @@ def test_init_successful_connection() -> None: select1 = select(1) with ( - patch("app.tests_pre_start.Session", return_value=session_mock), - patch("app.tests_pre_start.select", return_value=select1), + patch("fluksio.tests_pre_start.Session", return_value=session_mock), + patch("fluksio.tests_pre_start.select", return_value=select1), patch.object(logger, "info"), patch.object(logger, "error"), patch.object(logger, "warn"), diff --git a/backend/tests/test_cloud.py b/backend/tests/test_cloud.py index 1c5f482..b4645f4 100644 --- a/backend/tests/test_cloud.py +++ b/backend/tests/test_cloud.py @@ -18,11 +18,11 @@ from cryptography.hazmat.primitives.asymmetric import rsa from fastapi.testclient import TestClient from sqlmodel import Session -from app.api.deps import decode_token, user_from_token -from app.cloud import config as cloud_config -from app.core.config import settings -from app.flow.panels import PanelDef, PanelsConfig, write_config -from app.models import User +from fluksio.api.deps import decode_token, user_from_token +from fluksio.cloud import config as cloud_config +from fluksio.core.config import settings +from fluksio.flow.panels import PanelDef, PanelsConfig, write_config +from fluksio.models import User from tests.utils.portal import ISSUER, portal_token @@ -206,7 +206,7 @@ def test_adding_a_remote_user_maps_and_revokes( return_value={"user_id": "portal-user-9", "email": "remote@example.com"} ), ) - with patch("app.api.routes.cloud.httpx.post", return_value=portal_reply) as post: + with patch("fluksio.api.routes.cloud.httpx.post", return_value=portal_reply) as post: added = client.post( f"{settings.API_V1_STR}/cloud/users", headers=superuser_token_headers, @@ -224,7 +224,7 @@ def test_adding_a_remote_user_maps_and_revokes( resolved = user_from_token(db, theirs) assert resolved is not None and resolved.email == "remote@example.com" - with patch("app.api.routes.cloud.httpx.post", return_value=portal_reply): + with patch("fluksio.api.routes.cloud.httpx.post", return_value=portal_reply): again = client.post( f"{settings.API_V1_STR}/cloud/users", headers=superuser_token_headers, @@ -233,7 +233,7 @@ def test_adding_a_remote_user_maps_and_revokes( assert again.status_code == 409 with patch( - "app.api.routes.cloud.httpx.delete", return_value=Mock(status_code=200) + "fluksio.api.routes.cloud.httpx.delete", return_value=Mock(status_code=200) ) as delete: removed = client.delete( f"{settings.API_V1_STR}/users/{body['id']}", headers=superuser_token_headers @@ -279,7 +279,7 @@ def test_enrolling_against_a_portal_without_an_owner_is_refused( ), ) try: - with patch("app.api.routes.cloud.httpx.AsyncClient") as client_cls: + with patch("fluksio.api.routes.cloud.httpx.AsyncClient") as client_cls: client_cls.return_value.__aenter__.return_value.post = AsyncMock( return_value=reply ) @@ -304,7 +304,7 @@ def test_an_older_enrolment_adopts_the_owner_on_attach( again, which for a machine reached only through the portal means standing in front of it. """ - from app.cloud.connector import CloudConnector + from fluksio.cloud.connector import CloudConnector enrolled.portal_sub = None db.add(enrolled) diff --git a/backend/tests/test_metrics.py b/backend/tests/test_metrics.py index 9f42f1b..3c2e90f 100644 --- a/backend/tests/test_metrics.py +++ b/backend/tests/test_metrics.py @@ -10,9 +10,9 @@ from datetime import datetime, timezone from sqlmodel import Session, select -from app.flow.events import EventBus -from app.flow.metrics import DETAIL_CAP, RUN_STALE_S, MetricsCollector -from app.models import EngineEvent, FlowRun, MetricBucket +from fluksio.flow.events import EventBus +from fluksio.flow.metrics import DETAIL_CAP, RUN_STALE_S, MetricsCollector +from fluksio.models import EngineEvent, FlowRun, MetricBucket FLOW = "metrics-test" NODE = "metrics-test.calc" diff --git a/backend/tests/utils/user.py b/backend/tests/utils/user.py index 5867431..03c8349 100644 --- a/backend/tests/utils/user.py +++ b/backend/tests/utils/user.py @@ -1,9 +1,9 @@ from fastapi.testclient import TestClient from sqlmodel import Session -from app import crud -from app.core.config import settings -from app.models import User, UserCreate, UserUpdate +from fluksio import crud +from fluksio.core.config import settings +from fluksio.models import User, UserCreate, UserUpdate from tests.utils.utils import random_email, random_lower_string diff --git a/backend/tests/utils/utils.py b/backend/tests/utils/utils.py index 184bac4..c2f4443 100644 --- a/backend/tests/utils/utils.py +++ b/backend/tests/utils/utils.py @@ -3,7 +3,7 @@ import string from fastapi.testclient import TestClient -from app.core.config import settings +from fluksio.core.config import settings def random_lower_string() -> str: diff --git a/development.md b/development.md index fe68ff6..3cff438 100644 --- a/development.md +++ b/development.md @@ -66,7 +66,7 @@ Standalone (`cd app && make dev`) publishes the full set: ## Hot reload, and what it misses -`compose.dev.yml` bind-mounts `backend/app` into the api container and runs uvicorn with +`compose.dev.yml` bind-mounts `backend/fluksio` into the api container and runs uvicorn with `--reload`, so a backend edit is live. The `develop.watch` block next to it syncs the same directory, but only under `docker compose watch`, which no target runs — the detached `up -d` flow never triggers it. The mount is what does the work, so an api container created diff --git a/docker/compose.dev.yml b/docker/compose.dev.yml index 384bfe2..8c3bc3e 100644 --- a/docker/compose.dev.yml +++ b/docker/compose.dev.yml @@ -93,7 +93,7 @@ services: - fastapi - run - --reload - - "app/main.py" + - "fluksio/main.py" develop: watch: - path: ../backend @@ -109,7 +109,7 @@ services: # `make dev-local`): it starts the stack with `up -d`, which never runs the # sync above — only `docker compose watch` does. Without the mount the # container serves the source baked into the image until it is rebuilt. - - ../backend/app:/app/backend/app + - ../backend/fluksio:/app/backend/fluksio environment: SMTP_HOST: "mailcatcher" SMTP_PORT: "1025" diff --git a/scripts/generate-client.sh b/scripts/generate-client.sh index 9af6b63..4d812bb 100644 --- a/scripts/generate-client.sh +++ b/scripts/generate-client.sh @@ -4,7 +4,7 @@ set -e set -x cd backend -uv run python -c "import app.main; import json; print(json.dumps(app.main.app.openapi()))" > ../openapi.json +uv run python -c "import fluksio.main; import json; print(json.dumps(fluksio.main.app.openapi()))" > ../openapi.json cd .. mv openapi.json frontend/ bun run --filter frontend generate-client diff --git a/uv.lock b/uv.lock index 87addc4..ce8fa8d 100644 --- a/uv.lock +++ b/uv.lock @@ -8,7 +8,7 @@ resolution-markers = [ [manifest] members = [ - "app", + "fluksio", ] [[package]] @@ -213,80 +213,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl", hash = "sha256:d405828884fc140aa80a3c667b8beed277f1dfedec42ba031bd6ac3db606ab6c", size = 113592, upload-time = "2026-01-06T11:45:19.497Z" }, ] -[[package]] -name = "app" -version = "0.1.0" -source = { editable = "backend" } -dependencies = [ - { name = "aiomqtt" }, - { name = "alembic" }, - { name = "croniter" }, - { name = "cryptography" }, - { name = "email-validator" }, - { name = "emails" }, - { name = "fastapi", extra = ["standard"] }, - { name = "httpx" }, - { name = "influxdb-client", extra = ["async"] }, - { name = "jinja2" }, - { name = "mcp" }, - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "numpy", version = "2.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "psycopg", extra = ["binary"] }, - { name = "pwdlib", extra = ["argon2", "bcrypt"] }, - { name = "pydantic" }, - { name = "pydantic-settings" }, - { name = "pyjwt" }, - { name = "python-multipart" }, - { name = "redis" }, - { name = "sentry-sdk", extra = ["fastapi"] }, - { name = "sqlmodel" }, - { name = "tenacity" }, -] - -[package.dev-dependencies] -dev = [ - { name = "coverage" }, - { name = "mypy" }, - { name = "prek" }, - { name = "pytest" }, - { name = "ruff" }, -] - -[package.metadata] -requires-dist = [ - { name = "aiomqtt", specifier = ">=2.0.0" }, - { name = "alembic", specifier = ">=1.12.1,<2.0.0" }, - { name = "croniter", specifier = ">=1.3.0" }, - { name = "cryptography", specifier = ">=44.0.0" }, - { name = "email-validator", specifier = ">=2.1.0.post1,<3.0.0.0" }, - { name = "emails", specifier = ">=0.6,<1.0" }, - { name = "fastapi", extras = ["standard"], specifier = ">=0.114.2,<1.0.0" }, - { name = "httpx", specifier = ">=0.25.1,<1.0.0" }, - { name = "influxdb-client", extras = ["async"], specifier = ">=1.40.0" }, - { name = "jinja2", specifier = ">=3.1.4,<4.0.0" }, - { name = "mcp", specifier = ">=1.29,<2" }, - { name = "numpy", specifier = ">=2.2.6" }, - { name = "psycopg", extras = ["binary"], specifier = ">=3.1.13,<4.0.0" }, - { name = "pwdlib", extras = ["argon2", "bcrypt"], specifier = ">=0.3.0" }, - { name = "pydantic", specifier = ">2.0" }, - { name = "pydantic-settings", specifier = ">=2.2.1,<3.0.0" }, - { name = "pyjwt", specifier = ">=2.8.0,<3.0.0" }, - { name = "python-multipart", specifier = ">=0.0.7,<1.0.0" }, - { name = "redis", specifier = ">=7.1.0" }, - { name = "sentry-sdk", extras = ["fastapi"], specifier = ">=1.40.6,<2.0.0" }, - { name = "sqlmodel", specifier = ">=0.0.21,<1.0.0" }, - { name = "tenacity", specifier = ">=8.2.3,<9.0.0" }, -] - -[package.metadata.requires-dev] -dev = [ - { name = "coverage", specifier = ">=7.4.3,<8.0.0" }, - { name = "mypy", specifier = ">=1.8.0,<2.0.0" }, - { name = "prek", specifier = ">=0.2.24,<1.0.0" }, - { name = "pytest", specifier = ">=7.4.3,<8.0.0" }, - { name = "ruff", specifier = ">=0.2.2,<1.0.0" }, -] - [[package]] name = "argon2-cffi" version = "25.1.0" @@ -827,6 +753,80 @@ standard = [ { name = "uvicorn", extra = ["standard"] }, ] +[[package]] +name = "fluksio" +version = "0.1.0" +source = { editable = "backend" } +dependencies = [ + { name = "aiomqtt" }, + { name = "alembic" }, + { name = "croniter" }, + { name = "cryptography" }, + { name = "email-validator" }, + { name = "emails" }, + { name = "fastapi", extra = ["standard"] }, + { name = "httpx" }, + { name = "influxdb-client", extra = ["async"] }, + { name = "jinja2" }, + { name = "mcp" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "psycopg", extra = ["binary"] }, + { name = "pwdlib", extra = ["argon2", "bcrypt"] }, + { name = "pydantic" }, + { name = "pydantic-settings" }, + { name = "pyjwt" }, + { name = "python-multipart" }, + { name = "redis" }, + { name = "sentry-sdk", extra = ["fastapi"] }, + { name = "sqlmodel" }, + { name = "tenacity" }, +] + +[package.dev-dependencies] +dev = [ + { name = "coverage" }, + { name = "mypy" }, + { name = "prek" }, + { name = "pytest" }, + { name = "ruff" }, +] + +[package.metadata] +requires-dist = [ + { name = "aiomqtt", specifier = ">=2.0.0" }, + { name = "alembic", specifier = ">=1.12.1,<2.0.0" }, + { name = "croniter", specifier = ">=1.3.0" }, + { name = "cryptography", specifier = ">=44.0.0" }, + { name = "email-validator", specifier = ">=2.1.0.post1,<3.0.0.0" }, + { name = "emails", specifier = ">=0.6,<1.0" }, + { name = "fastapi", extras = ["standard"], specifier = ">=0.114.2,<1.0.0" }, + { name = "httpx", specifier = ">=0.25.1,<1.0.0" }, + { name = "influxdb-client", extras = ["async"], specifier = ">=1.40.0" }, + { name = "jinja2", specifier = ">=3.1.4,<4.0.0" }, + { name = "mcp", specifier = ">=1.29,<2" }, + { name = "numpy", specifier = ">=2.2.6" }, + { name = "psycopg", extras = ["binary"], specifier = ">=3.1.13,<4.0.0" }, + { name = "pwdlib", extras = ["argon2", "bcrypt"], specifier = ">=0.3.0" }, + { name = "pydantic", specifier = ">2.0" }, + { name = "pydantic-settings", specifier = ">=2.2.1,<3.0.0" }, + { name = "pyjwt", specifier = ">=2.8.0,<3.0.0" }, + { name = "python-multipart", specifier = ">=0.0.7,<1.0.0" }, + { name = "redis", specifier = ">=7.1.0" }, + { name = "sentry-sdk", extras = ["fastapi"], specifier = ">=1.40.6,<2.0.0" }, + { name = "sqlmodel", specifier = ">=0.0.21,<1.0.0" }, + { name = "tenacity", specifier = ">=8.2.3,<9.0.0" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "coverage", specifier = ">=7.4.3,<8.0.0" }, + { name = "mypy", specifier = ">=1.8.0,<2.0.0" }, + { name = "prek", specifier = ">=0.2.24,<1.0.0" }, + { name = "pytest", specifier = ">=7.4.3,<8.0.0" }, + { name = "ruff", specifier = ">=0.2.2,<1.0.0" }, +] + [[package]] name = "frozenlist" version = "1.8.0"