webchanges 3.31.4__tar.gz → 3.34.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 (33) hide show
  1. {webchanges-3.31.4/webchanges.egg-info → webchanges-3.34.0}/PKG-INFO +12 -71
  2. {webchanges-3.31.4 → webchanges-3.34.0}/README.rst +7 -0
  3. {webchanges-3.31.4 → webchanges-3.34.0}/pyproject.toml +94 -126
  4. {webchanges-3.31.4 → webchanges-3.34.0}/webchanges/__init__.py +2 -2
  5. {webchanges-3.31.4 → webchanges-3.34.0}/webchanges/_vendored/headers.py +5 -5
  6. {webchanges-3.31.4 → webchanges-3.34.0}/webchanges/cli.py +10 -7
  7. {webchanges-3.31.4 → webchanges-3.34.0}/webchanges/command.py +81 -53
  8. {webchanges-3.31.4 → webchanges-3.34.0}/webchanges/config.py +1 -2
  9. {webchanges-3.31.4 → webchanges-3.34.0}/webchanges/differs.py +355 -185
  10. {webchanges-3.31.4 → webchanges-3.34.0}/webchanges/filters.py +45 -34
  11. {webchanges-3.31.4 → webchanges-3.34.0}/webchanges/handler.py +97 -103
  12. {webchanges-3.31.4 → webchanges-3.34.0}/webchanges/jobs.py +410 -279
  13. {webchanges-3.31.4 → webchanges-3.34.0}/webchanges/main.py +7 -1
  14. {webchanges-3.31.4 → webchanges-3.34.0}/webchanges/reporters.py +118 -63
  15. {webchanges-3.31.4 → webchanges-3.34.0}/webchanges/storage.py +124 -55
  16. {webchanges-3.31.4 → webchanges-3.34.0}/webchanges/util.py +18 -8
  17. {webchanges-3.31.4 → webchanges-3.34.0}/webchanges/worker.py +21 -12
  18. {webchanges-3.31.4 → webchanges-3.34.0/webchanges.egg-info}/PKG-INFO +12 -71
  19. {webchanges-3.31.4 → webchanges-3.34.0}/LICENSE.md +0 -0
  20. {webchanges-3.31.4 → webchanges-3.34.0}/MANIFEST.in +0 -0
  21. {webchanges-3.31.4 → webchanges-3.34.0}/requirements.txt +0 -0
  22. {webchanges-3.31.4 → webchanges-3.34.0}/setup.cfg +0 -0
  23. {webchanges-3.31.4 → webchanges-3.34.0}/webchanges/__main__.py +0 -0
  24. {webchanges-3.31.4 → webchanges-3.34.0}/webchanges/_vendored/__init__.py +0 -0
  25. {webchanges-3.31.4 → webchanges-3.34.0}/webchanges/_vendored/packaging_version.py +0 -0
  26. {webchanges-3.31.4 → webchanges-3.34.0}/webchanges/mailer.py +0 -0
  27. {webchanges-3.31.4 → webchanges-3.34.0}/webchanges/py.typed +0 -0
  28. {webchanges-3.31.4 → webchanges-3.34.0}/webchanges/storage_minidb.py +0 -0
  29. {webchanges-3.31.4 → webchanges-3.34.0}/webchanges.egg-info/SOURCES.txt +0 -0
  30. {webchanges-3.31.4 → webchanges-3.34.0}/webchanges.egg-info/dependency_links.txt +0 -0
  31. {webchanges-3.31.4 → webchanges-3.34.0}/webchanges.egg-info/entry_points.txt +0 -0
  32. {webchanges-3.31.4 → webchanges-3.34.0}/webchanges.egg-info/requires.txt +0 -0
  33. {webchanges-3.31.4 → webchanges-3.34.0}/webchanges.egg-info/top_level.txt +0 -0
