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:
@@ -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
|
||||
Reference in New Issue
Block a user