Put the deployment-only dependencies behind a server extra
A data-science environment installing fluksio waited for lxml, aiohttp and the rest of a connector stack it has nothing to talk to. Outbound mail, error reporting and the MQTT and InfluxDB clients moved to `fluksio[server]`, which the image installs; each import is guarded and names the extra. `tenacity` had no import site at all and is gone. 23 fewer packages and the compiled ones among them — a bare `pip install fluksio` still serves, runs every python node, and registers the mqtt and influxdb node types, which only need the library when one is actually built. sentry-sdk arrives anyway underneath `fastapi[standard]`; what changed there is that nothing of ours requires it. The dev environment keeps every extra: the suite exercises the connectors and strict mypy checks their call sites. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019Hra4ndWMCLU5F3KjUuVAc
This commit is contained in:
@@ -105,11 +105,13 @@ down: ## Stop all running containers
|
|||||||
# Run `make dev-backend` and `make dev-frontend` in two separate terminals.
|
# Run `make dev-backend` and `make dev-frontend` in two separate terminals.
|
||||||
|
|
||||||
install: ## Install all dependencies (backend + frontend)
|
install: ## Install all dependencies (backend + frontend)
|
||||||
cd backend && uv sync
|
# Every extra: the suite exercises the connectors that moved into
|
||||||
|
# `fluksio[server]`, and strict mypy checks their call sites.
|
||||||
|
cd backend && uv sync --all-extras
|
||||||
cd frontend && bun install
|
cd frontend && bun install
|
||||||
|
|
||||||
dev-backend: ## Start the FastAPI backend with hot-reload (local)
|
dev-backend: ## Start the FastAPI backend with hot-reload (local)
|
||||||
cd backend && uv run fastapi dev fluksio/main.py
|
cd backend && uv run --all-extras fastapi dev fluksio/main.py
|
||||||
|
|
||||||
dev-frontend: ## Start the Vite dev server (local)
|
dev-frontend: ## Start the Vite dev server (local)
|
||||||
cd frontend && bun dev
|
cd frontend && bun dev
|
||||||
@@ -130,7 +132,7 @@ test: test-backend test-frontend ## Run all tests (backend + frontend)
|
|||||||
test-backend: ## Run backend tests (pytest + coverage)
|
test-backend: ## Run backend tests (pytest + coverage)
|
||||||
# Its own SQLite file in a temp directory (tests/__init__.py), so this needs
|
# Its own SQLite file in a temp directory (tests/__init__.py), so this needs
|
||||||
# nothing running and touches no development data.
|
# nothing running and touches no development data.
|
||||||
cd backend && uv run bash scripts/test.sh
|
cd backend && uv run --all-extras bash scripts/test.sh
|
||||||
|
|
||||||
PW_VERSION = $(shell sed -n 's/.*"@playwright\/test": "[^0-9]*\([0-9.]*\)".*/\1/p' frontend/package.json | head -1)
|
PW_VERSION = $(shell sed -n 's/.*"@playwright\/test": "[^0-9]*\([0-9.]*\)".*/\1/p' frontend/package.json | head -1)
|
||||||
|
|
||||||
@@ -181,9 +183,9 @@ build: ## Build the fluksio and fluksio-worker wheels into dist/
|
|||||||
lint: lint-backend lint-frontend ## Run all linters
|
lint: lint-backend lint-frontend ## Run all linters
|
||||||
|
|
||||||
lint-backend: ## Lint backend with ruff + mypy
|
lint-backend: ## Lint backend with ruff + mypy
|
||||||
cd backend && uv run ruff check .
|
cd backend && uv run --all-extras ruff check .
|
||||||
cd backend && uv run ruff format --check .
|
cd backend && uv run --all-extras ruff format --check .
|
||||||
cd backend && uv run mypy fluksio
|
cd backend && uv run --all-extras mypy fluksio
|
||||||
cd worker && uv run --no-project --with mypy mypy fluksio_worker
|
cd worker && uv run --no-project --with mypy mypy fluksio_worker
|
||||||
|
|
||||||
lint-frontend: ## Lint frontend with biome
|
lint-frontend: ## Lint frontend with biome
|
||||||
|
|||||||
+2
-2
@@ -31,7 +31,7 @@ ENV NODE_VENV=managed
|
|||||||
RUN --mount=type=cache,target=/root/.cache/uv \
|
RUN --mount=type=cache,target=/root/.cache/uv \
|
||||||
--mount=type=bind,source=uv.lock,target=uv.lock \
|
--mount=type=bind,source=uv.lock,target=uv.lock \
|
||||||
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
|
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
|
||||||
uv sync --frozen --no-install-workspace --package fluksio
|
uv sync --frozen --no-install-workspace --package fluksio --extra server
|
||||||
|
|
||||||
COPY ./backend/scripts /app/backend/scripts
|
COPY ./backend/scripts /app/backend/scripts
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@ COPY ./worker /app/worker
|
|||||||
RUN --mount=type=cache,target=/root/.cache/uv \
|
RUN --mount=type=cache,target=/root/.cache/uv \
|
||||||
--mount=type=bind,source=uv.lock,target=uv.lock \
|
--mount=type=bind,source=uv.lock,target=uv.lock \
|
||||||
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
|
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
|
||||||
uv sync --frozen --package fluksio
|
uv sync --frozen --package fluksio --extra server
|
||||||
|
|
||||||
# Connectors are ordinary installed packages found through the
|
# Connectors are ordinary installed packages found through the
|
||||||
# `fluksio.node_types` entry point. `make connectors` builds them into here;
|
# `fluksio.node_types` entry point. `make connectors` builds them into here;
|
||||||
|
|||||||
@@ -14,6 +14,22 @@ from fluksio.flow.nodes.base import Node, NodeResult
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def _influxdb() -> Any:
|
||||||
|
"""The client library, which is a `fluksio[server]` extra.
|
||||||
|
|
||||||
|
Imported per use rather than at module level, because the node type is
|
||||||
|
registered at boot and an installation with no InfluxDB behind it should
|
||||||
|
not have to carry the library to start.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
import influxdb_client
|
||||||
|
except ImportError:
|
||||||
|
raise RuntimeError(
|
||||||
|
"the influxdb node needs the server extra: pip install 'fluksio[server]'"
|
||||||
|
) from None
|
||||||
|
return influxdb_client
|
||||||
|
|
||||||
|
|
||||||
class InfluxDbNode(Node):
|
class InfluxDbNode(Node):
|
||||||
"""
|
"""
|
||||||
InfluxDB node for writing to and reading from InfluxDB.
|
InfluxDB node for writing to and reading from InfluxDB.
|
||||||
@@ -290,7 +306,7 @@ class InfluxDbNode(Node):
|
|||||||
:returns: ``{"rows": [...], **echo}``.
|
:returns: ``{"rows": [...], **echo}``.
|
||||||
:rtype: dict
|
:rtype: dict
|
||||||
"""
|
"""
|
||||||
from influxdb_client import InfluxDBClient
|
InfluxDBClient = _influxdb().InfluxDBClient
|
||||||
|
|
||||||
flux = str(request["flux"])
|
flux = str(request["flux"])
|
||||||
echo = {key: value for key, value in request.items() if key != "flux"}
|
echo = {key: value for key, value in request.items() if key != "flux"}
|
||||||
@@ -338,8 +354,10 @@ class InfluxDbNode(Node):
|
|||||||
- Dict with "value" and "tags" keys: Value written with merged tags
|
- Dict with "value" and "tags" keys: Value written with merged tags
|
||||||
:type data: dict
|
:type data: dict
|
||||||
"""
|
"""
|
||||||
from influxdb_client import InfluxDBClient, Point, WritePrecision
|
influxdb_client = _influxdb()
|
||||||
from influxdb_client.client.write_api import SYNCHRONOUS
|
InfluxDBClient = influxdb_client.InfluxDBClient
|
||||||
|
Point, WritePrecision = influxdb_client.Point, influxdb_client.WritePrecision
|
||||||
|
SYNCHRONOUS = influxdb_client.client.write_api.SYNCHRONOUS
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with InfluxDBClient(
|
with InfluxDBClient(
|
||||||
@@ -438,7 +456,7 @@ class InfluxDbNode(Node):
|
|||||||
:returns: Dict of port name to queried value.
|
:returns: Dict of port name to queried value.
|
||||||
:rtype: dict
|
:rtype: dict
|
||||||
"""
|
"""
|
||||||
from influxdb_client import InfluxDBClient
|
InfluxDBClient = _influxdb().InfluxDBClient
|
||||||
|
|
||||||
results = {}
|
results = {}
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,22 @@ if TYPE_CHECKING:
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def _aiomqtt() -> Any:
|
||||||
|
"""The client library, which is a `fluksio[server]` extra.
|
||||||
|
|
||||||
|
Imported per use rather than at module level, because the node type is
|
||||||
|
registered at boot and an installation that talks to no broker should not
|
||||||
|
have to carry the library to start.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
import aiomqtt
|
||||||
|
except ImportError:
|
||||||
|
raise RuntimeError(
|
||||||
|
"the mqtt node needs the server extra: pip install 'fluksio[server]'"
|
||||||
|
) from None
|
||||||
|
return aiomqtt
|
||||||
|
|
||||||
|
|
||||||
def topic_matches(filter_: str, topic: str) -> bool:
|
def topic_matches(filter_: str, topic: str) -> bool:
|
||||||
"""Does an MQTT topic filter cover this topic?
|
"""Does an MQTT topic filter cover this topic?
|
||||||
|
|
||||||
@@ -382,7 +398,7 @@ class MqttNode(Node):
|
|||||||
A dropped connection raises, and the supervisor decides when to
|
A dropped connection raises, and the supervisor decides when to
|
||||||
reconnect — the same arrangement the subscriber uses.
|
reconnect — the same arrangement the subscriber uses.
|
||||||
"""
|
"""
|
||||||
import aiomqtt
|
aiomqtt = _aiomqtt()
|
||||||
|
|
||||||
queue = self._publish_queue
|
queue = self._publish_queue
|
||||||
if queue is None:
|
if queue is None:
|
||||||
@@ -408,7 +424,7 @@ class MqttNode(Node):
|
|||||||
|
|
||||||
async def _publish_once(self, data: dict[str, Any]) -> None:
|
async def _publish_once(self, data: dict[str, Any]) -> None:
|
||||||
"""Connect, publish, disconnect — the unstarted node's path."""
|
"""Connect, publish, disconnect — the unstarted node's path."""
|
||||||
import aiomqtt
|
aiomqtt = _aiomqtt()
|
||||||
|
|
||||||
async with aiomqtt.Client(
|
async with aiomqtt.Client(
|
||||||
hostname=self.broker_host,
|
hostname=self.broker_host,
|
||||||
@@ -570,7 +586,7 @@ class MqttNode(Node):
|
|||||||
"""
|
"""
|
||||||
import json
|
import json
|
||||||
|
|
||||||
import aiomqtt
|
aiomqtt = _aiomqtt()
|
||||||
|
|
||||||
if not (self._stop_event and self._stop_event.is_set()):
|
if not (self._stop_event and self._stop_event.is_set()):
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import logging
|
|||||||
from collections.abc import AsyncIterator
|
from collections.abc import AsyncIterator
|
||||||
from contextlib import AbstractAsyncContextManager, asynccontextmanager
|
from contextlib import AbstractAsyncContextManager, asynccontextmanager
|
||||||
|
|
||||||
import sentry_sdk
|
|
||||||
from fastapi import FastAPI, Request
|
from fastapi import FastAPI, Request
|
||||||
from fastapi.concurrency import run_in_threadpool
|
from fastapi.concurrency import run_in_threadpool
|
||||||
from fastapi.responses import JSONResponse
|
from fastapi.responses import JSONResponse
|
||||||
@@ -51,6 +50,10 @@ def custom_generate_unique_id(route: APIRoute) -> str:
|
|||||||
|
|
||||||
|
|
||||||
if settings.SENTRY_DSN and settings.ENVIRONMENT != "local":
|
if settings.SENTRY_DSN and settings.ENVIRONMENT != "local":
|
||||||
|
# Imported here rather than at the top: it is a `fluksio[server]` extra, so
|
||||||
|
# a pip install without one has no sentry to import — and no DSN either.
|
||||||
|
import sentry_sdk
|
||||||
|
|
||||||
# `enable_tracing` was removed in sentry-sdk 2.x; this is what it meant.
|
# `enable_tracing` was removed in sentry-sdk 2.x; this is what it meant.
|
||||||
sentry_sdk.init(dsn=str(settings.SENTRY_DSN), traces_sample_rate=1.0)
|
sentry_sdk.init(dsn=str(settings.SENTRY_DSN), traces_sample_rate=1.0)
|
||||||
|
|
||||||
|
|||||||
@@ -485,14 +485,14 @@ def _params(definition: dict[str, Any], rest: list[str]) -> dict[str, Any]:
|
|||||||
f"{', '.join(sorted(types)) or 'none'}){hint}"
|
f"{', '.join(sorted(types)) or 'none'}){hint}"
|
||||||
)
|
)
|
||||||
params: dict[str, Any] = {}
|
params: dict[str, Any] = {}
|
||||||
for name, value in raw.items():
|
for key, written in raw.items():
|
||||||
if value is True:
|
if isinstance(written, bool):
|
||||||
params[name] = True
|
params[key] = written
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
params[name] = _coerce(value, types[name])
|
params[key] = _coerce(written, types[key])
|
||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
raise SyncError(f"'{name}' takes {types[name]}: {exc}") from exc
|
raise SyncError(f"'{key}' takes {types[key]}: {exc}") from exc
|
||||||
return params
|
return params
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ from datetime import UTC, datetime, timedelta
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import emails # type: ignore
|
|
||||||
import jwt
|
import jwt
|
||||||
from jinja2 import Template
|
from jinja2 import Template
|
||||||
from jwt.exceptions import InvalidTokenError
|
from jwt.exceptions import InvalidTokenError
|
||||||
@@ -37,6 +36,12 @@ def send_email(
|
|||||||
html_content: str = "",
|
html_content: str = "",
|
||||||
) -> None:
|
) -> None:
|
||||||
assert settings.emails_enabled, "no provided configuration for email variables"
|
assert settings.emails_enabled, "no provided configuration for email variables"
|
||||||
|
try:
|
||||||
|
import emails # type: ignore
|
||||||
|
except ImportError:
|
||||||
|
raise RuntimeError(
|
||||||
|
"sending mail needs the server extra: pip install 'fluksio[server]'"
|
||||||
|
) from None
|
||||||
message = emails.Message(
|
message = emails.Message(
|
||||||
subject=subject,
|
subject=subject,
|
||||||
html=html_content,
|
html=html_content,
|
||||||
|
|||||||
+11
-5
@@ -25,15 +25,12 @@ dependencies = [
|
|||||||
"fastapi[standard]<1.0.0,>=0.114.2",
|
"fastapi[standard]<1.0.0,>=0.114.2",
|
||||||
"python-multipart<1.0.0,>=0.0.7",
|
"python-multipart<1.0.0,>=0.0.7",
|
||||||
"email-validator<3.0.0.0,>=2.1.0.post1",
|
"email-validator<3.0.0.0,>=2.1.0.post1",
|
||||||
"tenacity<9.0.0,>=8.2.3",
|
|
||||||
"pydantic>2.0",
|
"pydantic>2.0",
|
||||||
"emails<1.0,>=0.6",
|
|
||||||
"jinja2<4.0.0,>=3.1.4",
|
"jinja2<4.0.0,>=3.1.4",
|
||||||
"alembic<2.0.0,>=1.12.1",
|
"alembic<2.0.0,>=1.12.1",
|
||||||
"httpx<1.0.0,>=0.25.1",
|
"httpx<1.0.0,>=0.25.1",
|
||||||
"sqlmodel<1.0.0,>=0.0.21",
|
"sqlmodel<1.0.0,>=0.0.21",
|
||||||
"pydantic-settings<3.0.0,>=2.2.1",
|
"pydantic-settings<3.0.0,>=2.2.1",
|
||||||
"sentry-sdk[fastapi]>=2.20.0",
|
|
||||||
"pyjwt<3.0.0,>=2.8.0",
|
"pyjwt<3.0.0,>=2.8.0",
|
||||||
"pwdlib[argon2,bcrypt]>=0.3.0",
|
"pwdlib[argon2,bcrypt]>=0.3.0",
|
||||||
"numpy>=2.2.6",
|
"numpy>=2.2.6",
|
||||||
@@ -43,8 +40,6 @@ dependencies = [
|
|||||||
# copied onto other people's machines and stays dependency-free.
|
# copied onto other people's machines and stays dependency-free.
|
||||||
"orjson>=3.10",
|
"orjson>=3.10",
|
||||||
"cryptography>=44.0.0",
|
"cryptography>=44.0.0",
|
||||||
"aiomqtt>=2.0.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",
|
"fluksio-worker>=0.1,<0.2",
|
||||||
@@ -62,6 +57,17 @@ dependencies = [
|
|||||||
# extra rather than a dependency: csv and jsonl need nothing, and pyarrow is
|
# extra rather than a dependency: csv and jsonl need nothing, and pyarrow is
|
||||||
# tens of megabytes for whoever wants dtypes kept.
|
# tens of megabytes for whoever wants dtypes kept.
|
||||||
parquet = ["pyarrow>=17"]
|
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]
|
[project.urls]
|
||||||
Homepage = "https://fluksio.com"
|
Homepage = "https://fluksio.com"
|
||||||
|
|||||||
@@ -433,9 +433,7 @@ def test_a_run_records_the_inputs_it_actually_starts_from():
|
|||||||
name="study",
|
name="study",
|
||||||
mode="batch",
|
mode="batch",
|
||||||
inputs=[
|
inputs=[
|
||||||
FlowInput(
|
FlowInput(spec=MessageSpec(name="lr", dtype=DType.FLOAT), initial=0.01),
|
||||||
spec=MessageSpec(name="lr", dtype=DType.FLOAT), initial=0.01
|
|
||||||
),
|
|
||||||
FlowInput(spec=MessageSpec(name="epochs", dtype=DType.INT)),
|
FlowInput(spec=MessageSpec(name="epochs", dtype=DType.INT)),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -872,16 +872,13 @@ name = "fluksio"
|
|||||||
version = "0.1.4+dev"
|
version = "0.1.4+dev"
|
||||||
source = { editable = "backend" }
|
source = { editable = "backend" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "aiomqtt" },
|
|
||||||
{ name = "alembic" },
|
{ name = "alembic" },
|
||||||
{ name = "croniter" },
|
{ name = "croniter" },
|
||||||
{ name = "cryptography" },
|
{ name = "cryptography" },
|
||||||
{ name = "email-validator" },
|
{ name = "email-validator" },
|
||||||
{ name = "emails" },
|
|
||||||
{ name = "fastapi", extra = ["standard"] },
|
{ name = "fastapi", extra = ["standard"] },
|
||||||
{ name = "fluksio-worker" },
|
{ name = "fluksio-worker" },
|
||||||
{ name = "httpx" },
|
{ name = "httpx" },
|
||||||
{ name = "influxdb-client", extra = ["async"] },
|
|
||||||
{ name = "jinja2" },
|
{ name = "jinja2" },
|
||||||
{ name = "mcp" },
|
{ name = "mcp" },
|
||||||
{ name = "numpy" },
|
{ name = "numpy" },
|
||||||
@@ -893,9 +890,7 @@ dependencies = [
|
|||||||
{ name = "python-multipart" },
|
{ name = "python-multipart" },
|
||||||
{ name = "redis" },
|
{ name = "redis" },
|
||||||
{ name = "rich" },
|
{ name = "rich" },
|
||||||
{ name = "sentry-sdk", extra = ["fastapi"] },
|
|
||||||
{ name = "sqlmodel" },
|
{ name = "sqlmodel" },
|
||||||
{ name = "tenacity" },
|
|
||||||
{ name = "uv" },
|
{ name = "uv" },
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -903,6 +898,12 @@ dependencies = [
|
|||||||
parquet = [
|
parquet = [
|
||||||
{ name = "pyarrow" },
|
{ name = "pyarrow" },
|
||||||
]
|
]
|
||||||
|
server = [
|
||||||
|
{ name = "aiomqtt" },
|
||||||
|
{ name = "emails" },
|
||||||
|
{ name = "influxdb-client", extra = ["async"] },
|
||||||
|
{ name = "sentry-sdk", extra = ["fastapi"] },
|
||||||
|
]
|
||||||
|
|
||||||
[package.dev-dependencies]
|
[package.dev-dependencies]
|
||||||
dev = [
|
dev = [
|
||||||
@@ -915,16 +916,16 @@ dev = [
|
|||||||
|
|
||||||
[package.metadata]
|
[package.metadata]
|
||||||
requires-dist = [
|
requires-dist = [
|
||||||
{ name = "aiomqtt", specifier = ">=2.0.0" },
|
{ name = "aiomqtt", marker = "extra == 'server'", specifier = ">=2.0.0" },
|
||||||
{ name = "alembic", specifier = ">=1.12.1,<2.0.0" },
|
{ name = "alembic", specifier = ">=1.12.1,<2.0.0" },
|
||||||
{ name = "croniter", specifier = ">=1.3.0" },
|
{ name = "croniter", specifier = ">=1.3.0" },
|
||||||
{ name = "cryptography", specifier = ">=44.0.0" },
|
{ name = "cryptography", specifier = ">=44.0.0" },
|
||||||
{ 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", marker = "extra == 'server'", 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 = "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"], marker = "extra == 'server'", specifier = ">=1.40.0" },
|
||||||
{ name = "jinja2", specifier = ">=3.1.4,<4.0.0" },
|
{ name = "jinja2", specifier = ">=3.1.4,<4.0.0" },
|
||||||
{ name = "mcp", specifier = ">=1.29,<2" },
|
{ name = "mcp", specifier = ">=1.29,<2" },
|
||||||
{ name = "numpy", specifier = ">=2.2.6" },
|
{ name = "numpy", specifier = ">=2.2.6" },
|
||||||
@@ -937,12 +938,11 @@ requires-dist = [
|
|||||||
{ name = "python-multipart", specifier = ">=0.0.7,<1.0.0" },
|
{ name = "python-multipart", specifier = ">=0.0.7,<1.0.0" },
|
||||||
{ name = "redis", specifier = ">=7.1.0" },
|
{ name = "redis", specifier = ">=7.1.0" },
|
||||||
{ name = "rich", specifier = ">=13" },
|
{ name = "rich", specifier = ">=13" },
|
||||||
{ name = "sentry-sdk", extras = ["fastapi"], specifier = ">=2.20.0" },
|
{ name = "sentry-sdk", extras = ["fastapi"], marker = "extra == 'server'", specifier = ">=2.20.0" },
|
||||||
{ name = "sqlmodel", specifier = ">=0.0.21,<1.0.0" },
|
{ name = "sqlmodel", specifier = ">=0.0.21,<1.0.0" },
|
||||||
{ name = "tenacity", specifier = ">=8.2.3,<9.0.0" },
|
|
||||||
{ name = "uv", specifier = ">=0.5" },
|
{ name = "uv", specifier = ">=0.5" },
|
||||||
]
|
]
|
||||||
provides-extras = ["parquet"]
|
provides-extras = ["parquet", "server"]
|
||||||
|
|
||||||
[package.metadata.requires-dev]
|
[package.metadata.requires-dev]
|
||||||
dev = [
|
dev = [
|
||||||
@@ -2673,15 +2673,6 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/c8/cb/6a6a47d5b464bd08695d254f3da6e7986cc70c9fa5d778eda57538edfe56/starlette-1.6.0-py3-none-any.whl", hash = "sha256:a86dd39d14bb45f85a3d18525215a9ef0cfd1f192ac793220e72598c90335f0c", size = 75969, upload-time = "2026-08-08T18:27:56.196Z" },
|
{ url = "https://files.pythonhosted.org/packages/c8/cb/6a6a47d5b464bd08695d254f3da6e7986cc70c9fa5d778eda57538edfe56/starlette-1.6.0-py3-none-any.whl", hash = "sha256:a86dd39d14bb45f85a3d18525215a9ef0cfd1f192ac793220e72598c90335f0c", size = 75969, upload-time = "2026-08-08T18:27:56.196Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tenacity"
|
|
||||||
version = "8.5.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/a3/4d/6a19536c50b849338fcbe9290d562b52cbdcf30d8963d3588a68a4107df1/tenacity-8.5.0.tar.gz", hash = "sha256:8bc6c0c8a09b31e6cad13c47afbed1a567518250a9a171418582ed8d9c20ca78", size = 47309, upload-time = "2024-07-05T07:25:31.836Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/d2/3f/8ba87d9e287b9d385a02a7114ddcef61b26f86411e121c9003eb509a1773/tenacity-8.5.0-py3-none-any.whl", hash = "sha256:b594c2a5945830c267ce6b79a166228323ed52718f30302c1359836112346687", size = 28165, upload-time = "2024-07-05T07:25:29.591Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typer"
|
name = "typer"
|
||||||
version = "0.21.1"
|
version = "0.21.1"
|
||||||
|
|||||||
@@ -89,6 +89,10 @@ def _emit(event: dict[str, Any]) -> None:
|
|||||||
class _Reporter(ModuleType):
|
class _Reporter(ModuleType):
|
||||||
"""``import fluksio`` — the parts of a node's job that need the engine."""
|
"""``import fluksio`` — the parts of a node's job that need the engine."""
|
||||||
|
|
||||||
|
#: The same name the SDK exports at top level, so a node written against
|
||||||
|
#: one surface finds it on the other. Set by ``_install_reporter``.
|
||||||
|
logger: logging.Logger
|
||||||
|
|
||||||
def emit(self, **ports: Any) -> None:
|
def emit(self, **ports: Any) -> None:
|
||||||
"""Publish on this node's output ports without returning yet.
|
"""Publish on this node's output ports without returning yet.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user