Files
app/backend/pyproject.toml
T
stroblme dea25ec08d One image for the whole app: engine, API and dashboard on one port
The backend image now builds the SPA and serves it on the API's own origin
(fluksio/spa.py), so an instance is a single container with no proxy, DNS,
port 80 or .env: `docker compose up -d` with the new compose.yml runs it on
localhost:8080. It starts on fluksio serve (persisted secret key, first
admin printed once) and brings its own Redis when REDIS_HOST is unset.

Slimmer: python:3.13-slim, no dev group, uv once, fastapi without the cloud
CLI. Optional features are per-feature extras (mqtt, influx, email, sentry,
ml, server for all) picked with the EXTRAS build argument; numpy moved to
`ml`. ffmpeg left the image for imageio-ffmpeg via the Modules screen.

The operator stack runs the same image with app. and api. routed to it; the
nginx frontend service, the LAN nginx proxy and DOMAIN in the backend are
gone, and the MCP issuer is FRONTEND_HOST.
2026-10-16 11:40:02 +02:00

234 lines
8.8 KiB
TOML

[project]
name = "fluksio"
version = "0.1.6+dev"
description = "Node-based automation engine: flows, dashboards, batch runs"
readme = "README.md"
license = "AGPL-3.0-or-later"
license-files = ["LICENSE"]
requires-python = ">=3.12"
authors = [{ name = "Fluksio", email = "stroblme@posteo.de" }]
keywords = ["automation", "workflow", "dataflow", "experiment-tracking", "mlops"]
classifiers = [
"Development Status :: 3 - Alpha",
"Intended Audience :: Developers",
"Intended Audience :: Science/Research",
"Operating System :: MacOS",
"Operating System :: POSIX :: Linux",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Topic :: Home Automation",
"Topic :: Scientific/Engineering",
"Topic :: System :: Distributed Computing",
]
# Named because this code imports them. Four of them — python-multipart,
# email-validator, jinja2 and httpx — also arrive inside `fastapi[standard]`,
# so those lines look redundant and install nothing extra; they are here so a
# direct import does not rest on somebody else's extra keeping its contents.
# The variant without fastapi-cloud-cli, which brings sentry-sdk and a deploy
# command nothing here runs.
dependencies = [
"fastapi[standard-no-fastapi-cloud-cli]<1.0.0,>=0.114.2",
"pydantic>2.0",
"pydantic-settings<3.0.0,>=2.2.1",
"sqlmodel<1.0.0,>=0.0.21",
"alembic<2.0.0,>=1.12.1",
"httpx<1.0.0,>=0.25.1",
# `Form(...)` on the OAuth token endpoint, which fastapi refuses to build
# the route without.
"python-multipart<1.0.0,>=0.0.7",
# `EmailStr` on the user model, so it is read on every start — an address
# is what a user is identified by, whether or not mail is ever sent. The
# sending itself is the `server` extra.
"email-validator<3.0.0.0,>=2.1.0.post1",
# Only the mail templates use it, and only after `send_email` — but it
# comes with `fastapi[standard]` either way, so an extra would move a line
# rather than a package.
"jinja2<4.0.0,>=3.1.4",
# Sessions, the secrets file, and the keypair an agent's token is signed
# with.
"pyjwt<3.0.0,>=2.8.0",
"pwdlib[argon2,bcrypt]>=0.3.0",
"cryptography>=44.0.0",
# The state store and the work queue, and what a panel's config is read
# through.
"redis>=7.1.0",
# Every message costs a round trip's worth of encode and decode — state,
# the journal, the worker pipe. Engine-side only: `fluksio-worker` is
# copied onto other people's machines and stays dependency-free.
"orjson>=3.10",
# The `wren` node runs its model on one BLAS thread, which answers in about
# half the time. Scoped to the call: OPENBLAS_NUM_THREADS would bind every
# other numpy call in the engine too, and only counts before numpy loads.
"threadpoolctl>=3.5",
# The `inject` node's cron. Imported where it is used, behind a message
# that cannot fire while this line is here.
"croniter>=1.3.0",
"mcp>=1.29,<2",
"fluksio-worker>=0.1,<0.2",
# The Modules screen installs node code's packages with it, into a venv of
# the user's own. Present in the image; a pip install would otherwise have
# to find one on PATH, and quietly fall back to the engine's interpreter.
"uv>=0.5",
# `fluksio status` draws with it, and textual is built on it — named
# anyway, because a command that imports it should say so.
"rich>=13",
# `fluksio serve` opens a dashboard with it at a terminal.
"textual>=1.0",
# That dashboard subscribes to the engine's event bus over the same socket
# the browser uses. It arrives under `fastapi[standard]` either way, and is
# named here because this code imports it. 14 is where the asyncio client
# the portal link dials with takes `additional_headers`.
"websockets>=14",
# The encrypted-payload half of web push (RFC 8291). It brings only
# cryptography, which is already here. `pywebpush` would do the signing and
# the request too, at the cost of `requests` *and* `aiohttp` — two HTTP
# stacks beside httpx, on a machine that may be a Raspberry Pi.
"http-ece>=1.2",
]
[project.optional-dependencies]
# `fluksio export --format parquet` converts the exported rows with it. An
# extra rather than a dependency: csv and jsonl need nothing, and pyarrow is
# tens of megabytes for whoever wants dtypes kept.
parquet = ["pyarrow>=17"]
# One per feature, so an instance carries only what it talks to. The engine,
# the CLI and every python node work without any of them. Each import is
# guarded and names its extra; the image takes them as EXTRAS=mqtt,influx.
mqtt = ["aiomqtt>=2.0.0"]
influx = ["influxdb-client[async]>=1.40.0"]
# Outbound mail. lxml and its stylesheet inliner are most of this.
email = ["emails<1.0,>=0.6"]
sentry = ["sentry-sdk[fastapi]>=2.20.0"]
# The `mlp` node. A data-science environment has it already.
ml = ["numpy>=2.2.6"]
# All of the above: what a full deployment runs.
server = ["fluksio[mqtt,influx,email,sentry,ml]"]
[project.urls]
Homepage = "https://fluksio.com"
Documentation = "https://docs.fluksio.com"
"Getting started" = "https://docs.fluksio.com/getting-started/data-science/"
[project.scripts]
fluksio = "fluksio.cli:main"
[tool.uv.sources]
fluksio-worker = { workspace = true }
[dependency-groups]
dev = [
"pytest<10.0.0,>=9.0.3",
"mypy<2.0.0,>=1.8.0",
"ruff<1.0.0,>=0.2.2",
"prek>=0.2.24,<1.0.0",
"coverage<8.0.0,>=7.4.3",
]
[tool.hatch.build.targets.wheel]
packages = ["fluksio"]
# Named rather than excluded: this directory also holds a working tree's
# runtime data — `flow-data/` with the user venv in it — which is not source.
[tool.hatch.build.targets.sdist]
include = ["fluksio", "tests", "scripts", "alembic.ini", "pyproject.toml", "README.md"]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.mypy]
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 (fluksio/flow/nodes/influx.py).
untyped_calls_exclude = ["influxdb_client"]
# Every module is checked strictly. influxdb_client is typed but re-exports its
# names implicitly, which strict mode refuses to follow. (croniter, the other
# untyped dependency, is ignored at its two import sites.)
[[tool.mypy.overrides]]
module = ["influxdb_client"]
implicit_reexport = true
# The parquet extra. Not installed here, and the import that uses it is inside
# the try/except that says so.
[[tool.mypy.overrides]]
module = ["pyarrow", "pyarrow.*"]
ignore_missing_imports = true
# The web push payload encoder carries no annotations.
[[tool.mypy.overrides]]
module = ["http_ece"]
ignore_missing_imports = true
# The model behind the `wren` node: optional and untyped, imported inside the
# guard that says how to install it.
[[tool.mypy.overrides]]
module = ["wren"]
ignore_missing_imports = true
# What scopes the `wren` node's BLAS threads to its call; it ships no types.
[[tool.mypy.overrides]]
module = ["threadpoolctl"]
ignore_missing_imports = true
[tool.ruff]
target-version = "py312"
exclude = ["alembic"]
[tool.ruff.lint]
select = [
"E", # pycodestyle errors
"W", # pycodestyle warnings
"F", # pyflakes
"I", # isort
"B", # flake8-bugbear
"C4", # flake8-comprehensions
"UP", # pyupgrade
"ARG001", # unused arguments in functions
"T201", # print statements are not allowed
]
ignore = [
"E501", # line too long, handled by black
"B008", # do not perform function calls in argument defaults
"W191", # indentation contains tabs
"B904", # Allow raising exceptions without from e, for HTTPException
]
[tool.ruff.lint.per-file-ignores]
# The node API's stubs carry the real signatures and raise; unused arguments
# are what a stub is.
"fluksio/__init__.py" = ["ARG001"]
# It talks to whoever ran it; that is what a command line is.
"fluksio/cli.py" = ["T201"]
"fluksio/sdk/cli.py" = ["T201"]
# What it prints is the line left in the terminal after the dashboard closes.
"fluksio/tui/app.py" = ["T201"]
# Node functions take `params` whether or not they use it — that is the
# contract the engine calls them with.
"fluksio/flow/nodes.py" = ["ARG001", "ARG002"]
"tests/flow/*" = ["ARG001"]
# Node functions declared for a test take the ports they declare, used or not.
"tests/sdk/*" = ["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"]
[tool.ruff.lint.pyupgrade]
# Preserve types, even if a file imports `from __future__ import annotations`.
keep-runtime-typing = true
[tool.coverage.run]
source = ["fluksio"]
dynamic_context = "test_function"
[tool.coverage.report]
show_missing = true
sort = "-Cover"
[tool.coverage.html]
show_contexts = true