[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