python-ntfy 0.3.5__tar.gz → 0.3.6__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-ntfy
3
- Version: 0.3.5
3
+ Version: 0.3.6
4
4
  Summary: An ntfy library aiming for feature completeness
5
5
  Home-page: https://github.com/MatthewCane/python-ntfy
6
6
  License: MIT
@@ -20,9 +20,13 @@ Project-URL: Documentation, https://matthewcane.github.io/python-ntfy/
20
20
  Project-URL: Repository, https://github.com/MatthewCane/python-ntfy
21
21
  Description-Content-Type: text/markdown
22
22
 
23
- # A Python Library For ntfy.sh
23
+ # A Python Library For ntfy
24
24
 
25
- An easy-to-use ntfy python library. Aiming for full feature support.
25
+ ![PyPI - Version](https://img.shields.io/pypi/v/python-ntfy?link=https%3A%2F%2Fpypi.org%2Fproject%2Fpython-ntfy%2F)
26
+ ![PyPI - Downloads](https://img.shields.io/pypi/dm/python-ntfy?link=https%3A%2F%2Fpypistats.org%2Fpackages%2Fpython-ntfy)
27
+ ![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/MatthewCane/python-ntfy/publish.yml?link=https%3A%2F%2Fgithub.com%2FMatthewCane%2Fpython-ntfy%2Factions%2Fworkflows%2Fpublish.yml)
28
+
29
+ An easy-to-use python library for the [ntfy notification service](https://ntfy.sh/). Aiming for full feature support and a super easy to use interface.
26
30
 
27
31
  ## Quickstart
28
32
 
@@ -60,7 +64,7 @@ See the full documentation site at [https://matthewcane.github.io/python-ntfy/](
60
64
 
61
65
  ## Future Features
62
66
 
63
- - Email notifications
67
+ - [Email notifications](https://docs.ntfy.sh/publish/#e-mail-notifications)
64
68
  - Send to multiple topics at once
65
69
 
66
70
  ## Testing and Development
@@ -1,6 +1,10 @@
1
- # A Python Library For ntfy.sh
1
+ # A Python Library For ntfy
2
2
 
3
- An easy-to-use ntfy python library. Aiming for full feature support.
3
+ ![PyPI - Version](https://img.shields.io/pypi/v/python-ntfy?link=https%3A%2F%2Fpypi.org%2Fproject%2Fpython-ntfy%2F)
4
+ ![PyPI - Downloads](https://img.shields.io/pypi/dm/python-ntfy?link=https%3A%2F%2Fpypistats.org%2Fpackages%2Fpython-ntfy)
5
+ ![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/MatthewCane/python-ntfy/publish.yml?link=https%3A%2F%2Fgithub.com%2FMatthewCane%2Fpython-ntfy%2Factions%2Fworkflows%2Fpublish.yml)
6
+
7
+ An easy-to-use python library for the [ntfy notification service](https://ntfy.sh/). Aiming for full feature support and a super easy to use interface.
4
8
 
5
9
  ## Quickstart
6
10
 
@@ -38,7 +42,7 @@ See the full documentation site at [https://matthewcane.github.io/python-ntfy/](
38
42
 
39
43
  ## Future Features
40
44
 
41
- - Email notifications
45
+ - [Email notifications](https://docs.ntfy.sh/publish/#e-mail-notifications)
42
46
  - Send to multiple topics at once
43
47
 
44
48
  ## Testing and Development
@@ -0,0 +1,74 @@
1
+ [tool.poetry]
2
+ name = "python-ntfy"
3
+ version = "0.3.6"
4
+ description = "An ntfy library aiming for feature completeness"
5
+ authors = ["Matthew Cane <matthew.cane0@gmail.com>"]
6
+ readme = "README.md"
7
+ license = "MIT"
8
+ repository = "https://github.com/MatthewCane/python-ntfy"
9
+ documentation = "https://matthewcane.github.io/python-ntfy/"
10
+ classifiers = [
11
+ "Development Status :: 4 - Beta",
12
+ "Intended Audience :: Developers",
13
+ ]
14
+
15
+ [tool.poetry.dependencies]
16
+ python = "^3.11"
17
+ requests = "^2.31.0"
18
+ mypy = "^1.12.0"
19
+
20
+ [tool.poetry.group.dev.dependencies]
21
+ pytest = ">=7.4.1,<9.0.0"
22
+ python-dotenv = "^1.0.0"
23
+ pytest-asyncio = ">=0.21.1,<0.25.0"
24
+ pytest-codecov = ">=0.5.1,<0.7.0"
25
+ ruff = "^0.7.0"
26
+ mkdocs-material = "^9.5.41"
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"
32
+
33
+ [build-system]
34
+ requires = ["poetry-core"]
35
+ build-backend = "poetry.core.masonry.api"
36
+
37
+ [tool.ruff.lint]
38
+ select = [
39
+ "E", # PycodeStyle errors
40
+ "W", # PycodeStyle warnings
41
+ "F", # PyFlakes
42
+ "I", # Isort
43
+ "N", # pep8-naming
44
+ "D", # Pydocstyle
45
+ "UP", # PyUpgrade
46
+ "ANN", # Flake8-annotations
47
+ "S", # Flake8-bandit
48
+ "BLE", # Flake8-blind-except
49
+ "B", # Flake8-bugbear
50
+ "A", # Flake8-builtins
51
+ "C4", # Flake8-comprehensions
52
+ "T10", # Flake8-debugger
53
+ "EM", # Flake8-errmessages
54
+ "Q", # Flake8-quotes
55
+ "RET", # Flake8-return
56
+ "TRY", # Triceratops
57
+ "FURB", # Refurb
58
+ "RUF", # Ruff
59
+ "PERF", # Perflint
60
+ ]
61
+ ignore = [
62
+ "E501", # Line too long
63
+ "D103", # Missing docstring in public function
64
+ "D417", # Undocumented parameters
65
+ "D104", # First line of docstring should be in imperative moof
66
+ "D100", # Missing docstring in public module
67
+ "ANN001", # Missing type annotation for function argument
68
+ "ANN101", # Missing type annotation for self in method
69
+ "S101", # Use of assert detected
70
+ ]
71
+
72
+
73
+ [tool.ruff.lint.pydocstyle]
74
+ convention = "google"
@@ -1,33 +1,55 @@
1
+ """This module provides the NtfyClient class for interacting with the ntfy notification service.
2
+
3
+ The NtfyClient class allows users to send notifications, files, and perform various actions
4
+ through the ntfy.sh service. It also supports retrieving cached messages.
5
+
6
+ Typical usage example:
7
+
8
+ client = NtfyClient(topic="my_topic")
9
+ client.send("Hello, World!")
10
+ """
11
+
1
12
  import os
2
13
 
3
14
 
4
15
  class NtfyClient:
16
+ """A class for interacting with the ntfy notification service."""
17
+
5
18
  # The functions need to be imported here to:
6
19
  # 1. Keep the functions in a separate file
7
20
  # 2. Keep the docstrings working in the IDE
8
21
  # 3. Allow the functions to be called with self
9
22
  # MyPy does not like this, but it works
23
+ from ._get_functions import get_cached_messages # type: ignore
10
24
  from ._send_functions import ( # type: ignore
11
- send,
12
- send_file,
13
- MessagePriority,
14
- ViewAction,
15
25
  BroadcastAction,
16
26
  HttpAction,
27
+ MessagePriority,
28
+ ViewAction,
29
+ send,
30
+ send_file,
17
31
  )
18
- from ._get_functions import get_cached_messages # type: ignore
19
32
 
20
33
  def __init__(
21
34
  self,
22
35
  topic: str,
23
36
  server: str = "https://ntfy.sh",
24
37
  ) -> None:
25
- """
26
- :param topic: The topic to use for this client
27
- :param server: The server to connect to. Must include the protocol (http/https)
28
- :return None:
29
- """
38
+ """Itinialize the NtfyClient.
39
+
40
+ Args:
41
+ topic: The topic to use for this client
42
+ server: The server to connect to. Must include the protocol (http/https)
30
43
 
44
+ Returns:
45
+ None
46
+
47
+ Exceptions:
48
+ ToDo
49
+
50
+ Examples:
51
+ client = NtfyClient(topic="my_topic")
52
+ """
31
53
  self._server = os.environ.get("NTFY_SERVER") or server
32
54
  self._topic = topic
33
55
  self.__set_url(self._server, topic)
@@ -44,23 +66,25 @@ class NtfyClient:
44
66
  else:
45
67
  self._auth = ("", "")
46
68
 
47
- def __set_url(self, server, topic):
69
+ def __set_url(self, server, topic) -> None:
48
70
  self.url = server.strip("/") + "/" + topic
49
71
 
50
- def set_topic(self, topic: str):
51
- """
52
- Set a new topic for the client
72
+ def set_topic(self, topic: str) -> None:
73
+ """Set a new topic for the client.
74
+
75
+ Args:
76
+ topic: The topic to set for this client.
53
77
 
54
- :param topic: The topic to use for this client
55
- :return: None
78
+ Returns:
79
+ None
56
80
  """
57
81
  self._topic = topic
58
82
  self.__set_url(self._server, self._topic)
59
83
 
60
- def get_topic(self):
61
- """
62
- Get the current topic
84
+ def get_topic(self) -> str:
85
+ """Get the current topic.
63
86
 
64
- :return: str
87
+ Returns:
88
+ str: The current topic.
65
89
  """
66
90
  return self._topic
@@ -0,0 +1,46 @@
1
+ import json
2
+
3
+ import requests
4
+
5
+
6
+ def get_cached_messages(
7
+ self,
8
+ since: str = "all",
9
+ scheduled: bool = False,
10
+ timeout_seconds: int = 10,
11
+ ) -> list[dict]:
12
+ """Get cached messages from the server.
13
+
14
+ Args:
15
+ since: The timestamp to start from. If set to "all", will return all messages.
16
+ scheduled: If true, will return scheduled messages.
17
+ timeout_seconds: The number of seconds to wait for the response.
18
+
19
+ Returns:
20
+ A list of messages.
21
+
22
+ Examples:
23
+ response = client.get(since="all")
24
+ response = client.get(since="all", scheduled=True)
25
+ response = client.get(since="2019-01-01")
26
+ response = client.get(since="2019-01-01", scheduled=True)
27
+ """
28
+ params = {"poll": "1"}
29
+ if scheduled:
30
+ params.update({"scheduled": str(scheduled)})
31
+ if since:
32
+ params.update({"since": since})
33
+
34
+ response = [
35
+ json.loads(line)
36
+ for line in requests.get(
37
+ url=self.url + "/json",
38
+ params=params,
39
+ auth=self._auth,
40
+ timeout=timeout_seconds,
41
+ )
42
+ .text.strip()
43
+ .splitlines()
44
+ ]
45
+ # Reverse the list so that the most recent notification is first
46
+ return sorted(response, key=lambda x: x["time"], reverse=True)
@@ -1,13 +1,13 @@
1
1
  import json
2
- import requests
3
2
  from enum import Enum
3
+ from pathlib import Path
4
4
  from typing import Optional, Union
5
5
 
6
+ import requests
7
+
6
8
 
7
9
  class MessagePriority(Enum):
8
- """
9
- Ntfy message priority levels.
10
- """
10
+ """Ntfy message priority levels."""
11
11
 
12
12
  MIN = "1"
13
13
  LOW = "2"
@@ -18,9 +18,7 @@ class MessagePriority(Enum):
18
18
 
19
19
 
20
20
  class ActionType(Enum):
21
- """
22
- Action button types
23
- """
21
+ """Action button types."""
24
22
 
25
23
  VIEW = "view"
26
24
  BROADCAST = "broadcast"
@@ -28,7 +26,7 @@ class ActionType(Enum):
28
26
 
29
27
 
30
28
  class Action:
31
- def __init__(self, label: str, url: str, clear: bool = False):
29
+ def __init__(self, label: str, url: str, clear: bool = False) -> None:
32
30
  self.label = label
33
31
  self.url = url
34
32
  self.actions: list = []
@@ -36,7 +34,7 @@ class Action:
36
34
 
37
35
 
38
36
  class ViewAction(Action):
39
- def __init__(self, label: str, url: str, clear: bool = False):
37
+ def __init__(self, label: str, url: str, clear: bool = False) -> None:
40
38
  self.action = ActionType.VIEW
41
39
  super().__init__(label=label, url=url, clear=clear)
42
40
 
@@ -59,7 +57,7 @@ class BroadcastAction(Action):
59
57
  intent: str = "io.heckel.ntfy.USER_ACTION",
60
58
  extras: Optional[dict[str, str]] = None,
61
59
  clear: bool = False,
62
- ):
60
+ ) -> None:
63
61
  self.action = ActionType.BROADCAST
64
62
  self.intent = intent
65
63
  self.extras = extras
@@ -94,7 +92,7 @@ class HttpAction(Action):
94
92
  headers: Optional[dict[str, str]] = None,
95
93
  body: Optional[str] = None,
96
94
  clear: bool = False,
97
- ):
95
+ ) -> None:
98
96
  self.action = ActionType.HTTP
99
97
  self.method = method
100
98
  self.headers = headers
@@ -133,26 +131,42 @@ def send(
133
131
  message: str,
134
132
  title: Optional[str] = None,
135
133
  priority: MessagePriority = MessagePriority.DEFAULT,
136
- tags: list = [],
137
- actions: list[Union[ViewAction, BroadcastAction, HttpAction]] = [],
134
+ tags: Optional[list] = None,
135
+ actions: Optional[list[Union[ViewAction, BroadcastAction, HttpAction]]] = None,
138
136
  format_as_markdown: bool = False,
137
+ timeout_seconds: int = 5,
139
138
  ) -> dict:
139
+ """Send a text-based message to the server.
140
+
141
+ Call this function to send a message to the server. The message will be sent
142
+ to the server and then broadcast to all clients subscribed to the topic.
143
+
144
+ Args:
145
+ message: The message to send.
146
+ title: The title of the message.
147
+ priority: The priority of the message.
148
+ tags: A list of tags to attach to the message. Can be an emoji short code.
149
+ actions: A list of Actions objects to attach to the message.
150
+ format_as_markdown: If true, the message will be formatted as markdown.
151
+ additional_topics: A list of additional topics to send the message to.
152
+ timeout_seconds: The number of seconds to wait before timing out.
153
+
154
+ Returns:
155
+ dict: The response from the server.
156
+
157
+ Raises:
158
+ ToDo
159
+
160
+ Examples:
161
+ response = client.send(message="Example message")
162
+ response = client.send(message="Example message", title="Example title", priority=MessagePriority.HIGH, tags=["fire", "warning"])
163
+ response = client.send(message="*Example markdown*", format_as_markdown=True)
140
164
  """
141
- Send a text based message to the server
142
-
143
- :param message: The message to send
144
- :param title: The title of the message. Optional
145
- :param priority: The priority of the message. Optional, defaults to MessagePriority.DEFAULT
146
- :param tags: A list of tags to attach to the message. Can be an emoji short code. Optional
147
- :param format_as_markdown: If true, the message will be formatted as markdown. Optional
148
- :param actions: A list of Actions objects to attach to the message. Optional
149
- :return: The response from the server
150
-
151
- :examples:
152
- response = client.send(message="Example message")
153
- response = client.send(message="Example message", title="Example title", priority=MessagePriority.HIGH, tags=["fire", "warning"])
154
- response = client.send(message="*Example markdown*", format_as_markdown=True)
155
- """
165
+ if tags is None:
166
+ tags = []
167
+ if actions is None:
168
+ actions = []
169
+
156
170
  headers = {
157
171
  "Title": title,
158
172
  "Priority": priority.value,
@@ -162,10 +176,15 @@ def send(
162
176
  if len(actions) > 0:
163
177
  headers["Actions"] = " ; ".join([action.to_header() for action in actions])
164
178
 
165
- response = json.loads(
166
- requests.post(url=self.url, data=message, headers=headers, auth=self._auth).text
179
+ return json.loads(
180
+ requests.post(
181
+ url=self.url,
182
+ data=message,
183
+ headers=headers,
184
+ auth=self._auth,
185
+ timeout=timeout_seconds,
186
+ ).text,
167
187
  )
168
- return response
169
188
 
170
189
 
171
190
  def send_file(
@@ -173,22 +192,34 @@ def send_file(
173
192
  file: str,
174
193
  title: Optional[str] = None,
175
194
  priority: MessagePriority = MessagePriority.DEFAULT,
176
- tags: list = [],
177
- actions: list[Union[ViewAction, BroadcastAction, HttpAction]] = [],
195
+ tags: Optional[list] = None,
196
+ actions: Optional[list[Union[ViewAction, BroadcastAction, HttpAction]]] = None,
197
+ timeout_seconds: int = 30,
178
198
  ) -> dict:
179
- """
180
- Send a file to the server
199
+ """Sends a file to the server.
181
200
 
182
- :param file_path: The path to the file to send.
183
- :param title: The title of the file. Optional
184
- :param priority: The priority of the message. Optional, defaults to MessagePriority.DEFAULT
185
- :param tags: A list of tags to attach to the message. Can be an emoji short code. Optional
186
- :param actions: A list of ActionButton objects to attach to the message. Optional
187
- :return: The response from the server
201
+ Args:
202
+ file: The path to the file to send.
203
+ title: The title of the file.
204
+ priority: The priority of the message. Optional, defaults to MessagePriority.
205
+ tags: A list of tags to attach to the message. Can be an emoji short code.
206
+ actions: A list of ActionButton objects to attach to the message.
207
+ timeout_seconds: The number of seconds to wait before timing out.
188
208
 
189
- :examples:
190
- response = client.send_file(file_path="example.txt")
209
+ Returns:
210
+ dict: The response from the server.
211
+
212
+ Raises:
213
+ ToDo
214
+
215
+ Examples:
216
+ response = client.send_file(file="example.txt")
191
217
  """
218
+ if actions is None:
219
+ actions = []
220
+ if tags is None:
221
+ tags = []
222
+
192
223
  headers = {
193
224
  "Title": str(title),
194
225
  "Filename": file.split("/")[-1],
@@ -197,8 +228,13 @@ def send_file(
197
228
  "Actions": " ; ".join([action.to_header() for action in actions]),
198
229
  }
199
230
 
200
- with open(file, "rb") as f:
201
- response = json.loads(
202
- requests.post(url=self.url, data=f, headers=headers, auth=self._auth).text
231
+ with Path(file).open("rb") as f:
232
+ return json.loads(
233
+ requests.post(
234
+ url=self.url,
235
+ data=f,
236
+ headers=headers,
237
+ auth=self._auth,
238
+ timeout=timeout_seconds,
239
+ ).text,
203
240
  )
204
- return response
@@ -1,35 +0,0 @@
1
- [tool.poetry]
2
- name = "python-ntfy"
3
- version = "0.3.5"
4
- description = "An ntfy library aiming for feature completeness"
5
- authors = ["Matthew Cane <matthew.cane0@gmail.com>"]
6
- readme = "README.md"
7
- license = "MIT"
8
- repository = "https://github.com/MatthewCane/python-ntfy"
9
- documentation = "https://matthewcane.github.io/python-ntfy/"
10
- classifiers = [
11
- "Development Status :: 4 - Beta",
12
- "Intended Audience :: Developers"
13
- ]
14
-
15
- [tool.poetry.dependencies]
16
- python = "^3.11"
17
- requests = "^2.31.0"
18
- mypy = "^1.12.0"
19
-
20
- [tool.poetry.group.dev.dependencies]
21
- pytest = ">=7.4.1,<9.0.0"
22
- python-dotenv = "^1.0.0"
23
- pytest-asyncio = ">=0.21.1,<0.25.0"
24
- pytest-codecov = ">=0.5.1,<0.7.0"
25
- ruff = "^0.7.0"
26
- mkdocs-material = "^9.5.41"
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"
32
-
33
- [build-system]
34
- requires = ["poetry-core"]
35
- build-backend = "poetry.core.masonry.api"
@@ -1,35 +0,0 @@
1
- import json
2
- import requests
3
-
4
-
5
- def get_cached_messages(
6
- self, since: str = "all", scheduled: bool = False
7
- ) -> list[dict]:
8
- """
9
- Get cached messages from the server
10
-
11
- :param since: The timestamp to start from. If set to "all", will return all messages. Optional
12
- :param scheduled: If true, will return scheduled messages. Optional
13
- :return: A list of messages
14
-
15
- :examples:
16
- response = client.get(since="all")
17
- response = client.get(since="all", scheduled=True)
18
- response = client.get(since="2019-01-01")
19
- response = client.get(since="2019-01-01", scheduled=True)
20
- """
21
-
22
- params = {"poll": "1"}
23
- if scheduled:
24
- params.update({"scheduled": str(scheduled)})
25
- if since:
26
- params.update({"since": since})
27
-
28
- response = [
29
- json.loads(line)
30
- for line in requests.get(url=self.url + "/json", params=params, auth=self._auth)
31
- .text.strip()
32
- .splitlines()
33
- ]
34
- # Reverse the list so that the most recent notification is first
35
- return sorted(response, key=lambda x: x["time"], reverse=True)
File without changes