python-ntfy 0.2.3__tar.gz → 0.2.5__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.
- {python_ntfy-0.2.3 → python_ntfy-0.2.5}/PKG-INFO +13 -8
- {python_ntfy-0.2.3 → python_ntfy-0.2.5}/README.md +11 -7
- {python_ntfy-0.2.3 → python_ntfy-0.2.5}/pyproject.toml +6 -1
- {python_ntfy-0.2.3 → python_ntfy-0.2.5}/python_ntfy/__init__.py +18 -4
- {python_ntfy-0.2.3 → python_ntfy-0.2.5}/python_ntfy/_get_functions.py +1 -1
- {python_ntfy-0.2.3 → python_ntfy-0.2.5}/python_ntfy/_send_functions.py +24 -22
- {python_ntfy-0.2.3 → python_ntfy-0.2.5}/LICENSE +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: python-ntfy
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.5
|
|
4
4
|
Summary: An ntfy library aiming for feature completeness
|
|
5
5
|
Home-page: https://github.com/MatthewCane/python-ntfy
|
|
6
6
|
License: MIT
|
|
@@ -14,6 +14,7 @@ Classifier: Programming Language :: Python :: 3
|
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.11
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.12
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Requires-Dist: mypy (>=1.12.0,<2.0.0)
|
|
17
18
|
Requires-Dist: requests (>=2.31.0,<3.0.0)
|
|
18
19
|
Project-URL: Documentation, https://matthewcane.github.io/python-ntfy/
|
|
19
20
|
Project-URL: Repository, https://github.com/MatthewCane/python-ntfy
|
|
@@ -62,21 +63,25 @@ See the full documentation site at [https://matthewcane.github.io/python-ntfy/](
|
|
|
62
63
|
- Email notifications
|
|
63
64
|
- Send to multiple topics at once
|
|
64
65
|
|
|
65
|
-
##
|
|
66
|
+
## Testing and Development
|
|
66
67
|
|
|
67
68
|
This project uses:
|
|
68
69
|
|
|
69
70
|
- Poetry as it's dependency manager
|
|
70
71
|
- Ruff for linting and code formatting
|
|
72
|
+
- MyPy for static type checking
|
|
71
73
|
|
|
72
74
|
To install dev dependencies, run `poetry install --with dev`.
|
|
73
75
|
|
|
74
|
-
### Linting and
|
|
76
|
+
### Linting, Formatting and Type Checking
|
|
75
77
|
|
|
76
78
|
These can be run with:
|
|
77
79
|
|
|
78
80
|
- `poetry run ruff format`
|
|
79
81
|
- `poetry run ruff check`
|
|
82
|
+
- `poetry run mypy .`
|
|
83
|
+
|
|
84
|
+
These tools are also run in the CI pipeline and must pass before merging.
|
|
80
85
|
|
|
81
86
|
### Tests
|
|
82
87
|
|
|
@@ -87,11 +92,11 @@ You can run the tests against a local instance of `ntfy` *or* `ntfy.sh`.
|
|
|
87
92
|
#### Setup Steps
|
|
88
93
|
|
|
89
94
|
1. To test against a *local* `ntfy` instance:
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
2. To test against https://ntfy.sh
|
|
94
|
-
|
|
95
|
+
i. Create a container using `docker run -p 80:80 -it binwiederhier/ntfy serve --attachment-cache-dir=/cache --base-url=http://localhost`
|
|
96
|
+
ii. Set the following key in the `.env` file: `NTFY_SERVER=http://localhost`
|
|
97
|
+
iii. Add a dummy username and password to `.env` (see example.env)
|
|
98
|
+
2. To test against `https://ntfy.sh`:
|
|
99
|
+
i. Add username and password for ntfy.sh to `.env` (see example.env)
|
|
95
100
|
3. Run the tests with `poetry run pytest --cov`
|
|
96
101
|
|
|
97
102
|
The tests will sent messages to the `python_ntfy_testing` topic so you will need to subcribe to that topic to see the test messages.
|
|
@@ -41,21 +41,25 @@ See the full documentation site at [https://matthewcane.github.io/python-ntfy/](
|
|
|
41
41
|
- Email notifications
|
|
42
42
|
- Send to multiple topics at once
|
|
43
43
|
|
|
44
|
-
##
|
|
44
|
+
## Testing and Development
|
|
45
45
|
|
|
46
46
|
This project uses:
|
|
47
47
|
|
|
48
48
|
- Poetry as it's dependency manager
|
|
49
49
|
- Ruff for linting and code formatting
|
|
50
|
+
- MyPy for static type checking
|
|
50
51
|
|
|
51
52
|
To install dev dependencies, run `poetry install --with dev`.
|
|
52
53
|
|
|
53
|
-
### Linting and
|
|
54
|
+
### Linting, Formatting and Type Checking
|
|
54
55
|
|
|
55
56
|
These can be run with:
|
|
56
57
|
|
|
57
58
|
- `poetry run ruff format`
|
|
58
59
|
- `poetry run ruff check`
|
|
60
|
+
- `poetry run mypy .`
|
|
61
|
+
|
|
62
|
+
These tools are also run in the CI pipeline and must pass before merging.
|
|
59
63
|
|
|
60
64
|
### Tests
|
|
61
65
|
|
|
@@ -66,11 +70,11 @@ You can run the tests against a local instance of `ntfy` *or* `ntfy.sh`.
|
|
|
66
70
|
#### Setup Steps
|
|
67
71
|
|
|
68
72
|
1. To test against a *local* `ntfy` instance:
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
2. To test against https://ntfy.sh
|
|
73
|
-
|
|
73
|
+
i. Create a container using `docker run -p 80:80 -it binwiederhier/ntfy serve --attachment-cache-dir=/cache --base-url=http://localhost`
|
|
74
|
+
ii. Set the following key in the `.env` file: `NTFY_SERVER=http://localhost`
|
|
75
|
+
iii. Add a dummy username and password to `.env` (see example.env)
|
|
76
|
+
2. To test against `https://ntfy.sh`:
|
|
77
|
+
i. Add username and password for ntfy.sh to `.env` (see example.env)
|
|
74
78
|
3. Run the tests with `poetry run pytest --cov`
|
|
75
79
|
|
|
76
80
|
The tests will sent messages to the `python_ntfy_testing` topic so you will need to subcribe to that topic to see the test messages.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "python-ntfy"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.5"
|
|
4
4
|
description = "An ntfy library aiming for feature completeness"
|
|
5
5
|
authors = ["Matthew Cane <matthew.cane0@gmail.com>"]
|
|
6
6
|
readme = "README.md"
|
|
@@ -15,6 +15,7 @@ classifiers = [
|
|
|
15
15
|
[tool.poetry.dependencies]
|
|
16
16
|
python = "^3.11"
|
|
17
17
|
requests = "^2.31.0"
|
|
18
|
+
mypy = "^1.12.0"
|
|
18
19
|
|
|
19
20
|
[tool.poetry.group.dev.dependencies]
|
|
20
21
|
pytest = ">=7.4.1,<9.0.0"
|
|
@@ -24,6 +25,10 @@ pytest-codecov = ">=0.5.1,<0.7.0"
|
|
|
24
25
|
ruff = "^0.7.0"
|
|
25
26
|
mkdocs-material = "^9.5.41"
|
|
26
27
|
mkdocstrings-python = "^1.12.1"
|
|
28
|
+
types-pygments = "^2.18.0.20240506"
|
|
29
|
+
types-colorama = "^0.4.15.20240311"
|
|
30
|
+
types-requests = "^2.32.0.20241016"
|
|
31
|
+
types-setuptools = "^75.2.0.20241018"
|
|
27
32
|
|
|
28
33
|
[build-system]
|
|
29
34
|
requires = ["poetry-core"]
|
|
@@ -2,7 +2,12 @@ import os
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
class NtfyClient:
|
|
5
|
-
|
|
5
|
+
# The functions need to be imported here to:
|
|
6
|
+
# 1. Keep the functions in a separate file
|
|
7
|
+
# 2. Keep the docstrings working in the IDE
|
|
8
|
+
# 3. Allow the functions to be called with self
|
|
9
|
+
# MyPy does not like this, but it works
|
|
10
|
+
from ._send_functions import ( # type: ignore
|
|
6
11
|
send,
|
|
7
12
|
send_file,
|
|
8
13
|
MessagePriority,
|
|
@@ -10,18 +15,19 @@ class NtfyClient:
|
|
|
10
15
|
BroadcastAction,
|
|
11
16
|
HttpAction,
|
|
12
17
|
)
|
|
13
|
-
from ._get_functions import get_cached_messages
|
|
18
|
+
from ._get_functions import get_cached_messages # type: ignore
|
|
14
19
|
|
|
15
20
|
def __init__(
|
|
16
21
|
self,
|
|
17
22
|
topic: str,
|
|
18
23
|
server: str = "https://ntfy.sh",
|
|
19
|
-
):
|
|
24
|
+
) -> None:
|
|
20
25
|
"""
|
|
21
26
|
:param topic: The topic to use for this client
|
|
22
27
|
:param server: The server to connect to. Must include the protocol (http/https)
|
|
23
|
-
:return:
|
|
28
|
+
:return None:
|
|
24
29
|
"""
|
|
30
|
+
|
|
25
31
|
self._server = os.environ.get("NTFY_SERVER") or server
|
|
26
32
|
self._topic = topic
|
|
27
33
|
self.__set_url(self._server, topic)
|
|
@@ -45,3 +51,11 @@ class NtfyClient:
|
|
|
45
51
|
"""
|
|
46
52
|
self._topic = topic
|
|
47
53
|
self.__set_url(self._server, self._topic)
|
|
54
|
+
|
|
55
|
+
def get_topic(self):
|
|
56
|
+
"""
|
|
57
|
+
Get the current topic
|
|
58
|
+
|
|
59
|
+
:return: str
|
|
60
|
+
"""
|
|
61
|
+
return self._topic
|
|
@@ -57,13 +57,13 @@ class BroadcastAction(Action):
|
|
|
57
57
|
self,
|
|
58
58
|
label: str,
|
|
59
59
|
intent: str = "io.heckel.ntfy.USER_ACTION",
|
|
60
|
-
extras: Optional[dict] = None,
|
|
60
|
+
extras: Optional[dict[str, str]] = None,
|
|
61
61
|
clear: bool = False,
|
|
62
62
|
):
|
|
63
63
|
self.action = ActionType.BROADCAST
|
|
64
64
|
self.intent = intent
|
|
65
65
|
self.extras = extras
|
|
66
|
-
super().__init__(label, ActionType.BROADCAST, clear)
|
|
66
|
+
super().__init__(label, ActionType.BROADCAST.value, clear)
|
|
67
67
|
|
|
68
68
|
def to_dict(self) -> dict:
|
|
69
69
|
return {
|
|
@@ -71,14 +71,18 @@ class BroadcastAction(Action):
|
|
|
71
71
|
"label": self.label,
|
|
72
72
|
"extras": self.extras,
|
|
73
73
|
"clear": self.clear,
|
|
74
|
+
"intent": self.intent,
|
|
74
75
|
}
|
|
75
76
|
|
|
76
77
|
def to_header(self) -> str:
|
|
77
|
-
extras
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
78
|
+
if self.extras:
|
|
79
|
+
extras_str = ", ".join([f"extras.{k}={v}" for k, v in self.extras.items()])
|
|
80
|
+
return (
|
|
81
|
+
f"action={self.action.value}, label={self.label}, url={self.url}, clear={self.clear}, intent={self.intent}"
|
|
82
|
+
+ extras_str
|
|
83
|
+
if self.extras
|
|
84
|
+
else ""
|
|
85
|
+
)
|
|
82
86
|
|
|
83
87
|
|
|
84
88
|
class HttpAction(Action):
|
|
@@ -87,7 +91,7 @@ class HttpAction(Action):
|
|
|
87
91
|
label: str,
|
|
88
92
|
url: str,
|
|
89
93
|
method: str = "POST",
|
|
90
|
-
headers: Optional[dict] = None,
|
|
94
|
+
headers: Optional[dict[str, str]] = None,
|
|
91
95
|
body: Optional[str] = None,
|
|
92
96
|
clear: bool = False,
|
|
93
97
|
):
|
|
@@ -97,8 +101,8 @@ class HttpAction(Action):
|
|
|
97
101
|
self.body = body
|
|
98
102
|
super().__init__(label, url, clear)
|
|
99
103
|
|
|
100
|
-
def to_dict(self) -> dict:
|
|
101
|
-
action_dict = {
|
|
104
|
+
def to_dict(self) -> dict[str, Union[str, bool, dict[str, str]]]:
|
|
105
|
+
action_dict: dict[str, Union[str, bool, dict[str, str]]] = {
|
|
102
106
|
"action": self.action.value,
|
|
103
107
|
"label": self.label,
|
|
104
108
|
"url": self.url,
|
|
@@ -127,10 +131,10 @@ class HttpAction(Action):
|
|
|
127
131
|
def send(
|
|
128
132
|
self,
|
|
129
133
|
message: str,
|
|
130
|
-
title: str = None,
|
|
131
|
-
priority:
|
|
134
|
+
title: Optional[str] = None,
|
|
135
|
+
priority: MessagePriority = MessagePriority.DEFAULT,
|
|
132
136
|
tags: list = [],
|
|
133
|
-
actions: list[Union[ViewAction, BroadcastAction, HttpAction
|
|
137
|
+
actions: list[Union[ViewAction, BroadcastAction, HttpAction]] = [],
|
|
134
138
|
format_as_markdown: bool = False,
|
|
135
139
|
) -> dict:
|
|
136
140
|
"""
|
|
@@ -153,7 +157,7 @@ def send(
|
|
|
153
157
|
"Title": title,
|
|
154
158
|
"Priority": priority.value,
|
|
155
159
|
"Tags": ",".join(tags),
|
|
156
|
-
"Markdown":
|
|
160
|
+
"Markdown": str(format_as_markdown).lower(),
|
|
157
161
|
}
|
|
158
162
|
if len(actions) > 0:
|
|
159
163
|
headers["Actions"] = " ; ".join([action.to_header() for action in actions])
|
|
@@ -167,10 +171,10 @@ def send(
|
|
|
167
171
|
def send_file(
|
|
168
172
|
self,
|
|
169
173
|
file: str,
|
|
170
|
-
title: str = None,
|
|
171
|
-
priority:
|
|
174
|
+
title: Optional[str] = None,
|
|
175
|
+
priority: MessagePriority = MessagePriority.DEFAULT,
|
|
172
176
|
tags: list = [],
|
|
173
|
-
actions: list[Union[ViewAction, BroadcastAction, HttpAction
|
|
177
|
+
actions: list[Union[ViewAction, BroadcastAction, HttpAction]] = [],
|
|
174
178
|
) -> dict:
|
|
175
179
|
"""
|
|
176
180
|
Send a file to the server
|
|
@@ -186,17 +190,15 @@ def send_file(
|
|
|
186
190
|
response = client.send_file(file_path="example.txt")
|
|
187
191
|
"""
|
|
188
192
|
headers = {
|
|
189
|
-
"Title": title,
|
|
193
|
+
"Title": str(title),
|
|
190
194
|
"Filename": file.split("/")[-1],
|
|
191
195
|
"Priority": priority.value,
|
|
192
196
|
"Tags": ",".join(tags),
|
|
193
197
|
"Actions": " ; ".join([action.to_header() for action in actions]),
|
|
194
198
|
}
|
|
195
199
|
|
|
196
|
-
with open(file, "rb") as
|
|
200
|
+
with open(file, "rb") as f:
|
|
197
201
|
response = json.loads(
|
|
198
|
-
requests.post(
|
|
199
|
-
url=self.url, data=file, headers=headers, auth=self._auth
|
|
200
|
-
).text
|
|
202
|
+
requests.post(url=self.url, data=f, headers=headers, auth=self._auth).text
|
|
201
203
|
)
|
|
202
204
|
return response
|
|
File without changes
|