webchanges 3.39.0__tar.gz → 3.40.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 (68) hide show
  1. {webchanges-3.39.0/webchanges.egg-info → webchanges-3.40.0}/PKG-INFO +11 -10
  2. {webchanges-3.39.0 → webchanges-3.40.0}/README.rst +4 -3
  3. {webchanges-3.39.0 → webchanges-3.40.0}/pyproject.toml +49 -27
  4. {webchanges-3.39.0 → webchanges-3.40.0}/requirements.txt +1 -1
  5. {webchanges-3.39.0 → webchanges-3.40.0}/setup.py +5 -3
  6. {webchanges-3.39.0 → webchanges-3.40.0}/tools/get_manpages_parser.py +2 -1
  7. {webchanges-3.39.0 → webchanges-3.40.0}/webchanges/__init__.py +5 -4
  8. {webchanges-3.39.0 → webchanges-3.40.0}/webchanges/__main__.py +2 -0
  9. webchanges-3.40.0/webchanges/_httpx.py +37 -0
  10. webchanges-3.40.0/webchanges/_resources/.config.schema.sha256 +1 -0
  11. webchanges-3.40.0/webchanges/_resources/.jobs.schema.sha256 +1 -0
  12. {webchanges-3.39.0 → webchanges-3.40.0}/webchanges/_resources/config.schema.json +58 -35
  13. {webchanges-3.39.0 → webchanges-3.40.0}/webchanges/_resources/jobs.schema.json +321 -167
  14. {webchanges-3.39.0 → webchanges-3.40.0}/webchanges/_vendored/headers.py +67 -37
  15. {webchanges-3.39.0 → webchanges-3.40.0}/webchanges/_vendored/packaging_version.py +4 -4
  16. {webchanges-3.39.0 → webchanges-3.40.0}/webchanges/_vendored/typeguard.py +1 -1
  17. {webchanges-3.39.0 → webchanges-3.40.0}/webchanges/cli.py +302 -230
  18. {webchanges-3.39.0 → webchanges-3.40.0}/webchanges/command.py +70 -71
  19. {webchanges-3.39.0 → webchanges-3.40.0}/webchanges/config.py +7 -1
  20. {webchanges-3.39.0 → webchanges-3.40.0}/webchanges/differs.py +735 -459
  21. {webchanges-3.39.0 → webchanges-3.40.0}/webchanges/filters/_base.py +13 -12
  22. {webchanges-3.39.0 → webchanges-3.40.0}/webchanges/filters/_document.py +15 -13
  23. {webchanges-3.39.0 → webchanges-3.40.0}/webchanges/filters/_html.py +111 -24
  24. {webchanges-3.39.0 → webchanges-3.40.0}/webchanges/filters/_shell.py +9 -4
  25. {webchanges-3.39.0 → webchanges-3.40.0}/webchanges/filters/_text.py +35 -21
  26. {webchanges-3.39.0 → webchanges-3.40.0}/webchanges/handler.py +43 -27
  27. {webchanges-3.39.0 → webchanges-3.40.0}/webchanges/jobs/_base.py +191 -110
  28. {webchanges-3.39.0 → webchanges-3.40.0}/webchanges/jobs/_browser.py +222 -256
  29. {webchanges-3.39.0 → webchanges-3.40.0}/webchanges/jobs/_exceptions.py +11 -10
  30. {webchanges-3.39.0 → webchanges-3.40.0}/webchanges/jobs/_shell.py +5 -5
  31. {webchanges-3.39.0 → webchanges-3.40.0}/webchanges/jobs/_url.py +405 -410
  32. {webchanges-3.39.0 → webchanges-3.40.0}/webchanges/mailer.py +4 -6
  33. {webchanges-3.39.0 → webchanges-3.40.0}/webchanges/main.py +5 -5
  34. {webchanges-3.39.0 → webchanges-3.40.0}/webchanges/reporters/_base.py +105 -57
  35. {webchanges-3.39.0 → webchanges-3.40.0}/webchanges/reporters/_browser.py +4 -4
  36. {webchanges-3.39.0 → webchanges-3.40.0}/webchanges/reporters/_chat.py +82 -47
  37. {webchanges-3.39.0 → webchanges-3.40.0}/webchanges/reporters/_email.py +8 -7
  38. {webchanges-3.39.0 → webchanges-3.40.0}/webchanges/reporters/_stdout.py +15 -10
  39. {webchanges-3.39.0 → webchanges-3.40.0}/webchanges/reporters/_system.py +18 -14
  40. {webchanges-3.39.0 → webchanges-3.40.0}/webchanges/reporters/_web.py +25 -22
  41. {webchanges-3.39.0 → webchanges-3.40.0}/webchanges/storage/_base.py +12 -10
  42. {webchanges-3.39.0 → webchanges-3.40.0}/webchanges/storage/_config.py +10 -5
  43. {webchanges-3.39.0 → webchanges-3.40.0}/webchanges/storage/_redis.py +3 -2
  44. {webchanges-3.39.0 → webchanges-3.40.0}/webchanges/storage/_sqlite3.py +18 -18
  45. {webchanges-3.39.0 → webchanges-3.40.0}/webchanges/storage/_ssdb.py +9 -5
  46. {webchanges-3.39.0 → webchanges-3.40.0}/webchanges/storage/_yaml.py +35 -29
  47. {webchanges-3.39.0 → webchanges-3.40.0}/webchanges/util.py +138 -17
  48. webchanges-3.40.0/webchanges/worker.py +338 -0
  49. {webchanges-3.39.0 → webchanges-3.40.0/webchanges.egg-info}/PKG-INFO +11 -10
  50. {webchanges-3.39.0 → webchanges-3.40.0}/webchanges.egg-info/SOURCES.txt +3 -0
  51. {webchanges-3.39.0 → webchanges-3.40.0}/webchanges.egg-info/requires.txt +6 -6
  52. webchanges-3.39.0/webchanges/worker.py +0 -310
  53. {webchanges-3.39.0 → webchanges-3.40.0}/LICENSE.md +0 -0
  54. {webchanges-3.39.0 → webchanges-3.40.0}/MANIFEST.in +0 -0
  55. {webchanges-3.39.0 → webchanges-3.40.0}/setup.cfg +0 -0
  56. {webchanges-3.39.0 → webchanges-3.40.0}/webchanges/_resources/__init__.py +0 -0
  57. {webchanges-3.39.0 → webchanges-3.40.0}/webchanges/_vendored/__init__.py +0 -0
  58. {webchanges-3.39.0 → webchanges-3.40.0}/webchanges/filters/__init__.py +0 -0
  59. {webchanges-3.39.0 → webchanges-3.40.0}/webchanges/filters/_encoding.py +0 -0
  60. {webchanges-3.39.0 → webchanges-3.40.0}/webchanges/jobs/__init__.py +0 -0
  61. {webchanges-3.39.0 → webchanges-3.40.0}/webchanges/py.typed +0 -0
  62. {webchanges-3.39.0 → webchanges-3.40.0}/webchanges/reporters/__init__.py +0 -0
  63. {webchanges-3.39.0 → webchanges-3.40.0}/webchanges/storage/__init__.py +0 -0
  64. {webchanges-3.39.0 → webchanges-3.40.0}/webchanges/storage/_minidb.py +0 -0
  65. {webchanges-3.39.0 → webchanges-3.40.0}/webchanges/storage_minidb.py +0 -0
  66. {webchanges-3.39.0 → webchanges-3.40.0}/webchanges.egg-info/dependency_links.txt +0 -0
  67. {webchanges-3.39.0 → webchanges-3.40.0}/webchanges.egg-info/entry_points.txt +0 -0
  68. {webchanges-3.39.0 → webchanges-3.40.0}/webchanges.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: webchanges
