Split the worker into a distribution of its own
A cluster or GPU host installs `pip install fluksio-worker` and gets the agent and the runner, not psycopg, numpy and the MCP SDK. The engine depends on it as a workspace member, so the file it launches node code with is the same file a remote worker runs — which is what keeps a node unable to tell the difference. Copying the two files by hand still works. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -170,6 +170,7 @@ lint-backend: ## Lint backend with ruff + mypy
|
|||||||
cd backend && uv run ruff check .
|
cd backend && uv run ruff check .
|
||||||
cd backend && uv run ruff format --check .
|
cd backend && uv run ruff format --check .
|
||||||
cd backend && uv run mypy fluksio
|
cd backend && uv run mypy fluksio
|
||||||
|
cd worker && uv run --no-project --with mypy mypy fluksio_worker
|
||||||
|
|
||||||
lint-frontend: ## Lint frontend with biome
|
lint-frontend: ## Lint frontend with biome
|
||||||
cd frontend && bun run lint
|
cd frontend && bun run lint
|
||||||
|
|||||||
@@ -33,6 +33,11 @@ COPY ./backend/pyproject.toml ./backend/alembic.ini /app/backend/
|
|||||||
|
|
||||||
COPY ./backend/fluksio /app/backend/fluksio
|
COPY ./backend/fluksio /app/backend/fluksio
|
||||||
|
|
||||||
|
# The worker is a workspace member of its own, so the engine's sync needs it
|
||||||
|
# present to resolve the dependency on it. It is also what the engine runs
|
||||||
|
# node code with.
|
||||||
|
COPY ./worker /app/worker
|
||||||
|
|
||||||
# Sync the project
|
# Sync the project
|
||||||
# Ref: https://docs.astral.sh/uv/guides/integration/docker/#intermediate-layers
|
# Ref: https://docs.astral.sh/uv/guides/integration/docker/#intermediate-layers
|
||||||
RUN --mount=type=cache,target=/root/.cache/uv \
|
RUN --mount=type=cache,target=/root/.cache/uv \
|
||||||
|
|||||||
@@ -15,12 +15,12 @@ from typing import Any
|
|||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException, Request, WebSocket
|
from fastapi import APIRouter, Depends, HTTPException, Request, WebSocket
|
||||||
from fastapi.responses import PlainTextResponse
|
from fastapi.responses import PlainTextResponse
|
||||||
|
from fluksio_worker import worker_main
|
||||||
from jwt.exceptions import InvalidTokenError
|
from jwt.exceptions import InvalidTokenError
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
from fluksio.api.deps import get_current_active_superuser, get_current_user
|
from fluksio.api.deps import get_current_active_superuser, get_current_user
|
||||||
from fluksio.core import security
|
from fluksio.core import security
|
||||||
from fluksio.flow import worker_main
|
|
||||||
from fluksio.flow.remote import PROTOCOL, RemoteWorker, RemoteWorkerHub
|
from fluksio.flow.remote import PROTOCOL, RemoteWorker, RemoteWorkerHub
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -103,7 +103,7 @@ def read_runtime() -> str:
|
|||||||
"""The worker's own code, so a fresh host installs by fetching one file.
|
"""The worker's own code, so a fresh host installs by fetching one file.
|
||||||
|
|
||||||
It is the same module the engine's local workers run — deliberately
|
It is the same module the engine's local workers run — deliberately
|
||||||
standard library only, and with nothing of the app importable in it.
|
standard library only, and with nothing of the engine importable in it.
|
||||||
"""
|
"""
|
||||||
return worker_main.__file__ and open(worker_main.__file__).read()
|
return worker_main.__file__ and open(worker_main.__file__).read()
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ from typing import Any, cast
|
|||||||
|
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
from fastapi.concurrency import run_in_threadpool
|
from fastapi.concurrency import run_in_threadpool
|
||||||
|
from fluksio_worker.worker_main import load_function
|
||||||
|
|
||||||
from fluksio.core.config import settings
|
from fluksio.core.config import settings
|
||||||
from fluksio.flow.alerts import AlertManager
|
from fluksio.flow.alerts import AlertManager
|
||||||
@@ -60,7 +61,6 @@ from fluksio.flow.secrets import SecretNotFound, resolve_params
|
|||||||
from fluksio.flow.state import MemoryState, StateBackend
|
from fluksio.flow.state import MemoryState, StateBackend
|
||||||
from fluksio.flow.store import LIB_DIR, FlowNotFound, FlowStore, LibNotFound
|
from fluksio.flow.store import LIB_DIR, FlowNotFound, FlowStore, LibNotFound
|
||||||
from fluksio.flow.supervision import Supervisor
|
from fluksio.flow.supervision import Supervisor
|
||||||
from fluksio.flow.worker_main import load_function
|
|
||||||
from fluksio.flow.workers import PythonWorkerPool
|
from fluksio.flow.workers import PythonWorkerPool
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|||||||
@@ -26,11 +26,15 @@ from collections.abc import Callable
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
from fluksio_worker import worker_main as _worker_main
|
||||||
|
|
||||||
from fluksio.flow.events import EventBus
|
from fluksio.flow.events import EventBus
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
WORKER_MAIN = Path(__file__).with_name("worker_main.py")
|
#: The runner, in the worker distribution — the same file a remote worker runs,
|
||||||
|
#: so a node cannot tell which kind of worker it is on.
|
||||||
|
WORKER_MAIN = Path(_worker_main.__file__)
|
||||||
|
|
||||||
#: Importing what a node needs can be slow the first time; compiling is not
|
#: Importing what a node needs can be slow the first time; compiling is not
|
||||||
#: something a person is watching a spinner for.
|
#: something a person is watching a spinner for.
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ from fastapi import FastAPI
|
|||||||
from fastapi.concurrency import run_in_threadpool
|
from fastapi.concurrency import run_in_threadpool
|
||||||
from fastapi.responses import JSONResponse
|
from fastapi.responses import JSONResponse
|
||||||
from fastapi.routing import APIRoute
|
from fastapi.routing import APIRoute
|
||||||
|
from fluksio_worker.worker_main import ARTIFACT_DIR_ENV
|
||||||
from starlette.middleware.cors import CORSMiddleware
|
from starlette.middleware.cors import CORSMiddleware
|
||||||
|
|
||||||
from fluksio.api.main import api_router
|
from fluksio.api.main import api_router
|
||||||
@@ -33,7 +34,6 @@ from fluksio.flow.secrets import init_secrets
|
|||||||
from fluksio.flow.state import MemoryState, RedisState, StateBackend
|
from fluksio.flow.state import MemoryState, RedisState, StateBackend
|
||||||
from fluksio.flow.store import FlowStore
|
from fluksio.flow.store import FlowStore
|
||||||
from fluksio.flow.watchdog import LoopWatchdog
|
from fluksio.flow.watchdog import LoopWatchdog
|
||||||
from fluksio.flow.worker_main import ARTIFACT_DIR_ENV
|
|
||||||
from fluksio.flow.workers import PythonWorkerPool
|
from fluksio.flow.workers import PythonWorkerPool
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -28,8 +28,12 @@ dependencies = [
|
|||||||
"influxdb-client[async]>=1.40.0",
|
"influxdb-client[async]>=1.40.0",
|
||||||
"croniter>=1.3.0",
|
"croniter>=1.3.0",
|
||||||
"mcp>=1.29,<2",
|
"mcp>=1.29,<2",
|
||||||
|
"fluksio-worker>=0.1,<0.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[tool.uv.sources]
|
||||||
|
fluksio-worker = { workspace = true }
|
||||||
|
|
||||||
[dependency-groups]
|
[dependency-groups]
|
||||||
dev = [
|
dev = [
|
||||||
"pytest<8.0.0,>=7.4.3",
|
"pytest<8.0.0,>=7.4.3",
|
||||||
@@ -42,6 +46,11 @@ dev = [
|
|||||||
[tool.hatch.build.targets.wheel]
|
[tool.hatch.build.targets.wheel]
|
||||||
packages = ["fluksio"]
|
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]
|
[build-system]
|
||||||
requires = ["hatchling"]
|
requires = ["hatchling"]
|
||||||
build-backend = "hatchling.build"
|
build-backend = "hatchling.build"
|
||||||
@@ -92,12 +101,6 @@ ignore = [
|
|||||||
# Printing is what this one is about: node code is user code, and `print` is
|
# Printing is what this one is about: node code is user code, and `print` is
|
||||||
# how it says things.
|
# how it says things.
|
||||||
"tests/flow/test_logs.py" = ["ARG001", "T201"]
|
"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.
|
|
||||||
"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.
|
|
||||||
"fluksio/worker/fluksio_worker.py" = ["T201"]
|
|
||||||
|
|
||||||
[tool.ruff.lint.pyupgrade]
|
[tool.ruff.lint.pyupgrade]
|
||||||
# Preserve types, even if a file imports `from __future__ import annotations`.
|
# Preserve types, even if a file imports `from __future__ import annotations`.
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ import time
|
|||||||
from collections.abc import Iterator
|
from collections.abc import Iterator
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from fluksio_worker.worker_main import ARTIFACT_DIR_ENV
|
||||||
|
|
||||||
from fluksio.flow.artifacts import ArtifactStore
|
from fluksio.flow.artifacts import ArtifactStore
|
||||||
from fluksio.flow.messages import DType, MessageSpec
|
from fluksio.flow.messages import DType, MessageSpec
|
||||||
from fluksio.flow.worker_main import ARTIFACT_DIR_ENV
|
|
||||||
from fluksio.flow.workers import NodeCancelled, NodeTimeout, PythonWorkerPool
|
from fluksio.flow.workers import NodeCancelled, NodeTimeout, PythonWorkerPool
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,2 +1,2 @@
|
|||||||
[tool.uv.workspace]
|
[tool.uv.workspace]
|
||||||
members = ["backend"]
|
members = ["backend", "worker"]
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ resolution-markers = [
|
|||||||
[manifest]
|
[manifest]
|
||||||
members = [
|
members = [
|
||||||
"fluksio",
|
"fluksio",
|
||||||
|
"fluksio-worker",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -765,6 +766,7 @@ dependencies = [
|
|||||||
{ name = "email-validator" },
|
{ name = "email-validator" },
|
||||||
{ name = "emails" },
|
{ name = "emails" },
|
||||||
{ name = "fastapi", extra = ["standard"] },
|
{ name = "fastapi", extra = ["standard"] },
|
||||||
|
{ name = "fluksio-worker" },
|
||||||
{ name = "httpx" },
|
{ name = "httpx" },
|
||||||
{ name = "influxdb-client", extra = ["async"] },
|
{ name = "influxdb-client", extra = ["async"] },
|
||||||
{ name = "jinja2" },
|
{ name = "jinja2" },
|
||||||
@@ -801,6 +803,7 @@ requires-dist = [
|
|||||||
{ name = "email-validator", specifier = ">=2.1.0.post1,<3.0.0.0" },
|
{ name = "email-validator", specifier = ">=2.1.0.post1,<3.0.0.0" },
|
||||||
{ name = "emails", specifier = ">=0.6,<1.0" },
|
{ name = "emails", specifier = ">=0.6,<1.0" },
|
||||||
{ name = "fastapi", extras = ["standard"], specifier = ">=0.114.2,<1.0.0" },
|
{ name = "fastapi", extras = ["standard"], specifier = ">=0.114.2,<1.0.0" },
|
||||||
|
{ name = "fluksio-worker", editable = "worker" },
|
||||||
{ name = "httpx", specifier = ">=0.25.1,<1.0.0" },
|
{ name = "httpx", specifier = ">=0.25.1,<1.0.0" },
|
||||||
{ name = "influxdb-client", extras = ["async"], specifier = ">=1.40.0" },
|
{ name = "influxdb-client", extras = ["async"], specifier = ">=1.40.0" },
|
||||||
{ name = "jinja2", specifier = ">=3.1.4,<4.0.0" },
|
{ name = "jinja2", specifier = ">=3.1.4,<4.0.0" },
|
||||||
@@ -827,6 +830,17 @@ dev = [
|
|||||||
{ name = "ruff", specifier = ">=0.2.2,<1.0.0" },
|
{ name = "ruff", specifier = ">=0.2.2,<1.0.0" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fluksio-worker"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = { editable = "worker" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "websockets" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.metadata]
|
||||||
|
requires-dist = [{ name = "websockets", specifier = ">=12" }]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "frozenlist"
|
name = "frozenlist"
|
||||||
version = "1.8.0"
|
version = "1.8.0"
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# fluksio-worker
|
||||||
|
|
||||||
|
Runs [Fluksio](https://fluksio.com) nodes on a machine the engine cannot reach —
|
||||||
|
a GPU box, a cluster node, anything behind a NAT. It dials *out* to the engine
|
||||||
|
over one authenticated websocket, so nothing has to be exposed here.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pip install fluksio-worker
|
||||||
|
|
||||||
|
fluksio-worker \
|
||||||
|
--url wss://api.example.com/api/v1/workers/attach \
|
||||||
|
--token "$FLUKSIO_WORKER_TOKEN" \
|
||||||
|
--labels gpu,cuda12 \
|
||||||
|
--python /opt/torch-venv/bin/python
|
||||||
|
```
|
||||||
|
|
||||||
|
`--python` is the interpreter node code runs on, which is how this machine keeps
|
||||||
|
its own wheels without the engine ever installing them. Mint a token from the
|
||||||
|
engine with `POST /api/v1/workers/tokens`.
|
||||||
|
|
||||||
|
Linux and macOS.
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
"""The Fluksio worker: node execution on a machine the engine cannot reach.
|
||||||
|
|
||||||
|
Deliberately import-free. The engine imports :mod:`fluksio_worker.worker_main`
|
||||||
|
to find the runner it launches as a subprocess, and paying for the agent's
|
||||||
|
imports on that path would be a cost for nothing.
|
||||||
|
"""
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
"""``python -m fluksio_worker`` — the same entry point as the console script."""
|
||||||
|
|
||||||
|
from fluksio_worker.agent import main
|
||||||
|
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
"""The agent that runs Fluksio nodes on a machine the engine cannot reach.
|
"""The agent that runs Fluksio nodes on a machine the engine cannot reach.
|
||||||
|
|
||||||
Copy this file and ``worker_main.py`` onto the box with the GPU, point it at
|
Point it at the engine on the box with the GPU and it dials in::
|
||||||
the engine, and it dials in::
|
|
||||||
|
|
||||||
pip install websockets
|
pip install fluksio-worker
|
||||||
python fluksio_worker.py --url wss://api.example.com/api/v1/workers/attach \\
|
fluksio-worker --url wss://api.example.com/api/v1/workers/attach \\
|
||||||
--token "$FLUKSIO_WORKER_TOKEN" --labels gpu --python /opt/venv/bin/python
|
--token "$FLUKSIO_WORKER_TOKEN" --labels gpu --python /opt/venv/bin/python
|
||||||
|
|
||||||
It connects *out*, so the engine needs no route back and nothing has to expose
|
It connects *out*, so the engine needs no route back and nothing has to expose
|
||||||
@@ -13,9 +12,11 @@ subprocesses running ``worker_main.py``, hand each call to one, and pass back
|
|||||||
everything that comes out — including the metrics a training loop reports
|
everything that comes out — including the metrics a training loop reports
|
||||||
while it is still running.
|
while it is still running.
|
||||||
|
|
||||||
Deliberately one file with one dependency. Nothing of the app is imported
|
Deliberately two files with one dependency. Nothing of the engine is imported
|
||||||
here; a worker host installs Python, ``websockets``, and whatever the nodes
|
here; a worker host installs Python, ``websockets``, and whatever the nodes
|
||||||
themselves need.
|
themselves need. Where pip is not an option, copying this file and
|
||||||
|
``worker_main.py`` into one directory and running ``python agent.py`` is the
|
||||||
|
same thing — the engine serves the runner at ``GET /api/v1/workers/runtime``.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
@@ -40,7 +41,6 @@ except ImportError: # pragma: no cover - the one dependency, named plainly
|
|||||||
)
|
)
|
||||||
raise SystemExit(1) from None
|
raise SystemExit(1) from None
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
|
|
||||||
log = logging.getLogger("fluksio-worker")
|
log = logging.getLogger("fluksio-worker")
|
||||||
|
|
||||||
PROTOCOL = 1
|
PROTOCOL = 1
|
||||||
@@ -50,22 +50,9 @@ HEARTBEAT_S = 10.0
|
|||||||
MAX_BACKOFF_S = 30.0
|
MAX_BACKOFF_S = 30.0
|
||||||
|
|
||||||
|
|
||||||
def _worker_main() -> Path:
|
#: The user-code runner, beside this file — installed together, or copied
|
||||||
"""The user-code runner: beside this file once deployed, or in a checkout.
|
#: together onto a host where pip is not an option.
|
||||||
|
WORKER_MAIN = Path(__file__).resolve().with_name("worker_main.py")
|
||||||
On a worker host the two files sit together, which is what the install
|
|
||||||
instructions say. Run straight from a clone and the runner is one directory
|
|
||||||
over, in the engine's own package — worth finding, so trying this out does
|
|
||||||
not start with copying files around.
|
|
||||||
"""
|
|
||||||
here = Path(__file__).resolve().parent
|
|
||||||
for candidate in (here / "worker_main.py", here.parent / "flow" / "worker_main.py"):
|
|
||||||
if candidate.exists():
|
|
||||||
return candidate
|
|
||||||
return here / "worker_main.py"
|
|
||||||
|
|
||||||
|
|
||||||
WORKER_MAIN = _worker_main()
|
|
||||||
|
|
||||||
|
|
||||||
class Subprocess:
|
class Subprocess:
|
||||||
@@ -267,7 +254,10 @@ def _venv_digest(python: str) -> str:
|
|||||||
return hashlib.sha256(listing.encode()).hexdigest()[:16]
|
return hashlib.sha256(listing.encode()).hexdigest()[:16]
|
||||||
|
|
||||||
|
|
||||||
def main() -> int:
|
def main(argv: list[str] | None = None) -> int:
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s"
|
||||||
|
)
|
||||||
parser = argparse.ArgumentParser(description="Run Fluksio nodes on this machine.")
|
parser = argparse.ArgumentParser(description="Run Fluksio nodes on this machine.")
|
||||||
parser.add_argument("--url", required=True, help="wss://…/api/v1/workers/attach")
|
parser.add_argument("--url", required=True, help="wss://…/api/v1/workers/attach")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
@@ -288,7 +278,7 @@ def main() -> int:
|
|||||||
)
|
)
|
||||||
parser.add_argument("--parallel", type=int, default=1)
|
parser.add_argument("--parallel", type=int, default=1)
|
||||||
parser.add_argument("--artifact-url", default="")
|
parser.add_argument("--artifact-url", default="")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args(argv)
|
||||||
|
|
||||||
if not args.token:
|
if not args.token:
|
||||||
parser.error("a token is required (--token or FLUKSIO_WORKER_TOKEN)")
|
parser.error("a token is required (--token or FLUKSIO_WORKER_TOKEN)")
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
This runs under the *user* venv's interpreter, so a node's imports resolve
|
This runs under the *user* venv's interpreter, so a node's imports resolve
|
||||||
against what the Modules page installed rather than against the engine's own
|
against what the Modules page installed rather than against the engine's own
|
||||||
packages. Nothing of the app is importable here — the file is handed to the
|
packages. Nothing of the engine is importable here — the file is handed to the
|
||||||
interpreter by path and is deliberately standard library only.
|
interpreter by path and is deliberately standard library only.
|
||||||
|
|
||||||
The protocol is one JSON object per line: a request arrives on stdin, the reply
|
The protocol is one JSON object per line: a request arrives on stdin, the reply
|
||||||
@@ -33,7 +33,7 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
# Handed to the interpreter by path, so CPython puts this file's own directory
|
# Handed to the interpreter by path, so CPython puts this file's own directory
|
||||||
# — the engine's ``app/flow`` — on ``sys.path[0]``. A node doing ``import
|
# — ``fluksio_worker`` — on ``sys.path[0]``. A node doing ``import
|
||||||
# queue`` would then get the engine's queue module rather than the standard
|
# queue`` would then get the engine's queue module rather than the standard
|
||||||
# library's. Drop it before anything else can import. (``-P`` and
|
# library's. Drop it before anything else can import. (``-P`` and
|
||||||
# ``PYTHONSAFEPATH`` do this at startup, but both are 3.11+.)
|
# ``PYTHONSAFEPATH`` do this at startup, but both are 3.11+.)
|
||||||
@@ -131,8 +131,8 @@ 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
|
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
|
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
|
— the same flow runs either place. The hashing is repeated from
|
||||||
``fluksio.flow.artifacts`` rather than imported, because nothing of the app is
|
``fluksio.flow.artifacts`` rather than imported, because nothing of the engine
|
||||||
importable here.
|
is importable here.
|
||||||
"""
|
"""
|
||||||
digest = "sha256:" + hashlib.sha256(data).hexdigest()
|
digest = "sha256:" + hashlib.sha256(data).hexdigest()
|
||||||
directory = os.environ.get(ARTIFACT_DIR_ENV)
|
directory = os.environ.get(ARTIFACT_DIR_ENV)
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
[project]
|
||||||
|
name = "fluksio-worker"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Runs Fluksio nodes on a machine the engine cannot reach"
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.10"
|
||||||
|
dependencies = [
|
||||||
|
"websockets>=12",
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.scripts]
|
||||||
|
fluksio-worker = "fluksio_worker.agent:main"
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["hatchling"]
|
||||||
|
build-backend = "hatchling.build"
|
||||||
|
|
||||||
|
[tool.hatch.build.targets.wheel]
|
||||||
|
packages = ["fluksio_worker"]
|
||||||
|
|
||||||
|
[tool.ruff]
|
||||||
|
# The engine's config, so both distributions are linted the same way.
|
||||||
|
extend = "../backend/pyproject.toml"
|
||||||
|
|
||||||
|
[tool.ruff.lint.per-file-ignores]
|
||||||
|
# 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.
|
||||||
|
"fluksio_worker/worker_main.py" = ["E402"]
|
||||||
|
# Copied onto another machine it has no logger configured before it tells you
|
||||||
|
# the one dependency it is missing.
|
||||||
|
"fluksio_worker/agent.py" = ["T201"]
|
||||||
Reference in New Issue
Block a user