pywebpush 2.2.0__tar.gz → 2.3.0__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 (24) hide show
  1. {pywebpush-2.2.0 → pywebpush-2.3.0}/CHANGELOG.md +12 -0
  2. {pywebpush-2.2.0 → pywebpush-2.3.0}/PKG-INFO +1 -2
  3. {pywebpush-2.2.0 → pywebpush-2.3.0}/pyproject.toml +1 -1
  4. {pywebpush-2.2.0 → pywebpush-2.3.0}/pywebpush/__init__.py +23 -23
  5. {pywebpush-2.2.0 → pywebpush-2.3.0}/pywebpush/__main__.py +4 -4
  6. {pywebpush-2.2.0 → pywebpush-2.3.0}/pywebpush/tests/test_webpush.py +6 -6
  7. {pywebpush-2.2.0 → pywebpush-2.3.0}/pywebpush.egg-info/PKG-INFO +1 -2
  8. {pywebpush-2.2.0 → pywebpush-2.3.0}/pywebpush.egg-info/requires.txt +0 -1
  9. {pywebpush-2.2.0 → pywebpush-2.3.0}/requirements.txt +0 -1
  10. {pywebpush-2.2.0 → pywebpush-2.3.0}/CODE_OF_CONDUCT.md +0 -0
  11. {pywebpush-2.2.0 → pywebpush-2.3.0}/LICENSE +0 -0
  12. {pywebpush-2.2.0 → pywebpush-2.3.0}/MANIFEST.in +0 -0
  13. {pywebpush-2.2.0 → pywebpush-2.3.0}/README.md +0 -0
  14. {pywebpush-2.2.0 → pywebpush-2.3.0}/README.rst +0 -0
  15. {pywebpush-2.2.0 → pywebpush-2.3.0}/entry_points.txt +0 -0
  16. {pywebpush-2.2.0 → pywebpush-2.3.0}/local_test.txt +0 -0
  17. {pywebpush-2.2.0 → pywebpush-2.3.0}/pywebpush/foo.py +0 -0
  18. {pywebpush-2.2.0 → pywebpush-2.3.0}/pywebpush/tests/__init__.py +0 -0
  19. {pywebpush-2.2.0 → pywebpush-2.3.0}/pywebpush.egg-info/SOURCES.txt +0 -0
  20. {pywebpush-2.2.0 → pywebpush-2.3.0}/pywebpush.egg-info/dependency_links.txt +0 -0
  21. {pywebpush-2.2.0 → pywebpush-2.3.0}/pywebpush.egg-info/entry_points.txt +0 -0
  22. {pywebpush-2.2.0 → pywebpush-2.3.0}/pywebpush.egg-info/top_level.txt +0 -0
  23. {pywebpush-2.2.0 → pywebpush-2.3.0}/setup.cfg +0 -0
  24. {pywebpush-2.2.0 → pywebpush-2.3.0}/test-requirements.txt +0 -0
@@ -1,5 +1,17 @@
1
1
  # I am terrible at keeping this up-to-date.
2
2
 
3
+ ## 2.3.0 (2026-02-09)
4
+
5
+ - Cleanup from @Rotzbua
6
+ - Use modern typing for annotations
7
+ - Remove legacy python 2 import
8
+ - remove redundant :type annotations
9
+ - use [.dev] extras in CI
10
+
11
+ ## 2.2.1 (2026-02-06)
12
+
13
+ - add'l typing info (Thanks @rotzbua)
14
+
3
15
  ## 2.2.0 (2026-)
4
16
 
5
17
  - Update `rst` files to reflect `md` file changes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pywebpush
3
- Version: 2.2.0
3
+ Version: 2.3.0
4
4
  Summary: WebPush publication library
5
5
  Author-email: JR Conlin <src+webpusher@jrconlin.com>
6
6
  License: MPL-2.0
@@ -17,7 +17,6 @@ Requires-Dist: aiohttp
17
17
  Requires-Dist: cryptography>=2.6.1
18
18
  Requires-Dist: http-ece>=1.1.0
19
19
  Requires-Dist: requests>=2.21.0
20
- Requires-Dist: six>=1.15.0
21
20
  Requires-Dist: py-vapid>=1.7.0
22
21
  Provides-Extra: dev
23
22
  Requires-Dist: black; extra == "dev"
@@ -9,7 +9,7 @@ build-backend = "setuptools.build_meta"
9
9
 
10
10
  [project]
11
11
  name = "pywebpush"