3
- Version: 3.39.0
3
+ Version: 3.40.0
4
4
  Summary: Web Changes Delivered. AI-Summarized. Totally Anonymous.
5
5
  Author-email: Mike Borsetti <mike+webchanges@borsetti.com>
6
6
  Maintainer-email: Mike Borsetti <mike+webchanges@borsetti.com>
@@ -40,7 +40,7 @@ License-File: LICENSE.md
40
40
  Requires-Dist: cssselect
41
41
  Requires-Dist: h2
42
42
  Requires-Dist: html2text
43
- Requires-Dist: httpx
43
+ Requires-Dist: httpx2
44
44
  Requires-Dist: lxml
45
45
  Requires-Dist: markdown2
46
46
  Requires-Dist: msgpack
@@ -77,11 +77,11 @@ Requires-Dist: cryptography; extra == "pypdf-crypto"
77
77
  Provides-Extra: xmltodict
78
78
  Requires-Dist: xmltodict; extra == "xmltodict"
79
79
  Provides-Extra: deepdiff
80
- Requires-Dist: deepdiff>=8.0; python_version < "3.15" and extra == "deepdiff"
81
- Requires-Dist: deepdiff<9,>=8.0; python_version >= "3.15" and extra == "deepdiff"
80
+ Requires-Dist: deepdiff; python_version < "3.15" and extra == "deepdiff"
81
+ Requires-Dist: deepdiff<9.1; python_version >= "3.15" and extra == "deepdiff"
82
82
  Provides-Extra: deepdiff-xml
83
- Requires-Dist: deepdiff>=8.0; python_version < "3.15" and extra == "deepdiff-xml"
84
- Requires-Dist: deepdiff<9,>=8.0; python_version >= "3.15" and extra == "deepdiff-xml"
83
+ Requires-Dist: deepdiff; python_version < "3.15" and extra == "deepdiff-xml"
84
+ Requires-Dist: deepdiff<9.1; python_version >= "3.15" and extra == "deepdiff-xml"
85
85
  Requires-Dist: xmltodict; extra == "deepdiff-xml"
86
86
  Provides-Extra: imagediff
87
87
  Requires-Dist: Pillow; extra == "imagediff"
@@ -92,7 +92,7 @@ Requires-Dist: pushbullet.py; extra == "pushbullet"
92
92
  Provides-Extra: pushover
93
93
  Requires-Dist: chump; extra == "pushover"
94
94
  Provides-Extra: xmpp
95
- Requires-Dist: aioxmpp; extra == "xmpp"
95
+ Requires-Dist: slixmpp; extra == "xmpp"
96
96
  Provides-Extra: curl-cffi
97
97
  Requires-Dist: curl_cffi; extra == "curl-cffi"
98
98
  Provides-Extra: redis
