Rename the import package app to fluksio

A wheel whose top-level module is `app` collides with anything else in a
user's venv, so the package that is about to be published takes the name
it is published under. Only the Python package moves; the repo, the
Docker WORKDIR and the compose project keep theirs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-21 21:48:05 +02:00
co-authored by Claude Opus 5
parent 97785ee590
commit 60d7ec81c0
170 changed files with 629 additions and 619 deletions
+87
View File
@@ -0,0 +1,87 @@
"""Ntfy: push a notification to a phone.
The flow-level counterpart to the engine's own alerting — this is a flow
deciding something is worth saying, rather than the engine reporting that it
broke.
"""
from __future__ import annotations
import logging
from collections.abc import Iterable
from typing import Any
from pydantic import BaseModel, ConfigDict, Field
from fluksio.flow.messages import MessageSpec
from fluksio.flow.nodes.base import Node
from fluksio.flow.nodes.http import shared_client
logger = logging.getLogger(__name__)
class NtfyNode(Node):
"""Publish an incoming value as a notification on an ntfy topic."""
# A notification sent twice is read twice.
idempotent = False
class Params(BaseModel):
model_config = ConfigDict(extra="allow")
server: str = Field(
default="https://ntfy.sh", description="Base URL of the ntfy server."
)
topic: str = Field(description="Topic to publish to.")
title: str = Field(default="", description="Notification title.")
priority: str = Field(
default="default",
description="min, low, default, high or urgent.",
)
tags: str = Field(default="", description="Comma-separated ntfy tags.")
token: str = Field(
default="",
description="Access token, for a server that needs one.",
json_schema_extra={"x-secret": True},
)
timeout: float = Field(default=10.0, gt=0)
__slots__ = ("cfg",)
def __init__(
self,
requires: MessageSpec | Iterable[MessageSpec] = (),
provides: MessageSpec | Iterable[MessageSpec] = (),
params: dict[str, Any] | None = None,
name: str | None = None,
):
self.cfg = self.Params.model_validate(params or {})
super().__init__(
f=self._notify,
requires=requires,
provides=provides,
params=params,
name=name or "ntfy",
)
def _notify(self, params: dict[str, Any], **kwargs: Any) -> dict[str, Any] | None:
if not kwargs:
return None
body = str(next(iter(kwargs.values())))
headers = {"Priority": self.cfg.priority}
if self.cfg.title:
headers["Title"] = self.cfg.title
if self.cfg.tags:
headers["Tags"] = self.cfg.tags
if self.cfg.token:
headers["Authorization"] = f"Bearer {self.cfg.token}"
response = shared_client().post(
f"{self.cfg.server.rstrip('/')}/{self.cfg.topic}",
content=body.encode(),
headers=headers,
timeout=self.cfg.timeout,
)
response.raise_for_status()
return None