multi_notifier 0.6.2__tar.gz → 0.6.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. multi_notifier-0.6.3/CLAUDE.md +83 -0
  2. {multi_notifier-0.6.2 → multi_notifier-0.6.3}/PKG-INFO +1 -1
  3. {multi_notifier-0.6.2 → multi_notifier-0.6.3}/changelog.md +4 -0
  4. {multi_notifier-0.6.2 → multi_notifier-0.6.3}/multi_notifier/__init__.py +1 -1
  5. {multi_notifier-0.6.2 → multi_notifier-0.6.3}/multi_notifier/connectors/connector_mail.py +1 -1
  6. {multi_notifier-0.6.2 → multi_notifier-0.6.3}/tests/connectors/test_connector_mail.py +4 -0
  7. {multi_notifier-0.6.2 → multi_notifier-0.6.3}/.github/workflows/publish_pypi.yml +0 -0
  8. {multi_notifier-0.6.2 → multi_notifier-0.6.3}/.github/workflows/run_tests.yml +0 -0
  9. {multi_notifier-0.6.2 → multi_notifier-0.6.3}/.yamlfmt.yaml +0 -0
  10. {multi_notifier-0.6.2 → multi_notifier-0.6.3}/LICENSE +0 -0
  11. {multi_notifier-0.6.2 → multi_notifier-0.6.3}/multi_notifier/connectors/__init__.py +0 -0
  12. {multi_notifier-0.6.2 → multi_notifier-0.6.3}/multi_notifier/connectors/connector_telegram.py +0 -0
  13. {multi_notifier-0.6.2 → multi_notifier-0.6.3}/multi_notifier/connectors/exceptions.py +0 -0
  14. {multi_notifier-0.6.2 → multi_notifier-0.6.3}/multi_notifier/connectors/interface.py +0 -0
  15. {multi_notifier-0.6.2 → multi_notifier-0.6.3}/multi_notifier/exceptions.py +0 -0
  16. {multi_notifier-0.6.2 → multi_notifier-0.6.3}/multi_notifier/py.typed +0 -0
  17. {multi_notifier-0.6.2 → multi_notifier-0.6.3}/prek.toml +0 -0
  18. {multi_notifier-0.6.2 → multi_notifier-0.6.3}/pyproject.toml +0 -0
  19. {multi_notifier-0.6.2 → multi_notifier-0.6.3}/scripts/__init__.py +0 -0
  20. {multi_notifier-0.6.2 → multi_notifier-0.6.3}/scripts/version_check.py +0 -0
  21. {multi_notifier-0.6.2 → multi_notifier-0.6.3}/tests/__init__.py +0 -0
  22. {multi_notifier-0.6.2 → multi_notifier-0.6.3}/tests/connectors/__init__.py +0 -0
  23. {multi_notifier-0.6.2 → multi_notifier-0.6.3}/tests/connectors/conftest.py +0 -0
  24. {multi_notifier-0.6.2 → multi_notifier-0.6.3}/tests/connectors/test_connector_telegram.py +0 -0
  25. {multi_notifier-0.6.2 → multi_notifier-0.6.3}/uv.lock +0 -0