12
- version = "2.2.0"
12
+ version = "2.3.0"
13
13
  requires-python = ">= 3.10"
14
14
  license = { text = "MPL-2.0" }
15
15
  authors = [{ name = "JR Conlin", email = "src+webpusher@jrconlin.com" }]
@@ -34,11 +34,11 @@ class WebPushException(Exception):
34
34
 
35
35
  """
36
36
 
37
- def __init__(self, message, response=None):
37
+ def __init__(self, message, response=None) -> None:
38
38
  self.message = message
39
39
  self.response = response
40
40
 
41
- def __str__(self):
41
+ def __str__(self) -> str:
42
42
  extra = ""
43
43
  if self.response is not None:
44
44
  try:
@@ -46,8 +46,8 @@ class WebPushException(Exception):
46
46
  self.response.text,
47
47
  )
48
48
  except AttributeError:
49
- extra = ", Response {}".format(self.response)
50
- return "WebPushException: {}{}".format(self.message, extra)
49
+ extra = f", Response {self.response}"
50
+ return f"WebPushException: {self.message}{extra}"
51
51
 
52
52
 
53
53
  class NoData(Exception):
@@ -57,12 +57,12 @@ class NoData(Exception):
57
57
  class CaseInsensitiveDict(dict):
58
58
  """A dictionary that has case-insensitive keys"""
59
59
 
60
- def __init__(self, data={}, **kwargs):
60
+ def __init__(self, data={}, **kwargs) -> None:
61
61
  for key in data:
62
62
  dict.__setitem__(self, key.lower(), data[key])
63
63
  self.update(kwargs)
64
64
 
65
- def __contains__(self, key):
65
+ def __contains__(self, key) -> bool:
66
66
  return dict.__contains__(self, key.lower())
67
67
 
68
68
  def __setitem__(self, key, value):
@@ -80,7 +80,7 @@ class CaseInsensitiveDict(dict):
80
80
  except KeyError:
81
81
  return default
82
82
 
83
- def update(self, data):
83
+ def update(self, data) -> None:
84
84
  for key in data:
85
85
  self.__setitem__(key, data[key])
86
86
 
@@ -137,7 +137,7 @@ class WebPusher:
137
137
  requests_session: Union[None, requests.Session] = None,
138
138
  aiohttp_session: Union[None, aiohttp.client.ClientSession] = None,
139
139
  verbose: bool = False,
140
- ):
140
+ ) -> None:
141
141
  """Initialize using the info provided by the client PushSubscription
142
142
  object (See
143
143
  https://developer.mozilla.org/en-US/docs/Web/API/PushManager/subscribe)
@@ -173,7 +173,7 @@ class WebPusher:
173
173
  )
174
174
  for k in ["p256dh", "auth"]:
175
175
  if keys.get(k) is None:
176
- raise WebPushException("Missing keys value: {}".format(k))
176
+ raise WebPushException(f"Missing keys value: {k}")
177
177
  if isinstance(keys[k], str):
178
178
  keys[k] = bytes(cast(str, keys[k]).encode("utf8"))
179
179
  receiver_raw = base64.urlsafe_b64decode(
@@ -186,11 +186,11 @@ class WebPusher:
186
186
  self._repad(cast(bytes, keys["auth"]))
187
187
  )
188
188
 
189
- def verb(self, msg: str, *args, **kwargs):
189
+ def verb(self, msg: str, *args, **kwargs) -> None:
190
190
  if self.verbose:
191
191
  logging.info(msg.format(*args, **kwargs))
192
192
 
193
- def _repad(self, data: bytes):
193
+ def _repad(self, data: bytes) -> bytes:
194
194
  """Add base64 padding to the end of a string, if required"""
195
195
  return data + b"===="[: len(data) % 4]
196
196
 
@@ -226,7 +226,7 @@ class WebPusher:
226
226
  if content_encoding == "aesgcm":
227
227
  self.verb("Generating salt for aesgcm...")
228
228
  salt = os.urandom(16)
229
- logging.debug("Salt: {}".format(salt))
229
+ logging.debug(f"Salt: {salt}")
230
230
  # The server key is an ephemeral ECDH key used only for this
231
231
  # transaction
232
232
  server_key = ec.generate_private_key(ec.SECP256R1(), default_backend())
@@ -266,7 +266,7 @@ class WebPusher:
266
266
  reply["salt"] = base64.urlsafe_b64encode(salt).strip(b"=")
267
267
  return reply
268
268
 
