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) <noreply@anthropic.com>
This commit is contained in:
2026-08-21 21:48:05 +02:00
co-authored by Claude Opus 5
parent 97785ee590
commit 60d7ec81c0
170 changed files with 629 additions and 619 deletions
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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/.*
+2 -2
View File
@@ -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
+1 -1
View File
@@ -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
+2 -2
View File
@@ -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
+1 -1
View File
@@ -1,6 +1,6 @@
# Python
__pycache__
app.egg-info
fluksio.egg-info
*.pyc
.mypy_cache
.coverage
+1 -1
View File
@@ -1,6 +1,6 @@
*.png
__pycache__
app.egg-info
fluksio.egg-info
*.pyc
.mypy_cache
.coverage
+4 -4
View File
@@ -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"]
+1 -1
View File
@@ -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
-26
View File
@@ -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",
]
-37
View File
@@ -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",
]
@@ -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
@@ -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"
@@ -1,6 +1,6 @@
from fastapi import APIRouter
from app.api.routes import (
from fluksio.api.routes import (
alerts,
artifacts,
cloud,
@@ -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)]
@@ -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:
@@ -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:
@@ -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)]
@@ -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()
@@ -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,
@@ -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)]
@@ -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)]
@@ -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,
@@ -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",
@@ -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
@@ -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,
)
@@ -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)]
@@ -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)]
@@ -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"])
@@ -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"])
@@ -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__)
@@ -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__)
@@ -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__)
@@ -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 "")
@@ -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.
@@ -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
@@ -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:
+26
View File
@@ -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",
]
@@ -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:
@@ -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
@@ -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]] = {}
@@ -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"
@@ -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__)
@@ -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__)
@@ -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__)
+37
View File
@@ -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",
]
@@ -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__)
@@ -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
@@ -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__)
@@ -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"
@@ -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
@@ -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__)
@@ -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
@@ -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__)
@@ -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__)
@@ -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
@@ -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__)
@@ -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__)
@@ -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):
@@ -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",
@@ -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):
@@ -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__)
@@ -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__)
@@ -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_]*$")
@@ -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__)
@@ -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__)
@@ -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__)
@@ -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()

Some files were not shown because too many files have changed in this diff Show More