webchanges 3.38.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.38.0/webchanges.egg-info → webchanges-3.40.0}/PKG-INFO +31 -29
  2. {webchanges-3.38.0 → webchanges-3.40.0}/README.rst +25 -23
  3. {webchanges-3.38.0 → webchanges-3.40.0}/pyproject.toml +49 -27
  4. {webchanges-3.38.0 → webchanges-3.40.0}/requirements.txt +1 -1
  5. {webchanges-3.38.0 → webchanges-3.40.0}/setup.py +5 -3
  6. {webchanges-3.38.0 → webchanges-3.40.0}/tools/get_manpages_parser.py +2 -1
  7. {webchanges-3.38.0 → webchanges-3.40.0}/webchanges/__init__.py +5 -4
  8. {webchanges-3.38.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.38.0 → webchanges-3.40.0}/webchanges/_resources/config.schema.json +78 -35
  13. {webchanges-3.38.0 → webchanges-3.40.0}/webchanges/_resources/jobs.schema.json +348 -170
  14. {webchanges-3.38.0 → webchanges-3.40.0}/webchanges/_vendored/headers.py +67 -37
  15. {webchanges-3.38.0 → webchanges-3.40.0}/webchanges/_vendored/packaging_version.py +4 -4
  16. {webchanges-3.38.0 → webchanges-3.40.0}/webchanges/_vendored/typeguard.py +1 -1
  17. {webchanges-3.38.0 → webchanges-3.40.0}/webchanges/cli.py +307 -230
  18. {webchanges-3.38.0 → webchanges-3.40.0}/webchanges/command.py +163 -84
  19. {webchanges-3.38.0 → webchanges-3.40.0}/webchanges/config.py +28 -10
  20. {webchanges-3.38.0 → webchanges-3.40.0}/webchanges/differs.py +735 -459
  21. {webchanges-3.38.0 → webchanges-3.40.0}/webchanges/filters/_base.py +13 -12
  22. {webchanges-3.38.0 → webchanges-3.40.0}/webchanges/filters/_document.py +15 -13
  23. {webchanges-3.38.0 → webchanges-3.40.0}/webchanges/filters/_html.py +121 -29
  24. {webchanges-3.38.0 → webchanges-3.40.0}/webchanges/filters/_shell.py +9 -4
  25. {webchanges-3.38.0 → webchanges-3.40.0}/webchanges/filters/_text.py +35 -21
  26. {webchanges-3.38.0 → webchanges-3.40.0}/webchanges/handler.py +43 -27
  27. {webchanges-3.38.0 → webchanges-3.40.0}/webchanges/jobs/_base.py +240 -112
  28. {webchanges-3.38.0 → webchanges-3.40.0}/webchanges/jobs/_browser.py +222 -256
  29. {webchanges-3.38.0 → webchanges-3.40.0}/webchanges/jobs/_exceptions.py +11 -10
  30. {webchanges-3.38.0 → webchanges-3.40.0}/webchanges/jobs/_shell.py +49 -11
  31. {webchanges-3.38.0 → webchanges-3.40.0}/webchanges/jobs/_url.py +406 -401
  32. {webchanges-3.38.0 → webchanges-3.40.0}/webchanges/mailer.py +14 -7
  33. {webchanges-3.38.0 → webchanges-3.40.0}/webchanges/main.py +5 -5
  34. {webchanges-3.38.0 → webchanges-3.40.0}/webchanges/reporters/__init__.py +3 -2
  35. {webchanges-3.38.0 → webchanges-3.40.0}/webchanges/reporters/_base.py +108 -59
  36. {webchanges-3.38.0 → webchanges-3.40.0}/webchanges/reporters/_browser.py +4 -4
  37. {webchanges-3.38.0 → webchanges-3.40.0}/webchanges/reporters/_chat.py +82 -47
  38. {webchanges-3.38.0 → webchanges-3.40.0}/webchanges/reporters/_email.py +13 -9
  39. {webchanges-3.38.0 → webchanges-3.40.0}/webchanges/reporters/_stdout.py +15 -10
  40. webchanges-3.40.0/webchanges/reporters/_system.py +136 -0
  41. {webchanges-3.38.0 → webchanges-3.40.0}/webchanges/reporters/_web.py +99 -64
  42. {webchanges-3.38.0 → webchanges-3.40.0}/webchanges/storage/__init__.py +2 -0
  43. {webchanges-3.38.0 → webchanges-3.40.0}/webchanges/storage/_base.py +12 -10
  44. {webchanges-3.38.0 → webchanges-3.40.0}/webchanges/storage/_config.py +25 -5
  45. {webchanges-3.38.0 → webchanges-3.40.0}/webchanges/storage/_redis.py +3 -2
  46. {webchanges-3.38.0 → webchanges-3.40.0}/webchanges/storage/_sqlite3.py +18 -18
  47. {webchanges-3.38.0 → webchanges-3.40.0}/webchanges/storage/_ssdb.py +9 -5
  48. {webchanges-3.38.0 → webchanges-3.40.0}/webchanges/storage/_yaml.py +84 -33
  49. {webchanges-3.38.0 → webchanges-3.40.0}/webchanges/util.py +138 -17
  50. {webchanges-3.38.0 → webchanges-3.40.0}/webchanges/worker.py +153 -118
  51. {webchanges-3.38.0 → webchanges-3.40.0/webchanges.egg-info}/PKG-INFO +31 -29
  52. {webchanges-3.38.0 → webchanges-3.40.0}/webchanges.egg-info/SOURCES.txt +3 -0
  53. {webchanges-3.38.0 → webchanges-3.40.0}/webchanges.egg-info/requires.txt +6 -6
  54. webchanges-3.38.0/webchanges/reporters/_system.py +0 -71
  55. {webchanges-3.38.0 → webchanges-3.40.0}/LICENSE.md +0 -0
  56. {webchanges-3.38.0 → webchanges-3.40.0}/MANIFEST.in +0 -0
  57. {webchanges-3.38.0 → webchanges-3.40.0}/setup.cfg +0 -0
  58. {webchanges-3.38.0 → webchanges-3.40.0}/webchanges/_resources/__init__.py +0 -0
  59. {webchanges-3.38.0 → webchanges-3.40.0}/webchanges/_vendored/__init__.py +0 -0
  60. {webchanges-3.38.0 → webchanges-3.40.0}/webchanges/filters/__init__.py +0 -0
  61. {webchanges-3.38.0 → webchanges-3.40.0}/webchanges/filters/_encoding.py +0 -0
  62. {webchanges-3.38.0 → webchanges-3.40.0}/webchanges/jobs/__init__.py +0 -0
  63. {webchanges-3.38.0 → webchanges-3.40.0}/webchanges/py.typed +0 -0
  64. {webchanges-3.38.0 → webchanges-3.40.0}/webchanges/storage/_minidb.py +0 -0
  65. {webchanges-3.38.0 → webchanges-3.40.0}/webchanges/storage_minidb.py +0 -0
  66. {webchanges-3.38.0 → webchanges-3.40.0}/webchanges.egg-info/dependency_links.txt +0 -0
  67. {webchanges-3.38.0 → webchanges-3.40.0}/webchanges.egg-info/entry_points.txt +0 -0
  68. {webchanges-3.38.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.38.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
@@ -123,22 +123,25 @@ notifications and AI-powered summaries to your favorite `platform
123
123
 
124
124
  Highlights
125
125
  ==========
126
- - **AI-powered change summaries** via Gemini (`ai_google differ
127
- <https://webchanges.readthedocs.io/en/stable/differs.html#ai-google-diff>`__, BETA).
128
- - **Structural diffs for JSON and XML** via the `deepdiff differ
129
- <https://webchanges.readthedocs.io/en/stable/differs.html#deepdiff-diff>`__.
130
- - **Visual diffs for images** via the `image differ
131
- <https://webchanges.readthedocs.io/en/stable/differs.html#image-diff>`__ (BETA).
132
- - **Bypass TLS/JA3 fingerprinting** with the optional `curl_cffi
133
- <https://webchanges.readthedocs.io/en/stable/jobs.html#http-client>`__ HTTP client.
134
- - **JSON-Schema-validated** ``jobs.yaml`` and ``config.yaml`` for editor autocompletion and inline validation.
126
+ - **Watch anything**: web pages (in a `real browser
127
+ <https://webchanges.readthedocs.io/en/stable/jobs.html#use-browser>`__ if needed), APIs, local files and command
128
+ output.
129
+ - **Track only what matters** with over 30 `filters <https://webchanges.readthedocs.io/en/stable/filters.html>`__.
130
+ - **Read changes at a glance** in formatted HTML, side by side, word by word, as `AI summaries
131
+ <https://webchanges.readthedocs.io/en/stable/differs.html#ai-google-diff>`__ and more.
132
+ - **Get notified anywhere**: email, Telegram, Discord, ntfy, webhooks and `more
133
+ <https://webchanges.readthedocs.io/en/stable/reporters.html>`__.
134
+ - **Few false alarms**: transient errors can be held back and repeated ones reported `once
135
+ <https://webchanges.readthedocs.io/en/stable/jobs.html#suppress-repeated-errors>`__.
136
+ - **Runs on your machine**, total privacy, no account needed, and extends with Python `hooks
137
+ <https://webchanges.readthedocs.io/en/stable/hooks.html>`__.
135
138
 
136
139
 
137
140
  Requirements
138
141
  ============
139
142
  **webchanges** requires |support|. For the best experience, use the current version of `Python
140
- <https://www.python.org/downloads/>`__; older Python versions are supported for 3 years after they're replaced. Free-threaded Python is supported, though some
141
- optional dependencies may not be.
143
+ <https://www.python.org/downloads/>`__; older Python versions are supported for 3 years after they're replaced.
144
+ Free-threaded Python is supported.
142
145
 
143
146
  For Generative AI summaries (BETA), you need a free `API Key from Google Cloud AI Studio
144
147
  <https://aistudio.google.com/app/apikey>`__ (see `here
@@ -167,9 +170,10 @@ On Windows with Python 3.15, first run ``set PYYAML_FORCE_LIBYAML=0`` (in PowerS
167
170
 
168
171
  Other ways to run
169
172
  -----------------
170
- - In a `Docker <https://www.docker.com/>`__ container: a minimal implementation (no browser) is `here
171
- <https://github.com/yubiuser/webchanges-docker>`__, and one with a browser is `here
172
- <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).
173
177
  - As a `GitHub Action <https://docs.github.com/en/actions>`__: an implementation is `here
174
178
  <https://github.com/swimmwatch/webchanges-action>`__.
175
179
 
@@ -224,7 +228,7 @@ Schedule
224
228
 
225
229
  Code
226
230
  ====
227
- |coveralls| |issues| |code_style| |OpenSSF_Scorecard|
231
+ |coveralls| |issues| |code_style|
228
232
 
229
233
  The code, issues tracker, and discussions are hosted on `GitHub <https://github.com/mborsetti/webchanges>`__.
230
234
 
@@ -243,10 +247,11 @@ License
243
247
  =======
244
248
  |license|
245
249
 
246
- See the `complete licenses <https://raw.githubusercontent.com/mborsetti/webchanges/refs/heads/main/LICENSE.md>`__ (released
247
- under the `MIT License <https://opensource.org/licenses/MIT>`__ but redistributing modified source code, dated 30
248
- July 2020, from `urlwatch 2.21 <https://github.com/thp/urlwatch/tree/346b25914b0418342ffe2fb0529bed702fddc01f>`__
249
- licensed under a `BSD 3-Clause License
250
+ See the `complete licenses
251
+ <https://raw.githubusercontent.com/mborsetti/webchanges/refs/heads/main/LICENSE.md>`__ (released under the `MIT
252
+ License <https://opensource.org/licenses/MIT>`__ but redistributing modified source code, dated 30 July 2020, from
253
+ `urlwatch 2.21 <https://github.com/thp/urlwatch/tree/346b25914b0418342ffe2fb0529bed702fddc01f>`__ licensed under a
254
+ `BSD 3-Clause License
250
255
  <https://raw.githubusercontent.com/thp/urlwatch/346b25914b0418342ffe2fb0529bed702fddc01f/COPYING>`__).
251
256
 
252
257
 
@@ -302,6 +307,3 @@ see the `comparison <https://webchanges.readthedocs.io/en/stable/upgrading.html>
302
307
  .. .. |snyk| image:: https://snyk.io/advisor/python/webchanges/badge.svg
303
308
  .. :target: https://security.snyk.io/package/pip/webchanges
304
309
  .. :alt: Snyk Package Health Score
305
- .. |OpenSSF_Scorecard| image:: https://api.scorecard.dev/projects/github.com/mborsetti/webchanges/badge
306
- :target: https://scorecard.dev/viewer/?uri=github.com/mborsetti/webchanges
307
- :alt: OpenSSF Scoreard
@@ -16,22 +16,25 @@ notifications and AI-powered summaries to your favorite `platform
16
16
 
17
17
  Highlights
18
18
  ==========
19
- - **AI-powered change summaries** via Gemini (`ai_google differ
20
- <https://webchanges.readthedocs.io/en/stable/differs.html#ai-google-diff>`__, BETA).
21
- - **Structural diffs for JSON and XML** via the `deepdiff differ
22
- <https://webchanges.readthedocs.io/en/stable/differs.html#deepdiff-diff>`__.
23
- - **Visual diffs for images** via the `image differ
24
- <https://webchanges.readthedocs.io/en/stable/differs.html#image-diff>`__ (BETA).
25
- - **Bypass TLS/JA3 fingerprinting** with the optional `curl_cffi
26
- <https://webchanges.readthedocs.io/en/stable/jobs.html#http-client>`__ HTTP client.
27
- - **JSON-Schema-validated** ``jobs.yaml`` and ``config.yaml`` for editor autocompletion and inline validation.
19
+ - **Watch anything**: web pages (in a `real browser
20
+ <https://webchanges.readthedocs.io/en/stable/jobs.html#use-browser>`__ if needed), APIs, local files and command
21
+ output.
22
+ - **Track only what matters** with over 30 `filters <https://webchanges.readthedocs.io/en/stable/filters.html>`__.
23
+ - **Read changes at a glance** in formatted HTML, side by side, word by word, as `AI summaries
24
+ <https://webchanges.readthedocs.io/en/stable/differs.html#ai-google-diff>`__ and more.
25
+ - **Get notified anywhere**: email, Telegram, Discord, ntfy, webhooks and `more
26
+ <https://webchanges.readthedocs.io/en/stable/reporters.html>`__.
27
+ - **Few false alarms**: transient errors can be held back and repeated ones reported `once
28
+ <https://webchanges.readthedocs.io/en/stable/jobs.html#suppress-repeated-errors>`__.
29
+ - **Runs on your machine**, total privacy, no account needed, and extends with Python `hooks
30
+ <https://webchanges.readthedocs.io/en/stable/hooks.html>`__.
28
31
 
29
32
 
30
33
  Requirements
31
34
  ============
32
35
  **webchanges** requires |support|. For the best experience, use the current version of `Python
33
- <https://www.python.org/downloads/>`__; older Python versions are supported for 3 years after they're replaced. Free-threaded Python is supported, though some
34
- optional dependencies may not be.
36
+ <https://www.python.org/downloads/>`__; older Python versions are supported for 3 years after they're replaced.
37
+ Free-threaded Python is supported.
35
38
 
36
39
  For Generative AI summaries (BETA), you need a free `API Key from Google Cloud AI Studio
37
40
  <https://aistudio.google.com/app/apikey>`__ (see `here
@@ -60,9 +63,10 @@ On Windows with Python 3.15, first run ``set PYYAML_FORCE_LIBYAML=0`` (in PowerS
60
63
 
61
64
  Other ways to run
62
65
  -----------------
63
- - In a `Docker <https://www.docker.com/>`__ container: a minimal implementation (no browser) is `here
64
- <https://github.com/yubiuser/webchanges-docker>`__, and one with a browser is `here
65
- <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).
66
70
  - As a `GitHub Action <https://docs.github.com/en/actions>`__: an implementation is `here
67
71
  <https://github.com/swimmwatch/webchanges-action>`__.
68
72
 
@@ -117,7 +121,7 @@ Schedule
117
121
 
118
122
  Code
119
123
  ====
120
- |coveralls| |issues| |code_style| |OpenSSF_Scorecard|
124
+ |coveralls| |issues| |code_style|
121
125
 
122
126
  The code, issues tracker, and discussions are hosted on `GitHub <https://github.com/mborsetti/webchanges>`__.
123
127
 
@@ -136,10 +140,11 @@ License
136
140
  =======
137
141
  |license|
138
142
 
139
- See the `complete licenses <https://raw.githubusercontent.com/mborsetti/webchanges/refs/heads/main/LICENSE.md>`__ (released
140
- under the `MIT License <https://opensource.org/licenses/MIT>`__ but redistributing modified source code, dated 30
141
- July 2020, from `urlwatch 2.21 <https://github.com/thp/urlwatch/tree/346b25914b0418342ffe2fb0529bed702fddc01f>`__
142
- licensed under a `BSD 3-Clause License
143
+ See the `complete licenses
144
+ <https://raw.githubusercontent.com/mborsetti/webchanges/refs/heads/main/LICENSE.md>`__ (released under the `MIT
145
+ License <https://opensource.org/licenses/MIT>`__ but redistributing modified source code, dated 30 July 2020, from
146
+ `urlwatch 2.21 <https://github.com/thp/urlwatch/tree/346b25914b0418342ffe2fb0529bed702fddc01f>`__ licensed under a
147
+ `BSD 3-Clause License
143
148
  <https://raw.githubusercontent.com/thp/urlwatch/346b25914b0418342ffe2fb0529bed702fddc01f/COPYING>`__).
144
149
 
145
150
 
@@ -194,7 +199,4 @@ see the `comparison <https://webchanges.readthedocs.io/en/stable/upgrading.html>
194
199
  .. :alt: Security Status
195
200
  .. .. |snyk| image:: https://snyk.io/advisor/python/webchanges/badge.svg
196
201
  .. :target: https://security.snyk.io/package/pip/webchanges
197
- .. :alt: Snyk Package Health Score
198
- .. |OpenSSF_Scorecard| image:: https://api.scorecard.dev/projects/github.com/mborsetti/webchanges/badge
199
- :target: https://scorecard.dev/viewer/?uri=github.com/mborsetti/webchanges
200
- :alt: OpenSSF Scoreard
202
+ .. :alt: Snyk Package Health Score
@@ -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.38.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