@@ -0,0 +1,83 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Commands
6
+
7
+ All commands use `uv` as the package manager.
8
+
9
+ ```bash
10
+ # Install dependencies
11
+ uv sync --frozen --all-groups
12
+
13
+ # Lint (with autofix)
14
+ uv run ruff check --fix multi_notifier
15
+
16
+ # Format
17
+ uv run ruff format multi_notifier
18
+
19
+ # Type checking
20
+ uv run mypy multi_notifier
21
+
22
+ # Run tests with coverage
23
+ uv run pytest --cov=multi_notifier --cov-report=html --cov-report=term
24
+
25
+ # Run a single test file
26
+ uv run pytest tests/connectors/test_connector_mail.py
27
+
28
+ # Run all pre-commit hooks
29
+ uv run prek run --all-files
30
+ ```
31
+
32
+ ## Architecture
33
+
34
+ `multi_notifier` is a Python library for sending notifications over multiple protocols. The two implemented connectors are **Mail** (SMTP) and **Telegram** (via `aiogram`).
35
+
36
+ ### Key abstractions
37
+
38
+ - **`multi_notifier/connectors/interface.py`** — Abstract base class all connectors must implement:
39
+
40
+ - `send_message(recipient, message, subject=None, images=None)` — accepts a single recipient or a list
41
+ - `is_valid_recipient(recipient)` — static method for format validation
42
+
43
+ - **Config models** — each connector defines a Pydantic v2 `<Protocol>Config` model that is validated at init time. The Telegram connector also calls `_test_config()` (an async call wrapped in `asyncio.run()`) during `__init__` to verify the bot token against the API.
44
+
45
+ - **Exceptions** — `multi_notifier/connectors/exceptions.py` contains `ConnectorError`, `ConnectorConfigurationError`, and `ConnectorTimeoutError`; the top-level `multi_notifier/exceptions.py` holds `NotificationError` and `ConfigurationError`.
46
+
47
+ ### Email MIME details
48
+
49
+ The Mail connector builds MIME structure dynamically based on content:
50
+
51
+ | Content | MIME type |
52
+ |---|---|
53
+ | Plain text only | `multipart/alternative` |
54
+ | Plain text + images | `multipart/mixed` |
55
+ | HTML only | `multipart/alternative` |
56
+ | HTML + images | `multipart/related` |
57
+
58
+ Images are embedded via Content-ID for HTML and sent as attachments for plain text.
59
+
60
+ ### Telegram HTML
61
+
62
+ The Telegram connector uses `sulguk.transform_html()` to convert HTML into Telegram message entities. Images are sent as a media group (photos).
63
+
64
+ ### Adding a new connector
65
+
66
+ 1. Create `multi_notifier/connectors/connector_<protocol>.py`
67
+ 1. Define a `<Protocol>Config` Pydantic model
68
+ 1. Subclass `Interface` and implement `send_message` and `is_valid_recipient`
69
+ 1. Raise exceptions from `multi_notifier.connectors.exceptions`
70
+ 1. Add `tests/connectors/test_connector_<protocol>.py` with async-compatible tests
71
+
72
+ ## Changelog
73
+
74
+ Every change must be documented in `changelog.md` under a new version heading using the format `# Version X.Y.Z - DD.MM.YYYY`, and the version in `multi_notifier/__init__.py` must be bumped to match.
75
+
76
+ ## Code quality standards
77
+
78
+ - **Python** ≥ 3.11
79
+ - **Line length** 150 characters (ruff)
80
+ - **Docstrings** Google style
81
+ - **Coverage** 100% required (`fail_under = 100`)
82
+ - **Ruff** runs with `select = ["ALL"]` — check `pyproject.toml` for the explicit ignores list before adding new code
83
+ - Async tests use `pytest-asyncio`; mocking via `pytest-mock`
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: multi_notifier
3
- Version: 0.6.2
3
+ Version: 0.6.3
4
4
  Summary: notify multiple recipients on multiple protocols
5
5
  Author: Seuling N.
6
6
  License-Expression: Apache-2.0
@@ -1,3 +1,7 @@
1
+ # Version 0.6.3 - 17.05.2026
2
+
3
+ - fixed missing file suffix on image attachments in HTML mails on Android (added `Content-Disposition: inline; filename=...` to embedded images)
4
+
1
5
  # Version 0.6.2 - 12.05.2026
2
6
 
3
7
  - bumped requests (and other packages) to avoid security vulnerabilities
@@ -1,3 +1,3 @@
1
1
  """Init file of send_handler."""
2
2
 
3
- __version__ = "0.6.2"
3
+ __version__ = "0.6.3"
@@ -148,8 +148,8 @@ class Mail(multi_notifier.connectors.interface.Interface):
148
148
  try:
149
149
  img_data = image_path.read_bytes()
150
150
  img = MIMEImage(img_data)
151
- # Set Content-ID header: allows HTML to reference this image via src="cid:..."
152
151
  img.add_header("Content-ID", f"<{cid}>")
152
+ img.add_header("Content-Disposition", f'inline; filename="{image_path.name}"')
153
153
  msg.attach(img)
154
154
  except FileNotFoundError:
155
155
  LOGGER.warning(f"Image file not found: {image_path}")
@@ -100,6 +100,10 @@ def test_create_msg_structure(
100
100
  # HTML messages should have Content-ID for embedding
101
101
  assert "Content-ID" in image_part
102
102
  assert "<img1>" in image_part.get("Content-ID")
103
+ # Must also carry a filename so Android clients know the extension
104
+ assert "Content-Disposition" in image_part
105
+ assert "inline" in image_part.get("Content-Disposition")
106
+ assert "filename=" in image_part.get("Content-Disposition")
103
107
  else:
104
108
  # Plain text messages should have Content-Disposition for attachment
105
109
  assert "Content-Disposition" in image_part
File without changes
File without changes
File without changes