269
- def as_curl(self, endpoint: str, encoded_data: bytes, headers: Dict[str, str]):
269
+ def as_curl(self, endpoint: str, encoded_data: bytes, headers: Dict[str, str]) -> str:
270
270
  """Return the send as a curl command.
271
271
 
272
272
  Useful for debugging. This will write out the encoded data to a local
@@ -282,7 +282,7 @@ class WebPusher:
282
282
 
283
283
  """
284
284
  header_list = [
285
- '-H "{}: {}" \\ \n'.format(key.lower(), val) for key, val in headers.items()
285
+ f'-H "{key.lower()}: {val}" \\ \n' for key, val in headers.items()
286
286
  ]
287
287
  data = ""
288
288
  if encoded_data:
@@ -292,7 +292,7 @@ class WebPusher:
292
292
  if "content-length" not in headers:
293
293
  self.verb("Generating content-length header...")
294
294
  header_list.append(
295
- '-H "content-length: {}" \\ \n'.format(len(encoded_data))
295
+ f'-H "content-length: {len(encoded_data)}" \\ \n'
296
296
  )
297
297
  return """curl -vX POST {url} \\\n{headers}{data}""".format(
298
298
  url=endpoint, headers="".join(header_list), data=data
@@ -488,7 +488,7 @@ def webpush(
488
488
  logging.info("Generating VAPID headers...")
489
489
  if not vapid_claims.get("aud"):
490
490
  url = urlparse(cast(str, subscription_info.get("endpoint")))
491
- aud = "{}://{}".format(url.scheme, url.netloc)
491
+ aud = f"{url.scheme}://{url.netloc}"
492
492
  vapid_claims["aud"] = aud
493
493
  # Remember, passed structures are mutable in python.
494
494
  # It's possible that a previously set `exp` field is no longer valid.
@@ -509,17 +509,17 @@ def webpush(
509
509
  # Presume that key from file is handled correctly by
510
510
  # py_vapid.
511
511
  if verbose:
512
- logging.info("Reading VAPID key from file {}".format(vapid_private_key))
512
+ logging.info(f"Reading VAPID key from file {vapid_private_key}")
513
513
  vv = Vapid.from_file(private_key_file=vapid_private_key) # pragma no cover
514
514
  else:
515
515
  if verbose:
516
516
  logging.info("Reading VAPID key from arguments")
517
517
  vv = Vapid.from_string(private_key=vapid_private_key)
518
518
  if verbose:
519
- logging.info("\t claims: {}".format(vapid_claims))
519
+ logging.info(f"\t claims: {vapid_claims}")
520
520
  vapid_headers = vv.sign(vapid_claims)
521
521
  if verbose:
522
- logging.info("\t headers: {}".format(vapid_headers))
522
+ logging.info(f"\t headers: {vapid_headers}")
523
523
  headers.update(vapid_headers)
524
524
 
525
525
  response = WebPusher(
@@ -625,7 +625,7 @@ async def webpush_async(
625
625
  logging.info("Generating VAPID headers...")
626
626
  if not vapid_claims.get("aud"):
627
627
  url = urlparse(cast(str, subscription_info.get("endpoint")))
628
- aud = "{}://{}".format(url.scheme, url.netloc)
628
+ aud = f"{url.scheme}://{url.netloc}"
629
629
  vapid_claims["aud"] = aud
630
630
  # Remember, passed structures are mutable in python.
631
631
  # It's possible that a previously set `exp` field is no longer valid.
@@ -648,17 +648,17 @@ async def webpush_async(
648
648
  # Presume that key from file is handled correctly by
649
649
  # py_vapid.
650
650
  if verbose:
651
- logging.info("Reading VAPID key from file {}".format(vapid_private_key))
651
+ logging.info(f"Reading VAPID key from file {vapid_private_key}")
652
652
  vv = Vapid.from_file(private_key_file=vapid_private_key) # pragma no cover
653
653
  else:
654
654
  if verbose:
655
655
  logging.info("Reading VAPID key from arguments")
656
656
  vv = Vapid.from_string(private_key=vapid_private_key)
657
657
  if verbose:
658
- logging.info("\t claims: {}".format(vapid_claims))
658
+ logging.info(f"\t claims: {vapid_claims}")
659
659
  vapid_headers = vv.sign(vapid_claims)
660
660
  if verbose:
661
- logging.info("\t headers: {}".format(vapid_headers))
661
+ logging.info(f"\t headers: {vapid_headers}")
662
662
  headers.update(vapid_headers)
663
663
 
664
664
  response = await WebPusher(
@@ -76,15 +76,15 @@ def get_config():
76
76
  args.claims = json.loads(r.read())
77
77
  except JSONDecodeError as e:
78
78
  raise WebPushException(
79
- "Could not read the VAPID claims file {}".format(e)
79
+ f"Could not read the VAPID claims file {e}"
80
80
  )
81
81
  except Exception as ex:
82
- logging.error("Couldn't read input {}.".format(ex))
82
+ logging.error(f"Couldn't read input {ex}.")
83
83
  raise ex
84
84
  return args
85
85
 
86
86
 
87
- def main():
87
+ def main() -> None:
88
88
  """Send data"""
89
89
 
90
90
  try:
@@ -101,7 +101,7 @@ def main():
101
101
  )
102
102
  print(result)
103
103
  except Exception as ex:
104
- logging.error("{}".format(ex))
104
+ logging.error(f"{ex}")
105
105
 
106
106
 
107
107
  if __name__ == "__main__":
@@ -319,7 +319,7 @@ class WebpushTestUtils(unittest.TestCase):
319
319
  '-H "ttl: 0"',
320
320
  '-H "content-length:',
321
321
  ]:
322
- assert s in result, "missing: {}".format(s)
322
+ assert s in result, f"missing: {s}"
323
323
 
324
324
  def test_ci_dict(self):
325
325
  ci = CaseInsensitiveDict({"Foo": "apple", "bar": "banana"})
@@ -554,7 +554,7 @@ class WebPusherAsyncTestCase(WebpushTestUtils, unittest.IsolatedAsyncioTestCase)
554
554
  '-H "ttl: 0"',
555
555
  '-H "content-length:',
556
556
  ]:
557
- assert s in result, "missing: {}".format(s)
557
+ assert s in result, f"missing: {s}"
558
558
 
559
559
 
560
560
  class WebpushExceptionTestCase(unittest.TestCase):
@@ -562,7 +562,7 @@ class WebpushExceptionTestCase(unittest.TestCase):
562
562
  from requests import Response
563
563
 
564
564
  exp = WebPushException("foo")
565
- assert "{}".format(exp) == "WebPushException: foo"
565
+ assert f"{exp}" == "WebPushException: foo"
566
566
  # Really should try to load the response to verify, but this mock
567
567
  # covers what we need.
568
568
  response = Mock(spec=Response)
@@ -578,10 +578,10 @@ class WebpushExceptionTestCase(unittest.TestCase):
578
578
  response.status_code = 401
579
579
  response.reason = "Unauthorized"
580
580
  exp = WebPushException("foo", response)
581
- assert "{}".format(exp) == "WebPushException: foo, Response {}".format(
581
+ assert f"{exp}" == "WebPushException: foo, Response {}".format(
582
582
  response.text
583
583
  )
584
- assert "{}".format(exp.response), "<Response [401]>"
584
+ assert f"{exp.response}", "<Response [401]>"
585
585
  assert cast(requests.Response, exp.response).json().get("errno") == 109
586
586
  exp = WebPushException("foo", [1, 2, 3])
587
- assert "{}".format(exp) == "WebPushException: foo, Response [1, 2, 3]"
587
+ assert f"{exp}" == "WebPushException: foo, Response [1, 2, 3]"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pywebpush
3
- Version: 2.2.0
3
+ Version: 2.3.0
4
4
  Summary: WebPush publication library
5
5
  Author-email: JR Conlin <src+webpusher@jrconlin.com>
6
6
  License: MPL-2.0
@@ -17,7 +17,6 @@ Requires-Dist: aiohttp
17
17
  Requires-Dist: cryptography>=2.6.1
18
18
  Requires-Dist: http-ece>=1.1.0
19
19
  Requires-Dist: requests>=2.21.0
20
- Requires-Dist: six>=1.15.0
21
20
  Requires-Dist: py-vapid>=1.7.0
22
21
  Provides-Extra: dev
23
22
  Requires-Dist: black; extra == "dev"
@@ -2,7 +2,6 @@ aiohttp
2
2
  cryptography>=2.6.1
3
3
  http-ece>=1.1.0
4
4
  requests>=2.21.0
5
- six>=1.15.0
6
5
  py-vapid>=1.7.0
7
6
 
8
7
  [dev]
@@ -2,5 +2,4 @@ aiohttp
2
2
  cryptography>=2.6.1
3
3
  http-ece>=1.1.0
4
4
  requests>=2.21.0
5
- six>=1.15.0
6
5
  py-vapid>=1.7.0
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes