Files
app/backend/pyproject.toml
T
stroblmeandClaude Opus 5 45cc7504e1 Notify a phone that has this installation installed
A `webpush` alert channel, and the PWA it needs to arrive. The payload is
encrypted to the subscription (RFC 8291) and the request signed with this
installation's own keypair (RFC 8292), both over `http-ece` — `pywebpush`
does the same in one call but brings `requests` and `aiohttp` with it, two
HTTP stacks beside httpx on a machine that may be a Raspberry Pi.

The manifest and the worker are hand-written rather than `vite-plugin-pwa`:
there is nothing worth precaching when the page carrying the credential is
`no-store`, so the worker handles `push` and `notificationclick` and nothing
else. `registration.scope` is the app's root in both places it runs, which is
why the payload carries no URL.

A run finishing in error is the first event worth waking someone for; `ok`
and `cancelled` describe to nothing, so a nightly batch that works stays
quiet. The events were already on the bus — only the filter changed.

`WEBPUSH_FILE` is a derived path, so the keypair lands on the data volume
with the alerts beside it. Off it, a rebuild would silently stop every phone
being notified: the key they subscribed against would be gone.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014EbeFPm6WNC3YD9vrqqT3a
2026-08-30 12:12:37 +02:00

213 lines
7.7 KiB
TOML

[project]
name = "fluksio"
version = "0.1.4+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",
"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.
dependencies = [
"fastapi[standard]<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 `mlp` node, which is registered at boot. Kept out of the extras
# because a data-science environment has it already.
"numpy>=2.2.6",
# 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",
# 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"]
# What a deployment has and a laptop does not: the device connectors, outbound
# mail and error reporting. The engine, the CLI and every python node work
# without them — `pip install fluksio` in a data-science environment is the
# case this exists for, and lxml and the aiohttp stack are most of its wait.
# Each import is guarded and names this extra. The image installs it.
server = [
"emails<1.0,>=0.6",
"sentry-sdk[fastapi]>=2.20.0",
"aiomqtt>=2.0.0",
"influxdb-client[async]>=1.40.0",
]
[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<8.0.0,>=7.4.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
[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.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