From 1344d1f7e9f72391844fe60b0cfd37183ab6d453 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dieter=20Schl=C3=BCter?= Date: Thu, 25 Jun 2026 13:00:02 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20Absender/Titel=20frei=20w=C3=A4hlbar,?= =?UTF-8?q?=20kein=20"Notruf"-Default=20mehr=20(0.1.2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "Notruf" war als Library/CLI-Default falsch — das gehört in die Bridge-Config, nicht ins Allzweck-Tool. Jetzt: - kein eingebauter Default-Absender; ohne Angabe nutzt seven.io den Kontostandard-Absender (send_sms lässt "from" dann weg) - CLI --from für SMS (Absender/Titel) und Voice (Caller-ID); zusätzlich $SEVEN_SMS_FROM / $SEVEN_VOICE_FROM als persistenter Default - Tests: Default ohne Absender, per-call-Override, CLI --from + Env (19 gesamt) - README: Absender/Titel dokumentiert Die Bridge übergibt sms_from="Notruf" weiterhin explizit -> Voice Assistant unverändert. Co-Authored-By: Claude Opus 4.8 --- README.md | 10 ++++++++-- pyproject.toml | 2 +- src/seven_send/__init__.py | 2 +- src/seven_send/cli.py | 12 +++++++++--- src/seven_send/client.py | 9 ++++++--- tests/test_cli.py | 24 ++++++++++++++++++++++++ tests/test_client.py | 15 +++++++++++++++ 7 files changed, 64 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 7a6f5cf..35c6f8a 100644 --- a/README.md +++ b/README.md @@ -19,12 +19,18 @@ pip install -e ".[dev]" export SEVEN_API_KEY=dein-key seven-send sms --to +4915112345678 --text "Hallo Welt" +seven-send sms --to +4915112345678 --text "Hallo Welt" --from "MeineFirma" # Absender/Titel seven-send voice --to +4915112345678 --text "Dies wird vorgelesen." --voice de-de-female seven-send --dry-run sms --to +49… --text "nur testen" # nichts senden seven-send --json sms --to +49… --text "..." # maschinenlesbar ``` +**Absender/Titel:** Per `--from` setzbar (SMS: alphanumerischer Titel oder Nummer; +Voice: verifizierte Caller-ID). Ohne `--from` greift `$SEVEN_SMS_FROM` / +`$SEVEN_VOICE_FROM`, sonst der **Kontostandard-Absender** von seven.io — es gibt +**keinen** eingebauten Default-Absender. + Exit-Codes: `0` Erfolg · `1` Sendefehler · `2` Konfig-/Aufruf-Fehler. ## Library @@ -32,7 +38,7 @@ Exit-Codes: `0` Erfolg · `1` Sendefehler · `2` Konfig-/Aufruf-Fehler. ```python from seven_send import SevenClient -c = SevenClient(api_key, sms_from="Notruf", voice_name="de-de-female") +c = SevenClient(api_key, sms_from="MeineFirma", voice_name="de-de-female") res = c.send_sms("+4915112345678", "Hallo") if not res: # bool(res) == res.ok @@ -52,7 +58,7 @@ res = await asyncio.to_thread(c.send_sms, "+49…", "Hallo") | Parameter | Default | Bedeutung | |-----------|---------|-----------| | `api_key` | – | seven.io API-Key (oder `$SEVEN_API_KEY` für die CLI) | -| `sms_from` | `Notruf` | Absender-ID der SMS (alphanumerisch, max. 11 Zeichen) | +| `sms_from` | `None` | Absender-ID/Titel der SMS (alphanumerisch max. 11 Zeichen oder Nummer); leer = Kontostandard | | `voice_from` | `None` | Caller-ID für Anrufe (verifizierte Nummer) | | `voice_name` | `de-de-female` | SSML-Stimme für die Anruf-Ansage | | `dry_run` | `False` | nichts senden, nur protokollieren | diff --git a/pyproject.toml b/pyproject.toml index d134fad..37b5de4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "seven-send" -version = "0.1.1" +version = "0.1.2" description = "Kleine Library + CLI zum Verschicken von SMS und TTS-Sprachanrufen über seven.io" readme = "README.md" requires-python = ">=3.10" diff --git a/src/seven_send/__init__.py b/src/seven_send/__init__.py index 8728c3c..b3f2cd6 100644 --- a/src/seven_send/__init__.py +++ b/src/seven_send/__init__.py @@ -3,4 +3,4 @@ from seven_send.client import Result, SevenClient, SevenError __all__ = ["SevenClient", "Result", "SevenError"] -__version__ = "0.1.1" +__version__ = "0.1.2" diff --git a/src/seven_send/cli.py b/src/seven_send/cli.py index ad3012e..f0f69d8 100644 --- a/src/seven_send/cli.py +++ b/src/seven_send/cli.py @@ -8,7 +8,7 @@ import logging import os import sys -from seven_send.client import DEFAULT_SMS_FROM, DEFAULT_VOICE_NAME, Result, SevenClient, SevenError +from seven_send.client import DEFAULT_VOICE_NAME, Result, SevenClient, SevenError def _build_parser() -> argparse.ArgumentParser: @@ -28,12 +28,18 @@ def _build_parser() -> argparse.ArgumentParser: sms = sub.add_parser("sms", help="SMS senden") sms.add_argument("--to", required=True, help="Empfänger (E.164, z. B. +4915112345678)") sms.add_argument("--text", required=True) - sms.add_argument("--from", dest="sender", default=DEFAULT_SMS_FROM, help="Absender-ID") + sms.add_argument( + "--from", dest="sender", default=os.getenv("SEVEN_SMS_FROM") or None, + help="Absender-ID/Titel (Default: $SEVEN_SMS_FROM, sonst seven.io-Kontostandard)", + ) voice = sub.add_parser("voice", help="TTS-Anruf auslösen") voice.add_argument("--to", required=True, help="Empfänger (E.164)") voice.add_argument("--text", required=True, help="vorzulesender Text") - voice.add_argument("--from", dest="sender", default=None, help="Caller-ID (verifizierte Nummer)") + voice.add_argument( + "--from", dest="sender", default=os.getenv("SEVEN_VOICE_FROM") or None, + help="Caller-ID (verifizierte Nummer; Default: $SEVEN_VOICE_FROM)", + ) voice.add_argument("--voice", dest="voice_name", default=DEFAULT_VOICE_NAME, help="SSML-Stimme") voice.add_argument("--no-ssml", dest="ssml", action="store_false", help="Text roh ohne SSML senden") return p diff --git a/src/seven_send/client.py b/src/seven_send/client.py index 79675a6..bdf97e5 100644 --- a/src/seven_send/client.py +++ b/src/seven_send/client.py @@ -17,8 +17,8 @@ import httpx logger = logging.getLogger("seven_send") BASE_URL = "https://gateway.seven.io/api" -DEFAULT_SMS_FROM = "Notruf" DEFAULT_VOICE_NAME = "de-de-female" +# Kein Default-Absender: ohne Angabe nutzt seven.io den Kontostandard-Absender. class SevenError(Exception): @@ -51,7 +51,7 @@ class SevenClient: self, api_key: str, *, - sms_from: str = DEFAULT_SMS_FROM, + sms_from: str | None = None, voice_from: str | None = None, voice_name: str = DEFAULT_VOICE_NAME, timeout: float = 15.0, @@ -70,7 +70,10 @@ class SevenClient: # -- öffentlich --------------------------------------------------------- def send_sms(self, to: str, text: str, *, sender: str | None = None) -> Result: - data = {"to": to, "text": text, "from": sender or self.sms_from} + data = {"to": to, "text": text} + frm = sender or self.sms_from + if frm: # leer/None -> seven.io-Kontostandard-Absender + data["from"] = frm return self._post("sms", to, data) def send_voice( diff --git a/tests/test_cli.py b/tests/test_cli.py index 2df0f45..934cd6d 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -51,3 +51,27 @@ def test_cli_missing_key(monkeypatch, capsys): rc = cli.main(["sms", "--to", "+49", "--text", "Hi"]) assert rc == 2 assert "Konfig-Fehler" in capsys.readouterr().err + + +def test_cli_sms_from_flag(monkeypatch): + monkeypatch.delenv("SEVEN_SMS_FROM", raising=False) + cap = [] + _patch(monkeypatch, cap) + cli.main(["--api-key", "k", "sms", "--to", "+49", "--text", "Hi", "--from", "Firma"]) + assert cap[0]["data"]["from"] == "Firma" + + +def test_cli_sms_no_from_by_default(monkeypatch): + monkeypatch.delenv("SEVEN_SMS_FROM", raising=False) + cap = [] + _patch(monkeypatch, cap) + cli.main(["--api-key", "k", "sms", "--to", "+49", "--text", "Hi"]) + assert "from" not in cap[0]["data"] + + +def test_cli_sms_from_env(monkeypatch): + monkeypatch.setenv("SEVEN_SMS_FROM", "EnvAbs") + cap = [] + _patch(monkeypatch, cap) + cli.main(["--api-key", "k", "sms", "--to", "+49", "--text", "Hi"]) + assert cap[0]["data"]["from"] == "EnvAbs" diff --git a/tests/test_client.py b/tests/test_client.py index cee4f10..534b5a7 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -54,6 +54,21 @@ def test_voice_no_ssml(monkeypatch): assert cap[0]["data"]["text"] == "roh" +def test_send_sms_default_omits_sender(monkeypatch): + # Ohne sms_from kein "from" -> seven.io nutzt den Kontostandard-Absender + cap = [] + _patch_post(monkeypatch, cap, FakeResp(payload={"success": "100"})) + SevenClient("key").send_sms("+49", "Hi") + assert "from" not in cap[0]["data"] + + +def test_send_sms_sender_override(monkeypatch): + cap = [] + _patch_post(monkeypatch, cap, FakeResp(payload={"success": "100"})) + SevenClient("key", sms_from="Firma").send_sms("+49", "Hi", sender="Aktion") + assert cap[0]["data"]["from"] == "Aktion" # per-call schlägt Instanz-Default + + def test_failure_code(monkeypatch): cap = [] _patch_post(monkeypatch, cap, FakeResp(payload={"success": "401"}))