@@ -1,75 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: webchanges
3
- Version: 3.31.4
3
+ Version: 3.34.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>
7
- License: ========
8
- Licenses
9
- ========
10
-
11
- The MIT License (MIT)
12
-
13
- Copyright (c) 2020- Mike Borsetti <mike@borsetti.com>
14
-
15
- Permission is hereby granted, free of charge, to any person obtaining a copy of
16
- this software and associated documentation files (the "Software"), to deal in
17
- the Software without restriction, including without limitation the rights to
18
- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
19
- the Software, and to permit persons to whom the Software is furnished to do so,
20
- subject to the following conditions:
21
-
22
- The above copyright notice and this permission notice shall be included in all
23
- copies or substantial portions of the Software.
24
-
25
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
26
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
27
- FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
28
- COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
29
- IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
30
- CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31
-
32
- --------------------------------------------------------------------------------
33
-
34
- SOURCE CODE REDISTRIBUTION NOTICE
35
- (urlwatch by Thomas Perl)
36
-
37
- This software redistributes source code of release 2.21, dated 30 July 2020, of
38
- urlwatch
39
- https://github.com/thp/urlwatch/tree/346b25914b0418342ffe2fb0529bed702fddc01f,
40
- which is subject to the following copyright notice and license (from
41
- https://raw.githubusercontent.com/thp/urlwatch/346b25914b0418342ffe2fb0529bed702fddc01f/COPYING),
42
- hereby retained and redistributed with the source code (of which this license
43
- file is part of), in binary form, and in the documentation. The appearance of
44
- the name of the author below does not constitute an endorsement or promotion of
45
- this software by such author.
46
-
47
- Copyright (c) 2008-2020 Thomas Perl <m@thp.io>
48
- All rights reserved.
49
-
50
- Redistribution and use in source and binary forms, with or without
51
- modification, are permitted provided that the following conditions
52
- are met:
53
-
54
- 1. Redistributions of source code must retain the above copyright
55
- notice, this list of conditions and the following disclaimer.
56
- 2. Redistributions in binary form must reproduce the above copyright
57
- notice, this list of conditions and the following disclaimer in the
58
- documentation and/or other materials provided with the distribution.
59
- 3. The name of the author may not be used to endorse or promote products
60
- derived from this software without specific prior written permission.
61
-
62
- THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY EXPRESS OR
63
- IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
64
- OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
65
- IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
66
- INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
67
- NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
68
- DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
69
- THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
70
- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
71
- THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
72
-
7
+ License-Expression: MIT AND BSD-3-Clause
73
8
  Project-URL: Documentation, https://webchanges.readthedocs.io/
74
9
  Project-URL: Repository, https://github.com/mborsetti/webchanges/
75
10
  Project-URL: Changelog, https://webchanges.readthedocs.io/en/stable/changelog.html
@@ -81,18 +16,17 @@ Classifier: Environment :: Console
81
16
  Classifier: Intended Audience :: Developers
82
17
  Classifier: Intended Audience :: End Users/Desktop
83
18
  Classifier: Intended Audience :: System Administrators
84
- Classifier: License :: OSI Approved :: BSD License
85
- Classifier: License :: OSI Approved :: MIT License
86
19
  Classifier: Natural Language :: English
87
20
  Classifier: Operating System :: OS Independent
88
21
  Classifier: Programming Language :: Python
89
22
  Classifier: Programming Language :: Python :: 3
90
23
  Classifier: Programming Language :: Python :: 3 :: Only
91
- Classifier: Programming Language :: Python :: 3.10
92
24
  Classifier: Programming Language :: Python :: 3.11
93
25
  Classifier: Programming Language :: Python :: 3.12
94
26
  Classifier: Programming Language :: Python :: 3.13
95
27
  Classifier: Programming Language :: Python :: 3.14
28
+ Classifier: Programming Language :: Python :: Free Threading
29
+ Classifier: Programming Language :: Python :: Free Threading :: 4 - Resilient
96
30
  Classifier: Programming Language :: Python :: Implementation :: CPython
97
31
  Classifier: Topic :: Internet
98
32
  Classifier: Topic :: Internet :: WWW/HTTP
@@ -100,7 +34,7 @@ Classifier: Topic :: Internet :: WWW/HTTP :: Indexing/Search
100
34
  Classifier: Topic :: System :: Monitoring
101
35
  Classifier: Topic :: Utilities
102
36
  Classifier: Typing :: Typed
103
- Requires-Python: >=3.10
37
+ Requires-Python: >=3.11
104
38
  Description-Content-Type: text/x-rst
105
39
  License-File: LICENSE.md
106
40
  Requires-Dist: colorama; sys_platform == "win32"
@@ -189,6 +123,8 @@ For the best experience, use the current version of `Python <https://www.python.
189
123
  older Python versions for 3 years after they're replaced by a newer one; we just ask that you use the most up-to-date
190
124
  bug and security fix release from that older version.
191
125
 
126
+ While **webchanges** supports free-threated Python, certain optional dependencies may not.
127
+
192
128
  For Generative AI summaries (BETA), you need a free `API Key from Google Cloud AI Studio
193
129
  <https://aistudio.google.com/app/apikey>`__ (see `here
194
130
  <https://webchanges.readthedocs.io/en/stable/differs.html#ai-google>`__).
@@ -216,6 +152,11 @@ Running in Docker
216
152
  implementation (no browser) `here <https://github.com/yubiuser/webchanges-docker>`__, and one with a browser
217
153
  `here <https://github.com/jhedlund/webchanges-docker>`__.
218
154
 
155
+ As a GitHub Action
156
+ ------------------
157
+ **webchanges** can easily run as a `GitHub Action <https://www.docker.com/>`__! You will find an implementation
158
+ `here <https://github.com/swimmwatch/webchanges-action>`__.
159
+
219
160
 
220
161
  Documentation |readthedocs|
221
162
  ===========================
@@ -22,6 +22,8 @@ For the best experience, use the current version of `Python <https://www.python.
22
22
  older Python versions for 3 years after they're replaced by a newer one; we just ask that you use the most up-to-date
23
23
  bug and security fix release from that older version.
24
24
 
25
+ While **webchanges** supports free-threated Python, certain optional dependencies may not.
26
+
25
27
  For Generative AI summaries (BETA), you need a free `API Key from Google Cloud AI Studio
26
28
  <https://aistudio.google.com/app/apikey>`__ (see `here
27
29
  <https://webchanges.readthedocs.io/en/stable/differs.html#ai-google>`__).
@@ -49,6 +51,11 @@ Running in Docker
49
51
  implementation (no browser) `here <https://github.com/yubiuser/webchanges-docker>`__, and one with a browser
50
52
  `here <https://github.com/jhedlund/webchanges-docker>`__.
51
53
 
54
+ As a GitHub Action
55
+ ------------------
56
+ **webchanges** can easily run as a `GitHub Action <https://www.docker.com/>`__! You will find an implementation
57
+ `here <https://github.com/swimmwatch/webchanges-action>`__.
58
+
52
59
 
53
60
  Documentation |readthedocs|
54
61
  ===========================
@@ -6,9 +6,9 @@
6
6
 
7
7
  [build-system]
8
8
  # Minimum requirements for the build system to execute.
9
- requires = ['setuptools'] # PEP 508 specifications.
9
+ requires = ['argparse-manpage[setuptools]'] # PEP 508 specifications.
10
10
  # Setuptools specification
11
- build-backend = "setuptools.build_meta"
11
+ build-backend = 'setuptools.build_meta'
12
12
 
13
13
  [project]
14
14
  # See https://packaging.python.org/en/latest/specifications/declaring-project-metadata/
@@ -16,8 +16,9 @@ dynamic = ['version', 'dependencies']
16
16
  name = 'webchanges'
17
17
  description = 'Web Changes Delivered. AI-Summarized. Totally Anonymous.'
18
18
  readme = { file = 'README.rst', content-type = 'text/x-rst' }
19
- requires-python = '>=3.10'
20
- license = { file = 'LICENSE.md' }
19
+ requires-python = '>=3.11'
20
+ license = "MIT AND BSD-3-Clause"
21
+ license-files = ['LICENSE.md']
21
22
  authors = [{ name = 'Mike Borsetti', email = 'mike+webchanges@borsetti.com' }]
22
23
  maintainers = [
23
24
  { name = 'Mike Borsetti', email = 'mike+webchanges@borsetti.com' },
@@ -29,18 +30,17 @@ classifiers = [
29
30
  'Intended Audience :: Developers',
30
31
  'Intended Audience :: End Users/Desktop',
31
32
  'Intended Audience :: System Administrators',
32
- 'License :: OSI Approved :: BSD License',
33
- 'License :: OSI Approved :: MIT License',
34
33
  'Natural Language :: English',
35
34
  'Operating System :: OS Independent',
36
35
  'Programming Language :: Python',
37
36
  'Programming Language :: Python :: 3',
38
37
  'Programming Language :: Python :: 3 :: Only',
39
- 'Programming Language :: Python :: 3.10',
40
38
  'Programming Language :: Python :: 3.11',
41
39
  'Programming Language :: Python :: 3.12',
42
40
  'Programming Language :: Python :: 3.13',
43
41
  'Programming Language :: Python :: 3.14',
42
+ 'Programming Language :: Python :: Free Threading',
43
+ 'Programming Language :: Python :: Free Threading :: 4 - Resilient',
44
44
  'Programming Language :: Python :: Implementation :: CPython',
45
45
  'Topic :: Internet',
46
46
  'Topic :: Internet :: WWW/HTTP',
@@ -88,6 +88,7 @@ xmpp = ['aioxmpp']
88
88
  redis = ['redis']
89
89
  requests = ['requests']
90
90
  safe_password = ['keyring']
91
+ # all
91
92
  all = [
92
93
  'webchanges[use_browser,beautify,bs4,html5lib,ical2text,jq,ocr,pdf2text,pypdf_crypto,deepdiff_xml,imagediff,matrix,pushbullet,pushover,xmpp,redis,requests,safe_password]',
93
94
  ]
@@ -109,6 +110,10 @@ dependencies = { file = 'requirements.txt' }
109
110
  [tool.setuptools.package-data]
110
111
  'webchanges' = ['py.typed']
111
112
 
113
+ # -------------------------- manpages --------------------------
114
+ # https://github.com/praiskup/argparse-manpage/blob/main/README.md
115
+ [tool.build_manpages]
116
+ manpages = ['man/webchanges.1:function=get_parser:pyfile=get_parser']
112
117
 
113
118
  # -------------------------- coverage --------------------------
114
119
  [tool.coverage.run]
@@ -170,60 +175,6 @@ exclude_lines = [
170
175
  # ignore_errors = true
171
176
 
172
177
 
173
- # -------------------------- mypy --------------------------
174
- [tool.mypy]
175
- # Static Typing for Python
176
- # Runs as part of pre-commit
177
- # Config file documentation at https://mypy.readthedocs.io/en/stable/config_file.html
178
-
179
- # Disables import discovery of namespace packages (see PEP 420)
180
- namespace_packages = true
181
-
182
- # Specifies the Python version used to parse and check the target program.
183
- # python_version = 3.12
184
-
185
- # Suppresses error messages about imports that cannot be resolved.
186
- ignore_missing_imports = true
187
-
188
- # Disallows calling functions without type annotations from functions with type annotations.
189
- disallow_untyped_calls = false
190
-
191
- # Disallows defining functions without type annotations or with incomplete type annotations.
192
- disallow_untyped_defs = true
193
-
194
- # Reports an error whenever a function with type annotations is decorated with a decorator without annotations.
195
- disallow_untyped_decorators = true
196
-
197
- # Warns about casting an expression to its inferred type.
198
- warn_redundant_casts = true
199
-
200
- # Warns about unneeded # type: ignore comments.
201
- # May behave differently in GitHub Actions than it does on Windows.
202
- # warn_unused_ignores = true
203
-
204
- # Shows a warning when returning a value with type Any from a function declared with a non-Any return type.
205
- warn_return_any = true
206
-
207
- # Shows a warning when encountering any code inferred to be unreachable or redundant after performing type analysis.
208
- warn_unreachable = false
209
-
210
- # Enables additional checks that are technically correct but may be impractical in real code.
211
- extra_checks = true
212
-
213
- # Shows documentation link to corresponding error code.
214
- show_error_code_links = true
215
-
216
- # Use visually nicer output in error messages: use soft word wrap, show source code snippets, and show error location
217
- # markers.
218
- pretty = true
219
-
220
- # Use an SQLite database to store the cache.
221
- sqlite_cache = true
222
-
223
- # Warns about per-module sections in the config file that do not match any files processed when invoking mypy.
224
- warn_unused_configs = true
225
-
226
-
227
178
  # -------------------------- rstcheck --------------------------
228
179
  [tool.rstcheck]
229
180
  # Checks syntax of reStructuredText and code blocks nested within it.
@@ -240,12 +191,15 @@ report_level = 'WARNING'
240
191
  # Testing framework
241
192
  # Config file documentation at https://docs.pytest.org/en/stable/reference/reference.html#ini-options-ref
242
193
 
243
- log_auto_indent = true
194
+ # log_auto_indent = 2
244
195
  # Enable log display during test run (aka "live logging" https://docs.pytest.org/en/stable/logging.html#live-logs)
245
- log_cli = true
246
- minversion = '8.4.1'
196
+ # log_cli = true
197
+ minversion = '9.0.2'
247
198
  testpaths = ['tests']
248
199
 
200
+ # pytest-playwright
201
+ # addopts = ['--browser', 'chromium', '--browser-channel', 'chrome']
202
+
249
203
  # Adds pytest-cov functionality (see https://pytest-cov.readthedocs.io/en/latest/config.html)
250
204
  # Note: --cov moved to .github/workflows/ci-cd.yaml and tox.ini due to interference with PyCharm breakpoints (see
251
205
  # https://github.com/pytest-dev/pytest-cov/issues/131) and to enable running tox --parallel
@@ -258,10 +212,7 @@ testpaths = ['tests']
258
212
  # Config file documentation at https://docs.astral.sh/ruff/configuration/ and https://docs.astral.sh/ruff/settings/
259
213
 
260
214
  # File patterns to omit from formatting and linting, in addition to those specified by exclude.
261
- extend-exclude = [
262
- "webchanges/storage_minidb.py",
263
- "webchanges/_vendored",
264
- ]
215
+ extend-exclude = ['webchanges/storage_minidb.py', 'webchanges/_vendored']
265
216
 
266
217
  # By default, Ruff will discover files matching *.py, *.pyi, *.ipynb, or pyproject.toml.
267
218
  # Include additional files
@@ -271,83 +222,90 @@ extend-exclude = [
271
222
  line-length = 120
272
223
 
273
224
  # Target Python version
274
- # target-version = "py311" # Commented out to infer from [project] requires-python
225
+ # target-version = 'py311' # Commented out to infer from [project] requires-python
275
226
 
276
227
  [tool.ruff.lint]
277
228
  # By default, Ruff enables Flake8's F rules, along with a subset of the E rules
278
229
  # Enable rules not enabled by default, and ignore specific rules.
279
230
  select = [
280
- "ANN", # flake8-annotations
281
- "S", # flake8-bandit
282
- "BLE", # flake8-blind-except
283
- "B", # flake8-bugbear
284
- "A", # flake8-builtin
285
- "C4", # flake8-comprehensions
286
- "DTZ", # flake8-datetimez
287
- # "EM", # flake8-errmsg # TODO
288
- "FA", # flake8-future-annotations
289
- "INT", # flake8-gettext
290
- "ISC", # flake8-implicit-str-concat
291
- "LOG", # flake8-logging
292
- # "G", # flake8-logging-format # TODO
293
- "PIE", # flake8-pie
294
- "PYI", # flake8-pyi
295
- "PT", # flake8-pytest-style
296
- "Q", # flake8-quotes
297
- "RSE", # flake8-raise
298
- "RET", # flake8-return
299
- "SIM", # flake8-simplify
300
- "TID", # flake8-tidy-imports
301
- "TD", # flake8-todos
302
- "TC", # flake8-type-checking
303
- # "ARG", # flake8-unused-arguments # TODO
304
- "PTH", # flake8-use-pathlib
305
- # "FLY", # flynt # TODO
306
- "I", # isort
307
- "C90", # mccabe
308
- "N", # pep8-naming
309
- "PERF", # Perflint
310
- "E", # pycodestyle errors
311
- "W", # pycodestyle warnings
312
- # "DOC", # pydoclint # TODO
313
- # "D", # pydocstyle # TODO
314
- "F", # Pyflakes
315
- # "PL", # Pylint # TODO
316
- "FURB", # refurb
317
- "RUF", # Ruff-specific rules
318
- # "TRY", # tryceratops # TODO
231
+ 'ANN', # flake8-annotations
232
+ 'S', # flake8-bandit
233
+ 'BLE', # flake8-blind-except
234
+ 'B', # flake8-bugbear
235
+ 'A', # flake8-builtin
236
+ 'C4', # flake8-comprehensions
237
+ 'DTZ', # flake8-datetimez
238
+ # 'EM', # flake8-errmsg # TODO
239
+ 'FA', # flake8-future-annotations
240
+ 'INT', # flake8-gettext
241
+ 'ISC', # flake8-implicit-str-concat
242
+ 'LOG', # flake8-logging
243
+ # 'G', # flake8-logging-format # Prefer f-string to lazy `%` formatting and OK with '+'
244
+ 'PIE', # flake8-pie
245
+ 'PYI', # flake8-pyi
246
+ 'PT', # flake8-pytest-style
247
+ 'Q', # flake8-quotes
248
+ 'RSE', # flake8-raise
249
+ 'RET', # flake8-return
250
+ 'SIM', # flake8-simplify
251
+ 'TID', # flake8-tidy-imports
252
+ 'TD', # flake8-todos
253
+ 'TC', # flake8-type-checking
254
+ # 'ARG', # flake8-unused-arguments # Code loses clarity
255
+ 'PTH', # flake8-use-pathlib
256
+ 'FLY', # flynt
257
+ 'I', # isort
258
+ 'C90', # mccabe
259
+ 'N', # pep8-naming
260
+ 'PERF', # Perflint
261
+ 'E', # pycodestyle errors
262
+ 'W', # pycodestyle warnings
263
+ # 'DOC', # pydoclint # TODO
264
+ # 'D', # pydocstyle # TODO
265
+ 'F', # Pyflakes
266
+ # 'PL', # Pylint # TODO
267
+ 'FURB', # refurb
268
+ 'RUF', # Ruff-specific rules
269
+ # 'TRY', # tryceratops # TODO
319
270
  ]
320
271
 
321
272
  ignore = [
322
- "PT011", # pytest.raises({exception}) is too broad, set the match parameter or use a more specific exception
323
- "PT030", # pytest.warns({warning}) is too broad, set the match parameter or use a more specific warning
324
- "RUF012", # Mutable class attributes should be annotated with `typing.ClassVar`
325
- "SIM105", # Use contextlib.suppress({exception}) instead of try-except-pass
326
- "SIM115", # Use a context manager for opening files
327
- "TD002", # Missing author in TODO
328
- "TD003", # Missing issue link for this TODO
273
+ 'FLY002', # Consider f-string instead of string join
274
+ # 'G003', # Logging statement uses `+`'
275
+ # 'G004', # Logging statement uses f-string (Prefer f-string to lazy `%` formatting)
276
+ 'PLC0415', # `import` should be at the top-level of a file
277
+ 'PT011', # pytest.raises({exception}) is too broad, set the match parameter or use a more specific exception
278
+ 'PT030', # pytest.warns({warning}) is too broad, set the match parameter or use a more specific warning
279
+ 'RUF012', # Mutable class attributes should be annotated with `typing.ClassVar`
280
+ 'SIM105', # Use contextlib.suppress({exception}) instead of try-except-pass
281
+ 'SIM115', # Use a context manager for opening files
282
+ 'TD002', # Missing author in TODO
283
+ 'TD003', # Missing issue link for this TODO
329
284
  ]
330
285
 
331
286
  # Allow fix for all enabled rules (when `--fix`) is provided.
332
- fixable = ["ALL"]
287
+ fixable = ['ALL']
333
288
  unfixable = []
334
289
 
335
290
  # Allow unused variables when underscore-prefixed.
336
- dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
291
+ dummy-variable-rgx = '^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$'
292
+
293
+ # List of allowed 'confusable' Unicode characters to ignore when enforcing RUF001, RUF002, and RUF003.
294
+ allowed-confusables = ['–', '‘', '’']
337
295
 
338
296
  [tool.ruff.lint.flake8-annotations]
339
- allow-star-arg-any = true # TODO
297
+ allow-star-arg-any = true # TODO
340
298
 
341
299
  [tool.ruff.lint.flake8-quotes]
342
- inline-quotes = "single"
300
+ inline-quotes = 'single'
343
301
 
344
302
  [tool.ruff.lint.mccabe]
345
303
  # Flag errors (`C901`) whenever the complexity level exceeds below.
346
304
  max-complexity = 30
347
305
 
348
306
  [tool.ruff.lint.per-file-ignores]
349
- "tests/*" = [
350
- "S101", # Use of `assert` detected
307
+ 'tests/*' = [
308
+ 'S101', # Use of `assert` detected
351
309
  ]
352
310
 
353
311
  [tool.ruff.lint.pydoclint]
@@ -355,7 +313,7 @@ max-complexity = 30
355
313
  ignore-one-line-docstrings = true
356
314
 
357
315
  [tool.ruff.lint.pydocstyle]
358
- convention = "google"
316
+ convention = 'google'
359
317
 
360
318
  [tool.ruff.format]
361
319
  # Enable the formatter, which is a drop-in replacement for Black.
@@ -365,16 +323,16 @@ convention = "google"
365
323
  exclude = []
366
324
 
367
325
  # Use single quotes for strings.
368
- quote-style = "single"
326
+ quote-style = 'single'
369
327
 
370
328
  # Like Black, indent with spaces, rather than tabs.
371
- indent-style = "space"
329
+ indent-style = 'space'
372
330
 
373
331
  # Like Black, respect magic trailing commas.
374
332
  skip-magic-trailing-comma = false
375
333
 
376
334
  # Like Black, automatically detect the appropriate line ending.
377
- line-ending = "auto"
335
+ line-ending = 'auto'
378
336
 
379
337
  # Enable auto-formatting of code examples in docstrings. Markdown,
380
338
  # reStructuredText code/literal blocks and doctests are all supported.
@@ -388,4 +346,14 @@ docstring-code-format = true
388
346
  #
389
347
  # This only has an effect when the `docstring-code-format` setting is
390
348
  # enabled.
391
- docstring-code-line-length = "dynamic"
349
+ docstring-code-line-length = 'dynamic'
350
+
351
+
352
+ # -------------------------- ty --------------------------
353
+ # Config file documentation at https://docs.astral.sh/ty/reference/configuration/
354
+
355
+ [tool.ty.environment]
356
+ extra-paths = ['./webchanges']
357
+
358
+ [tool.ty.src]
359
+ exclude = ['./webchanges/storage_minidb.py']
@@ -12,7 +12,7 @@ supported services. Can check the output of local commands as well.
12
12
 
13
13
  from __future__ import annotations
14
14
 
15
- __min_python_version__ = (3, 10) # minimum version of Python required to run; supported until fall 2025
15
+ __min_python_version__ = (3, 11) # minimum version of Python required to run; 3.11 supported until fall 2026
16
16
 
17
17
 
18
18
  __project_name__ = str(__package__)
@@ -22,7 +22,7 @@ __project_name__ = str(__package__)
22
22
  # * MINOR version when you add functionality in a backwards compatible manner, and
23
23
  # * MICRO or PATCH version when you make backwards compatible bug fixes. We no longer use '0'
24
24
  # If unsure on increments, use pkg_resources.parse_version to parse
25
- __version__ = '3.31.4'
25
+ __version__ = '3.34.0'
26
26
  __description__ = (
27
27
  'Check web (or command output) for changes since last run and notify.\n\nAnonymously alerts you of web changes.'
28
28
  )
@@ -50,7 +50,7 @@ def to_str(value: str | bytes, encoding: str = 'utf-8') -> str:
50
50
 
51
51
 
52
52
  def to_bytes_or_str(value: str, match_type_of: typing.AnyStr) -> typing.AnyStr:
53
- return value if isinstance(match_type_of, str) else value.encode()
53
+ return value if isinstance(match_type_of, str) else value.encode() # ty:ignore[invalid-return-type]
54
54
 
55
55
 
56
56
  # from https://github.com/encode/httpx/blob/master/httpx/_models.py
@@ -81,8 +81,8 @@ def _obfuscate_sensitive_headers(
81
81
  items: typing.Iterable[tuple[typing.AnyStr, typing.AnyStr]],
82
82
  ) -> typing.Iterator[tuple[typing.AnyStr, typing.AnyStr]]:
83
83
  for k, v in items:
84
- if to_str(k.lower()) in SENSITIVE_HEADERS:
85
- v = to_bytes_or_str('[secure]', match_type_of=v)
84
+ if to_str(k.lower()) in SENSITIVE_HEADERS: # ty:ignore[no-matching-overload, invalid-argument-type]
85
+ v = to_bytes_or_str('[secure]', match_type_of=v) # ty:ignore[invalid-argument-type]
86
86
  yield k, v
87
87
 
88
88
 
@@ -102,8 +102,8 @@ class Headers(typing.MutableMapping[str, str]):
102
102
  self._list = list(headers._list)
103
103
  elif isinstance(headers, Mapping):
104
104
  for k, v in headers.items():
105
- bytes_key = _normalize_header_key(k, encoding)
106
- bytes_value = _normalize_header_value(v, encoding)
105
+ bytes_key = _normalize_header_key(k, encoding) # ty:ignore[invalid-argument-type]
106
+ bytes_value = _normalize_header_value(v, encoding) # ty:ignore[invalid-argument-type]
107
107
  self._list.append((bytes_key, bytes_key.lower(), bytes_value))
108
108
  elif headers is not None:
109
109
  for k, v in headers:
@@ -24,9 +24,14 @@ from webchanges import __copyright__, __docs_url__, __min_python_version__, __pr
24
24
  from webchanges.config import CommandConfig
25
25
  from webchanges.util import file_ownership_checks, get_new_version_number, import_module_from_source
26
26
 
27
- # Ignore signal SIGPIPE ("broken pipe") for stdout (see https://github.com/thp/urlwatch/issues/77)
28
- if os.name != 'nt': # Windows does not have signal.SIGPIPE
27
+ # Restore the default system behavior for the SIGPIPE signal, which is ignored by Python by default.
28
+ # This prevents a BrokenPipeError when piping output to a command like `less` that may close the pipe before reading all
29
+ # of the output.
30
+ try:
29
31
  signal.signal(signal.SIGPIPE, signal.SIG_DFL) # type: ignore[attr-defined] # not defined in Windows
32
+ except AttributeError:
33
+ pass
34
+
30
35
 
31
36
  logger = logging.getLogger(__name__)
32
37
 
@@ -376,7 +381,7 @@ def main() -> None: # pragma: no cover
376
381
  python_version_warning()
377
382
 
378
383
  # Path where the config, jobs and hooks files are located
379
- if os.name != 'nt':
384
+ if sys.platform != 'win32':
380
385
  config_path = platformdirs.user_config_path(__project_name__) # typically ~/.config/{__project_name__}
381
386
  else:
382
387
  config_path = platformdirs.user_documents_path().joinpath(__project_name__)
@@ -471,10 +476,8 @@ def main() -> None: # pragma: no cover
471
476
  config_storage.load()
472
477
 
473
478
  # Setup database API
474
- database_engine = (
475
- command_config.database_engine or config_storage.config.get('database', {}).get('engine') or 'sqlite3'
476
- ) # "or 'sqlite3'" is not needed except for a mypy bug; same for the "or 4" below
477
- max_snapshots = command_config.max_snapshots or config_storage.config.get('database', {}).get('max_snapshots') or 4
479
+ database_engine = command_config.database_engine or config_storage.config.get('database', {}).get('engine')
480
+ max_snapshots = command_config.max_snapshots or config_storage.config.get('database', {}).get('max_snapshots')
478
481
  if database_engine == 'sqlite3':
479
482
  ssdb_storage: SsdbStorage = SsdbSQLite3Storage(command_config.ssdb_file, max_snapshots) # storage.py
480
483
  elif any(str(command_config.ssdb_file).startswith(prefix) for prefix in ('redis://', 'rediss://')):