@@ -170,9 +170,10 @@ On Windows with Python 3.15, first run ``set PYYAML_FORCE_LIBYAML=0`` (in PowerS
170
170
 
171
171
  Other ways to run
172
172
  -----------------
173
- - In a `Docker <https://www.docker.com/>`__ container: a minimal implementation (no browser) is `here
174
- <https://github.com/yubiuser/webchanges-docker>`__, and one with a browser is `here
175
- <https://github.com/jhedlund/webchanges-docker>`__.
173
+ - In a `Docker <https://www.docker.com/>`__ container: download a third-party container, `yubiuser/webchanges-docker
174
+ <https://github.com/yubiuser/webchanges-docker>`__ (no browser) or `jhedlund/webchanges-docker
175
+ <https://github.com/jhedlund/webchanges-docker>`__ (with a browser), or adapt the tested `sample Dockerfile
176
+ <https://webchanges.readthedocs.io/en/stable/advanced.html#docker-sample>`__ (no browser).
176
177
  - As a `GitHub Action <https://docs.github.com/en/actions>`__: an implementation is `here
177
178
  <https://github.com/swimmwatch/webchanges-action>`__.
178
179
 
@@ -63,9 +63,10 @@ On Windows with Python 3.15, first run ``set PYYAML_FORCE_LIBYAML=0`` (in PowerS
63
63
 
64
64
  Other ways to run
65
65
  -----------------
66
- - In a `Docker <https://www.docker.com/>`__ container: a minimal implementation (no browser) is `here
67
- <https://github.com/yubiuser/webchanges-docker>`__, and one with a browser is `here
68
- <https://github.com/jhedlund/webchanges-docker>`__.
66
+ - In a `Docker <https://www.docker.com/>`__ container: download a third-party container, `yubiuser/webchanges-docker
67
+ <https://github.com/yubiuser/webchanges-docker>`__ (no browser) or `jhedlund/webchanges-docker
68
+ <https://github.com/jhedlund/webchanges-docker>`__ (with a browser), or adapt the tested `sample Dockerfile
69
+ <https://webchanges.readthedocs.io/en/stable/advanced.html#docker-sample>`__ (no browser).
69
70
  - As a `GitHub Action <https://docs.github.com/en/actions>`__: an implementation is `here
70
71
  <https://github.com/swimmwatch/webchanges-action>`__.
71
72
 
@@ -77,13 +77,12 @@ pypdf = ['pypdf']
77
77
  pypdf_crypto = ['pypdf', 'cryptography']
78
78
  xmltodict = ['xmltodict'] # xmltojson and xmltoyaml filters
79
79
  # differs
80
- # deepdiff >= 8.0 is required for threshold_to_diff_deeper
81
- # deepdiff < 9 on Python >= 3.15: deepdiff 9 requires cachebox < 6, and cachebox 5.x has no 3.15 wheels and cannot be
80
+ # deepdiff < 9.1 on Python >= 3.15: deepdiff 9.1 requires cachebox < 6, and cachebox 5.x has no 3.15 wheels and cannot be
82
81
  # built for 3.15 (it uses PyO3 0.27, which supports Python only up to 3.14). Keep in sync with tests/requirements_*.txt
83
- deepdiff = ['deepdiff >= 8.0; python_version < "3.15"', 'deepdiff >= 8.0, < 9; python_version >= "3.15"']
82
+ deepdiff = ['deepdiff; python_version < "3.15"', 'deepdiff < 9.1; python_version >= "3.15"']
84
83
  deepdiff_xml = [
85
- 'deepdiff >= 8.0; python_version < "3.15"',
86
- 'deepdiff >= 8.0, < 9; python_version >= "3.15"',
84
+ 'deepdiff; python_version < "3.15"',
85
+ 'deepdiff < 9.1; python_version >= "3.15"',
87
86
  'xmltodict',
88
87
  ]
89
88
  imagediff = ['Pillow']
@@ -91,7 +90,7 @@ imagediff = ['Pillow']
91
90
  matrix = ['matrix_client']
92
91
  pushbullet = ['pushbullet.py']
93
92
  pushover = ['chump']
94
- xmpp = ['aioxmpp']
93
+ xmpp = ['slixmpp']
95
94
  # other
96
95
  curl_cffi = ['curl_cffi']
97
96
  redis = ['redis']
@@ -126,7 +125,7 @@ dependencies = { file = 'requirements.txt' }
126
125
 
127
126
  [tool.setuptools.package-data]
128
127
  'webchanges' = ['py.typed']
129
- 'webchanges._resources' = ['*.schema.json', '*.schema.sha256']
128
+ 'webchanges._resources' = ['*.schema.json', '.*.schema.sha256'] # globs skip dotfiles unless the dot is explicit
130
129
 
131
130
  # -------------------------- manpages --------------------------
132
131
  # https://github.com/praiskup/argparse-manpage/blob/main/README.md
@@ -154,6 +153,7 @@ omit = [
154
153
  'pip/*',
155
154
  'tests/*',
156
155
  'webchanges/_vendored/*',
156
+ 'webchanges/storage/_redis.py',
157
157
  'webchanges.egg-info/*',
158
158
  ]
159
159
  relative_files = true
@@ -258,26 +258,43 @@ line-length = 120
258
258
  # By default, Ruff enables Flake8's F rules, along with a subset of the E rules
259
259
  # Enable rules not enabled by default, and ignore specific rules.
260
260
  select = [
261
+ # 'AIR', # Airflow
262
+ # 'ERA', # eradicate
263
+ # 'FAST', # FastAPI
264
+ 'YTT', # flake8-2020
261
265
  'ANN', # flake8-annotations
266
+ 'ASYNC', # flake8-async
262
267
  'S', # flake8-bandit
263
268
  'BLE', # flake8-blind-except
269
+ # 'FBT', # flake8-boolean-trap
264
270
  'B', # flake8-bugbear
265
271
  'A', # flake8-builtin
272
+ # 'COM', # flake8-commas
266
273
  'C4', # flake8-comprehensions
274
+ # 'CPY', # flake8-copyright
267
275
  'DTZ', # flake8-datetimez
268
- # 'EM', # flake8-errmsg # TODO
276
+ # 'T10', # flake8-debugger
277
+ # 'DJ', # flake8-django
278
+ # 'EM', # flake8-errmsg
279
+ # 'EXE', # flake8-executable
280
+ # 'FIX', # flake8-fixme
269
281
  'FA', # flake8-future-annotations
270
282
  'INT', # flake8-gettext
271
283
  'ISC', # flake8-implicit-str-concat
284
+ 'ICN', # flake8-import-conventions
272
285
  'LOG', # flake8-logging
273
- # 'G', # flake8-logging-format # Prefer f-string to lazy `%` formatting and OK with '+'
286
+ 'G', # flake8-logging-format
287
+ # 'INP', # flake8-no-pep420
274
288
  'PIE', # flake8-pie
289
+ # 'T20', # flake8-print
275
290
  'PYI', # flake8-pyi
276
291
  'PT', # flake8-pytest-style
277
292
  'Q', # flake8-quotes
278
293
  'RSE', # flake8-raise
279
294
  'RET', # flake8-return
295
+ # 'SLF', # flake8-self
280
296
  'SIM', # flake8-simplify
297
+ 'SLOT', # flake8-slots
281
298
  'TID', # flake8-tidy-imports
282
299
  'TD', # flake8-todos
283
300
  'TC', # flake8-type-checking
@@ -286,29 +303,31 @@ select = [
286
303
  'FLY', # flynt
287
304
  'I', # isort
288
305
  'C90', # mccabe
306
+ # 'NPY', # NumPy-specific rules
307
+ # 'PD', # pandas-vet
289
308
  'N', # pep8-naming
290
309
  'PERF', # Perflint
291
310
  'E', # pycodestyle errors
292
311
  'W', # pycodestyle warnings
293
- # 'DOC', # pydoclint # TODO
294
- # 'D', # pydocstyle # TODO
312
+ # 'DOC', # pydoclint (requires enabling preview, which turns on a TON of other stuff)
313
+ 'D', # pydocstyle
295
314
  'F', # Pyflakes
315
+ # 'PGH', # pygrep-hooks
296
316
  # 'PL', # Pylint # TODO
317
+ 'UP', # pyupgrade
297
318
  'FURB', # refurb
298
319
  'RUF', # Ruff-specific rules
299
320
  # 'TRY', # tryceratops # TODO
300
321
  ]
301
322
 
302
323
  ignore = [
303
- 'FLY002', # Consider f-string instead of string join
304
- # 'G003', # Logging statement uses `+`'
305
- # 'G004', # Logging statement uses f-string (Prefer f-string to lazy `%` formatting)
324
+ 'D107', # Missing docstring in __init__. Class-level docstring (D101) already covers the object's purpose.
325
+ 'D203', # Conflicts with D211, which forbids a blank line before a class docstring).
326
+ 'D213', # Conflicts with D212, which says multi-line docstring summary should start on the first line (Google style).
306
327
  'PLC0415', # `import` should be at the top-level of a file
307
- 'PT011', # pytest.raises({exception}) is too broad, set the match parameter or use a more specific exception
308
328
  'PT030', # pytest.warns({warning}) is too broad, set the match parameter or use a more specific warning
309
329
  'RUF012', # Mutable class attributes should be annotated with `typing.ClassVar`
310
330
  'SIM105', # Use contextlib.suppress({exception}) instead of try-except-pass
311
- 'SIM115', # Use a context manager for opening files
312
331
  'TD002', # Missing author in TODO
313
332
  'TD003', # Missing issue link for this TODO
314
333
  ]
@@ -324,18 +343,24 @@ dummy-variable-rgx = '^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$'
324
343
  allowed-confusables = ['–', '‘', '’']
325
344
 
326
345
  [tool.ruff.lint.flake8-annotations]
327
- allow-star-arg-any = true # TODO
346
+ allow-star-arg-any = true
328
347
 
329
348
  [tool.ruff.lint.flake8-quotes]
330
349
  inline-quotes = 'single'
331
350
 
332
351
  [tool.ruff.lint.mccabe]
333
352
  # Flag errors (`C901`) whenever the complexity level exceeds below.
334
- max-complexity = 30
353
+ max-complexity = 20 # 10 is default, 13 to 15 is modern practice
335
354
 
336
355
  [tool.ruff.lint.per-file-ignores]
337
- 'tests/*' = [
356
+ '{tests/**,tools/test_*}' = [
357
+ 'ANN401', # Dynamically typed expressions (typing.Any) are disallowed
358
+ 'PT011', # pytest.raises({exception}) is too broad, set the match parameter or use a more specific exception
338
359
  'S101', # Use of `assert` detected
360
+ # Test functions are self-documenting:
361
+ 'D101', # Missing docstring in public class
362
+ 'D102', # Missing docstring in public method
363
+ 'D103', # Missing docstring in public function
339
364
  ]
340
365
 
341
366
  [tool.ruff.lint.pydoclint]
@@ -382,14 +407,11 @@ docstring-code-line-length = 'dynamic'
382
407
  # -------------------------- ty --------------------------
383
408
  # Config file documentation at https://docs.astral.sh/ty/reference/configuration/
384
409
 
410
+ # A ty.toml (e.g. one adding machine-specific interpreter paths) replaces this section entirely, so keep its other
411
+ # settings the same as here. 'tools' is needed to resolve the scripts there that tests import after adding the
412
+ # directory to sys.path at runtime (e.g. update_schema_hashes in tests/test_cli.py), which ty cannot follow.
385
413
  [tool.ty.environment]
386
- extra-paths = ['webchanges']
414
+ extra-paths = ['webchanges', 'tools']
387
415
 
388
416
  [tool.ty.src]
389
- exclude = ["./webchanges/.**", "./webchanges/stora_minidb.py"]
390
-
391
- [[tool.ty.overrides]]
392
- include = ["./webchanges/storage/_minidb.py"]
393
-
394
- [tool.ty.overrides.rules]
395
- all = "ignore"
417
+ exclude = ["./webchanges/.**", "./webchanges/storage/_minidb.py"]
@@ -1,7 +1,7 @@
1
1
  cssselect
2
2
  h2
3
3
  html2text
4
- httpx
4
+ httpx2
5
5
  lxml
6
6
  markdown2
7
7
  msgpack
@@ -1,6 +1,8 @@
1
- # Shim required to wire argparse-manpage's build_manpages cmdclass into setuptools.
2
- # All other configuration lives in pyproject.toml.
3
- # See https://github.com/praiskup/argparse-manpage/blob/main/README.md
1
+ """Shim wiring argparse-manpage's ``build_manpages`` cmdclass into setuptools.
2
+
3
+ All other configuration lives in pyproject.toml. See https://github.com/praiskup/argparse-manpage/blob/main/README.md
4
+ """
5
+
4
6
  from __future__ import annotations
5
7
 
6
8
  from build_manpages import build_manpages, get_build_py_cmd, get_install_cmd
@@ -1,3 +1,5 @@
1
+ """Expose webchanges' argument parser to argparse-manpage."""
2
+
1
3
  from __future__ import annotations
2
4
 
3
5
  import sys
@@ -18,6 +20,5 @@ def get_manpages_parser() -> ArgumentParser:
18
20
 
19
21
  https://github.com/praiskup/argparse-manpage/blob/main/README.md
20
22
  """
21
-
22
23
  null_path = Path()
23
24
  return CommandConfig([], null_path, null_path, null_path, null_path, null_path).parse_args([])
@@ -1,6 +1,7 @@
1
- """Checks web content, including images, to detect any changes since the prior run. If any are found,
2
- it summarizes (including with Gen AI) what changed ('diff') and displays it and/or sends it via email and/or other
3
- supported services. Can check the output of local commands as well.
1
+ """Checks web content, including images, to detect any changes since the prior run.
2
+
3
+ If any are found, it summarizes (including with Gen AI) what changed ('diff') and displays it and/or sends it via email
4
+ and/or other supported services. Can check the output of local commands as well.
4
5
  """
5
6
 
6
7
  # The code below is subject to the license contained in the LICENSE.md file, which is part of the source code.
@@ -22,7 +23,7 @@ __project_name__ = str(__package__)
22
23
  # * MINOR version when you add functionality in a backwards compatible manner, and
23
24
  # * MICRO or PATCH version when you make backwards compatible bug fixes. We no longer use '0'
24
25
  # If unsure on increments, use pkg_resources.parse_version to parse
25
- __version__ = '3.39.0'
26
+ __version__ = '3.40.0'
26
27
  __description__ = (
27
28
  'Check web (or command output) for changes since last run and notify.\n\nAnonymously alerts you of web changes.'
28
29
  )
@@ -1,3 +1,5 @@
1
+ """Run webchanges with ``python -m webchanges``."""
2
+
1
3
  if __name__ == '__main__':
2
4
  import sys
3
5
  from pathlib import Path
@@ -0,0 +1,37 @@
1
+ """The primary HTTP client: ``httpx2``, else ``httpx`` (same API) imported as ``httpx2``, else ``None``.
2
+
3
+ Modules get the client from here (``from webchanges._httpx import httpx2``) so that the choice is made in one place.
4
+ If neither is installed, ``requests`` and then ``curl_cffi`` are used instead (see ``UrlJob._select_http_client()`` in
5
+ ``webchanges/jobs/_url.py``), so every use of ``httpx2`` must be guarded with ``if httpx2 is None`` / ``if httpx2``.
6
+ """
7
+
8
+ # The code below is subject to the license contained in the LICENSE.md file, which is part of the source code.
9
+
10
+ from __future__ import annotations
11
+
12
+ from typing import TYPE_CHECKING
13
+
14
+ __all__ = ['Headers', 'h2', 'httpx2']
15
+
16
+ # The fallback assignments below are hidden from the type checker behind `if not TYPE_CHECKING`. Otherwise the type
17
+ # checker infers the union of all bindings (e.g. `<module 'httpx2'> | None`), and every reference to a module
18
+ # attribute — including in annotations such as `httpx2.Response`, which cannot be guarded at runtime — is reported as
19
+ # unresolved. Hiding the fallbacks keeps the httpx2 module type, which is what these code paths are guarded to run
20
+ # under.
21
+ try:
22
+ import httpx2
23
+ from httpx2 import Headers
24
+ except ImportError: # pragma: no cover
25
+ if not TYPE_CHECKING:
26
+ try:
27
+ import httpx as httpx2
28
+ from httpx import Headers
29
+ except ImportError:
30
+ httpx2 = None
31
+ from webchanges._vendored.headers import Headers
32
+
33
+ # HTTP/2 support (used only by the client above).
34
+ try:
35
+ import h2
36
+ except ImportError: # pragma: no cover
37
+ h2 = None
@@ -0,0 +1 @@
1
+ 314f226aa22b4e546970df26dc47aa9bbd4ae170c67fabe9610f040f2d3f345e
@@ -0,0 +1 @@
1
+ 5b3c2a1db96278aefcb4a20acf1442a321b9c5c84711da1dc1751ca714a0d8ea
@@ -4,7 +4,6 @@
4
4
  "title": "Webchanges Configuration File",
5
5
  "description": "JSON Schema for the webchanges config.yaml configuration file.",
6
6
  "type": "object",
7
- "required": ["display", "report", "job_defaults", "differ_defaults", "database", "footnote", "version_check"],
8
7
  "properties": {
9
8
  "display": {
10
9
  "$ref": "#/$defs/_ConfigDisplay",
@@ -46,13 +45,17 @@
46
45
  "default": null
47
46
  }
48
47
  },
48
+ "patternProperties": {
49
+ "^_": { "description": "A remark: keys starting with an underscore are ignored." }
50
+ },
49
51
  "additionalProperties": false,
50
52
  "$defs": {
51
53
  "_ConfigDisplay": {
52
54
  "title": "Display",
53
55
  "description": "Which job outcomes appear in reports. Changes are always reported.",
54
56
  "type": "object",
55
- "required": ["new", "error", "unchanged"],
57
+ "patternProperties": { "^_": {} },
58
+ "additionalProperties": false,
56
59
  "properties": {
57
60
  "new": {
58
61
  "type": "boolean",
@@ -70,8 +73,9 @@
70
73
  "default": false
71
74
  },
72
75
  "empty-diff": {
73
- "enum": [false],
76
+ "type": "boolean",
74
77
  "description": "Deprecated; use additions_only in the job instead.",
78
+ "default": false,
75
79
  "deprecated": true
76
80
  }
77
81
  }
@@ -80,7 +84,8 @@
80
84
  "title": "Text report format",
81
85
  "description": "Settings for the plain-text report (used by stdout, ifttt, mailgun, ntfy, prowl, pushbullet, pushover, run_command, webhook (default), xmpp).",
82
86
  "type": "object",
83
- "required": ["line_length", "details", "footer", "minimal", "separate"],
87
+ "patternProperties": { "^_": {} },
88
+ "additionalProperties": false,
84
89
  "properties": {
85
90
  "line_length": {
86
91
  "type": "integer",
@@ -113,7 +118,8 @@
113
118
  "title": "HTML report format",
114
119
  "description": "Settings for the HTML report (used by browser, email with html: true).",
115
120
  "type": "object",
116
- "required": ["diff", "footer", "separate", "title"],
121
+ "patternProperties": { "^_": {} },
122
+ "additionalProperties": false,
117
123
  "properties": {
118
124
  "diff": {
119
125
  "enum": ["unified", "table"],
@@ -142,7 +148,8 @@
142
148
  "title": "Markdown report format",
143
149
  "description": "Settings for the Markdown report (used by gotify, matrix, telegram, webhook with markdown: true).",
144
150
  "type": "object",
145
- "required": ["details", "footer", "minimal", "separate"],
151
+ "patternProperties": { "^_": {} },
152
+ "additionalProperties": false,
146
153
  "properties": {
147
154
  "details": {
148
155
  "type": "boolean",
@@ -170,6 +177,8 @@
170
177
  "title": "stdout reporter",
171
178
  "description": "Display a text report on standard output (the console).",
172
179
  "type": "object",
180
+ "patternProperties": { "^_": {} },
181
+ "additionalProperties": false,
173
182
  "properties": {
174
183
  "tags": {
175
184
  "anyOf": [{ "type": "string" }, { "type": "array", "items": { "type": "string" } }],
@@ -191,6 +200,8 @@
191
200
  "title": "Browser reporter",
192
201
  "description": "Open the HTML report in the system's default web browser.",
193
202
  "type": "object",
203
+ "patternProperties": { "^_": {} },
204
+ "additionalProperties": false,
194
205
  "properties": {
195
206
  "tags": {
196
207
  "anyOf": [{ "type": "string" }, { "type": "array", "items": { "type": "string" } }],
@@ -207,6 +218,8 @@
207
218
  "title": "Discord reporter",
208
219
  "description": "Send the report as a message in a Discord channel via a webhook.",
209
220
  "type": "object",
221
+ "patternProperties": { "^_": {} },
222
+ "additionalProperties": false,
210
223
  "properties": {
211
224
  "tags": {
212
225
  "anyOf": [{ "type": "string" }, { "type": "array", "items": { "type": "string" } }],
@@ -248,7 +261,8 @@
248
261
  "title": "SMTP settings",
249
262
  "description": "SMTP server settings used when email.method is 'smtp'.",
250
263
  "type": "object",
251
- "required": ["host", "user", "port", "starttls", "auth", "insecure_password", "utf_8"],
264
+ "patternProperties": { "^_": {} },
265
+ "additionalProperties": false,
252
266
  "properties": {
253
267
  "host": {
254
268
  "type": "string",
@@ -296,7 +310,8 @@
296
310
  "title": "sendmail settings",
297
311
  "description": "External sendmail program settings used when email.method is 'sendmail' (Linux only).",
298
312
  "type": "object",
299
- "required": ["path"],
313
+ "patternProperties": { "^_": {} },
314
+ "additionalProperties": false,
300
315
  "properties": {
301
316
  "path": {
302
317
  "type": "string",
@@ -309,6 +324,8 @@
309
324
  "title": "Email reporter",
310
325
  "description": "Send the report via email, using either SMTP or the external sendmail program.",
311
326
  "type": "object",
327
+ "patternProperties": { "^_": {} },
328
+ "additionalProperties": false,
312
329
  "properties": {
313
330
  "tags": {
314
331
  "anyOf": [{ "type": "string" }, { "type": "array", "items": { "type": "string" } }],
@@ -357,6 +374,8 @@
357
374
  "title": "GitHub Issue reporter",
358
375
  "description": "Submit the Markdown report as a new issue on a GitHub repository. Requires a personal access token with 'public_repo' (public) or 'repo' (private) scope.",
359
376
  "type": "object",
377
+ "patternProperties": { "^_": {} },
378
+ "additionalProperties": false,
360
379
  "properties": {
361
380
  "tags": {
362
381
  "anyOf": [{ "type": "string" }, { "type": "array", "items": { "type": "string" } }],
@@ -415,7 +434,7 @@
415
434
  "default": ""
416
435
  },
417
436
  "milestone": {
418
- "type": "string",
437
+ "type": ["integer", "string"],
419
438
  "description": "Number of the milestone to associate with the issue.",
420
439
  "default": ""
421
440
  }
@@ -425,6 +444,8 @@
425
444
  "title": "Gotify reporter",
426
445
  "description": "Send the Markdown report to a Gotify server. Requires an application token created on the Gotify server's Web-UI.",
427
446
  "type": "object",
447
+ "patternProperties": { "^_": {} },
448
+ "additionalProperties": false,
428
449
  "properties": {
429
450
  "tags": {
430
451
  "anyOf": [{ "type": "string" }, { "type": "array", "items": { "type": "string" } }],
@@ -466,6 +487,8 @@
466
487
  "title": "IFTTT reporter",
467
488
  "description": "Send the text report as an IFTTT event via the Maker Webhooks service.",
468
489
  "type": "object",
490
+ "patternProperties": { "^_": {} },
491
+ "additionalProperties": false,
469
492
  "properties": {
470
493
  "tags": {
471
494
  "anyOf": [{ "type": "string" }, { "type": "array", "items": { "type": "string" } }],
@@ -492,6 +515,8 @@
492
515
  "title": "Mailgun reporter",
493
516
  "description": "Send the text report via email using the Mailgun service.",
494
517
  "type": "object",
518
+ "patternProperties": { "^_": {} },
519
+ "additionalProperties": false,
495
520
  "properties": {
496
521
  "tags": {
497
522
  "anyOf": [{ "type": "string" }, { "type": "array", "items": { "type": "string" } }],
@@ -543,6 +568,8 @@
543
568
  "title": "Matrix reporter",
544
569
  "description": "Send the report to a room using the Matrix protocol.",
545
570
  "type": "object",
571
+ "patternProperties": { "^_": {} },
572
+ "additionalProperties": false,
546
573
  "properties": {
547
574
  "tags": {
548
575
  "anyOf": [{ "type": "string" }, { "type": "array", "items": { "type": "string" } }],
@@ -574,7 +601,8 @@
574
601
  "title": "ntfy priorities",
575
602
  "description": "Per-event-kind ntfy message priorities. Each value is either an integer 1-5 or a name ('min', 'low', 'default', 'high', 'max').",
576
603
  "type": "object",
577
- "required": ["default", "new", "changed", "error"],
604
+ "patternProperties": { "^_": {} },
605
+ "additionalProperties": false,
578
606
  "properties": {
579
607
  "default": {
580
608
  "type": ["integer", "string"],
@@ -602,6 +630,8 @@
602
630
  "title": "ntfy reporter",
603
631
  "description": "Send the text report to an ntfy server (https://ntfy.sh).",
604
632
  "type": "object",
633
+ "patternProperties": { "^_": {} },
634
+ "additionalProperties": false,
605
635
  "properties": {
606
636
  "tags": {
607
637
  "anyOf": [{ "type": "string" }, { "type": "array", "items": { "type": "string" } }],
@@ -629,6 +659,8 @@
629
659
  "title": "Prowl reporter",
630
660
  "description": "Send the text report via Prowl (iOS push notifications).",
631
661
  "type": "object",
662
+ "patternProperties": { "^_": {} },
663
+ "additionalProperties": false,
632
664
  "properties": {
633
665
  "tags": {
634
666
  "anyOf": [{ "type": "string" }, { "type": "array", "items": { "type": "string" } }],
@@ -665,6 +697,8 @@
665
697
  "title": "Pushbullet reporter",
666
698
  "description": "Send the text report via Pushbullet. Note: the underlying 'pushbullet' Python package has not been maintained since 2022.",
667
699
  "type": "object",
700
+ "patternProperties": { "^_": {} },
701
+ "additionalProperties": false,
668
702
  "properties": {
669
703
  "tags": {
670
704
  "anyOf": [{ "type": "string" }, { "type": "array", "items": { "type": "string" } }],
@@ -686,6 +720,8 @@
686
720
  "title": "Pushover reporter",
687
721
  "description": "Send the text report via Pushover (https://pushover.net).",
688
722
  "type": "object",
723
+ "patternProperties": { "^_": {} },
724
+ "additionalProperties": false,
689
725
  "properties": {
690
726
  "tags": {
691
727
  "anyOf": [{ "type": "string" }, { "type": "array", "items": { "type": "string" } }],
@@ -728,6 +764,8 @@
728
764
  "title": "run_command reporter",
729
765
  "description": "Run a local command receiving the text report. Substitutes {count}, {jobs}, and {text} in the command string and exposes the WEBCHANGES_REPORT_CONFIG_JSON and WEBCHANGES_CHANGED_JOBS_JSON environment variables.",
730
766
  "type": "object",
767
+ "patternProperties": { "^_": {} },
768
+ "additionalProperties": false,
731
769
  "properties": {
732
770
  "tags": {
733
771
  "anyOf": [{ "type": "string" }, { "type": "array", "items": { "type": "string" } }],
@@ -749,6 +787,8 @@
749
787
  "title": "Telegram reporter",
750
788
  "description": "Send the Markdown report via Telegram using the Bot API.",
751
789
  "type": "object",
790
+ "patternProperties": { "^_": {} },
791
+ "additionalProperties": false,
752
792
  "properties": {
753
793
  "tags": {
754
794
  "anyOf": [{ "type": "string" }, { "type": "array", "items": { "type": "string" } }],
@@ -789,6 +829,8 @@
789
829
  "title": "Webhook reporter",
790
830
  "description": "Send the report to a webhook endpoint (e.g. Slack, Mattermost).",
791
831
  "type": "object",
832
+ "patternProperties": { "^_": {} },
833
+ "additionalProperties": false,
792
834
  "properties": {
793
835
  "tags": {
794
836
  "anyOf": [{ "type": "string" }, { "type": "array", "items": { "type": "string" } }],
@@ -825,6 +867,8 @@
825
867
  "title": "XMPP reporter",
826
868
  "description": "Send the text report via the XMPP protocol. Use a dedicated XMPP account, never a primary one.",
827
869
  "type": "object",
870
+ "patternProperties": { "^_": {} },
871
+ "additionalProperties": false,
828
872
  "properties": {
829
873
  "tags": {
830
874
  "anyOf": [{ "type": "string" }, { "type": "array", "items": { "type": "string" } }],
@@ -856,29 +900,6 @@
856
900
  "title": "Report and reporter settings",
857
901
  "description": "Container for report-format settings (text, html, markdown) and reporter settings.",
858
902
  "type": "object",
859
- "required": [
860
- "tz",
861
- "text",
862
- "html",
863
- "markdown",
864
- "stdout",
865
- "browser",
866
- "discord",
867
- "email",
868
- "github_issue",
869
- "gotify",
870
- "ifttt",
871
- "mailgun",
872
- "matrix",
873
- "ntfy",
874
- "prowl",
875
- "pushbullet",
876
- "pushover",
877
- "run_command",
878
- "telegram",
879
- "webhook",
880
- "xmpp"
881
- ],
882
903
  "properties": {
883
904
  "tz": {
884
905
  "type": ["string", "null"],
@@ -1102,7 +1123,8 @@
1102
1123
  "title": "Database",
1103
1124
  "description": "Snapshot database engine and retention.",
1104
1125
  "type": "object",
1105
- "required": ["engine", "max_snapshots"],
1126
+ "patternProperties": { "^_": {} },
1127
+ "additionalProperties": false,
1106
1128
  "properties": {
1107
1129
  "engine": {
1108
1130
  "type": "string",
@@ -1121,7 +1143,8 @@
1121
1143
  "title": "Version Check",
1122
1144
  "description": "Settings for the once-a-day check against PyPi for a newer release of webchanges.",
1123
1145
  "type": "object",
1124
- "required": ["enabled"],
1146
+ "patternProperties": { "^_": {} },
1147
+ "additionalProperties": false,
1125
1148
  "properties": {
1126
1149
  "enabled": {
1127
1150
  "type": "boolean",