compatibility 2.3.0__tar.gz → 2.4.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 (22) hide show
  1. {compatibility-2.3.0 → compatibility-2.4.0}/PKG-INFO +175 -20
  2. {compatibility-2.3.0 → compatibility-2.4.0}/README.md +169 -14
  3. {compatibility-2.3.0 → compatibility-2.4.0}/compatibility/core.py +152 -52
  4. {compatibility-2.3.0 → compatibility-2.4.0}/compatibility/locales/compatibility.pot +55 -37
  5. compatibility-2.4.0/compatibility/locales/de/LC_MESSAGES/compatibility.mo +0 -0
  6. {compatibility-2.3.0 → compatibility-2.4.0}/compatibility/locales/de/LC_MESSAGES/compatibility.po +13 -0
  7. compatibility-2.4.0/compatibility/locales/es/LC_MESSAGES/compatibility.mo +0 -0
  8. {compatibility-2.3.0 → compatibility-2.4.0}/compatibility/locales/es/LC_MESSAGES/compatibility.po +13 -0
  9. compatibility-2.4.0/compatibility/locales/fr/LC_MESSAGES/compatibility.mo +0 -0
  10. {compatibility-2.3.0 → compatibility-2.4.0}/compatibility/locales/fr/LC_MESSAGES/compatibility.po +13 -0
  11. compatibility-2.4.0/compatibility/locales/nl/LC_MESSAGES/compatibility.mo +0 -0
  12. {compatibility-2.3.0 → compatibility-2.4.0}/compatibility/locales/nl/LC_MESSAGES/compatibility.po +13 -0
  13. {compatibility-2.3.0 → compatibility-2.4.0}/pyproject.toml +7 -9
  14. compatibility-2.3.0/compatibility/locales/de/LC_MESSAGES/compatibility.mo +0 -0
  15. compatibility-2.3.0/compatibility/locales/es/LC_MESSAGES/compatibility.mo +0 -0
  16. compatibility-2.3.0/compatibility/locales/fr/LC_MESSAGES/compatibility.mo +0 -0
  17. compatibility-2.3.0/compatibility/locales/nl/LC_MESSAGES/compatibility.mo +0 -0
  18. {compatibility-2.3.0 → compatibility-2.4.0}/LICENSE +0 -0
  19. {compatibility-2.3.0 → compatibility-2.4.0}/compatibility/__init__.py +0 -0
  20. {compatibility-2.3.0 → compatibility-2.4.0}/compatibility/__main__.py +0 -0
  21. {compatibility-2.3.0 → compatibility-2.4.0}/compatibility/err.py +0 -0
  22. {compatibility-2.3.0 → compatibility-2.4.0}/compatibility/py.typed +0 -0
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: compatibility
3
- Version: 2.3.0
4
- Summary: A library that checks whether the running version of Python is compatible and tested. Remind the user to check for updates of the library.
3
+ Version: 2.4.0
4
+ Summary: A library that checks whether the running version of Python is compatible and tested. Can remind the user to check for updates of the library.
5
5
  License-Expression: Apache-2.0
6
6
  License-File: LICENSE
7
7
  Keywords: compatibility,version-check,python-version,cross-platform,update-notifier
@@ -27,11 +27,11 @@ Classifier: Topic :: Software Development :: Quality Assurance
27
27
  Classifier: Topic :: Utilities
28
28
  Classifier: Typing :: Typed
29
29
  Provides-Extra: dev
30
- Requires-Dist: coverage[toml] (>=7.16.1,<8) ; extra == "dev"
31
- Requires-Dist: mypy (>=2.3.1,<3) ; extra == "dev"
32
- Requires-Dist: pytest (>=9.1.1,<10) ; extra == "dev"
30
+ Requires-Dist: coverage[toml] (>=7.16.2,<8) ; extra == "dev"
31
+ Requires-Dist: mypy (>=2.4.0,<3) ; extra == "dev"
32
+ Requires-Dist: pytest (>=9.2.0,<10) ; extra == "dev"
33
33
  Requires-Dist: pytest-cov (>=7.1.0,<8) ; extra == "dev"
34
- Requires-Dist: ruff (>=0.16.8,<0.17) ; extra == "dev"
34
+ Requires-Dist: ruff (>=0.17.3,<0.18) ; extra == "dev"
35
35
  Project-URL: Bug Tracker, https://github.com/RuedigerVoigt/compatibility/issues
36
36
  Project-URL: Changelog, https://github.com/RuedigerVoigt/compatibility/blob/main/CHANGELOG.md
37
37
  Project-URL: Documentation, https://github.com/RuedigerVoigt/compatibility
@@ -51,11 +51,11 @@ Description-Content-Type: text/markdown
51
51
 
52
52
  Ensure your Python package runs on the right versions, warn users about untested versions, and gracefully handle incompatible environments.
53
53
 
54
- Compatibility is a lightweight, zero-dependency library that helps Python package authors and library developers provide a better user experience by checking Python version compatibility, operating system compatibility (Linux, macOS, Windows), and gently reminding users to update. Uses Python's standard `gettext` for translations and follows PEP 561 (typed package). Perfect for PyPI package maintainers who want to prevent cryptic errors and provide helpful guidance to users.
54
+ Compatibility is a lightweight, zero-dependency library that helps Python package authors and library developers provide a better user experience by checking Python version compatibility, operating system compatibility (Linux, macOS, Windows), and gently reminding users to update. Uses Python's standard `gettext` for translations and follows PEP 561 (typed package). Perfect for PyPI package maintainers who want to replace cryptic errors with helpful guidance to users.
55
55
 
56
56
  ## Why Use This Library?
57
57
 
58
- - **Prevent cryptic runtime errors** - Catch incompatible Python versions before they cause problems
58
+ - **Clear messages instead of cryptic errors** - Tell users when they run your package on an incompatible Python version
59
59
  - **Zero dependencies** - Uses only Python's standard library
60
60
  - **Fully typed** - Complete type hints (PEP 484) for better IDE support
61
61
  - **Multilingual** - Built-in English and German messages, plus AI-translated French, Dutch, and Spanish
@@ -67,12 +67,17 @@ Compatibility is a lightweight, zero-dependency library that helps Python packag
67
67
  ## Table of Contents
68
68
 
69
69
  - [What It Does](#what-it-does)
70
+ - [What It Does Not Do](#what-it-does-not-do)
70
71
  - [Key Features](#key-features)
71
72
  - [Installation](#installation)
72
73
  - [Quick Start](#quick-start)
73
74
  - [Detailed Usage](#detailed-usage)
74
75
  - [⚠️ Important: Where to Call Compatibility](#️-important-where-to-call-compatibility)
75
76
  - [Complete Example](#complete-example)
77
+ - [Recipes](#recipes)
78
+ - [Command-Line Tool: Configure Logging First](#command-line-tool-configure-logging-first)
79
+ - [Warn Instead of Raising](#warn-instead-of-raising)
80
+ - [Typed Configuration](#typed-configuration)
76
81
  - [Parameters](#parameters)
77
82
  - [Required Parameters](#required-parameters)
78
83
  - [Optional Parameters](#optional-parameters)
@@ -86,12 +91,21 @@ Compatibility is a lightweight, zero-dependency library that helps Python packag
86
91
 
87
92
  1. **Python Version Validation** - Check if the running Python interpreter meets minimum requirements and isn't in your list of incompatible versions. Raises `RuntimeError` for incompatible versions.
88
93
  2. **Untested Version Warnings** - Warn users when running your package on Python versions newer than you've tested.
89
- 3. **Package Version Logging** - Log package name, version, and release date for better debugging.
90
- 4. **Privacy-Friendly Update Reminders** - Optionally remind users to check for updates after N days (without phoning home or checking if updates exist).
91
- 5. **Operating System Compatibility** - Validate whether the OS (Linux, macOS, Windows) is fully supported, partially supported, or incompatible.
94
+ 3. **End-of-Life Warnings** - Optionally warn users whose Python version has reached its end of life and no longer receives security updates (built-in dates, no network access).
95
+ 4. **Package Version Logging** - Log package name, version, and release date for better debugging.
96
+ 5. **Privacy-Friendly Update Reminders** - Optionally remind users to check for updates after N days (without phoning home or checking if updates exist).
97
+ 6. **Operating System Compatibility** - Validate whether the OS (Linux, macOS, Windows) is fully supported, partially supported, or incompatible.
92
98
 
93
99
  All messages are available in English and German (plus AI-translated French, Dutch, and Spanish), selectable per-instance. See [Translations](#translations).
94
100
 
101
+ ## What It Does Not Do
102
+
103
+ - **No update lookup** - The update reminder only counts the days since your release date. It never contacts PyPI or any other server, so it cannot tell whether a newer version exists.
104
+ - **No dependency checks** - It checks the Python interpreter and the operating system, not the versions of other installed packages. Declare those as dependencies in your `pyproject.toml`, so `pip` resolves them.
105
+ - **No patch versions** - Python versions are matched on major and minor version, optionally with the release level (like `'3.12'` or `'3.12.beta'`). You cannot target a single patch release such as 3.12.1.
106
+ - **No OS versions or distributions** - Operating systems are only distinguished as Linux, MacOS, and Windows.
107
+ - **No protection before your code is imported** - The check runs in your constructor. An error while Python imports your modules comes first (see [Avoid running your package with an incompatible version of Python](#avoid-running-your-package-with-an-incompatible-version-of-python)).
108
+
95
109
  ## Key Features
96
110
 
97
111
  - **Zero Dependencies**: Pure Python stdlib - no external packages required
@@ -118,6 +132,7 @@ logging.basicConfig(level=logging.INFO)
118
132
 
119
133
  Here's a minimal example to get started:
120
134
 
135
+ <!-- tested -->
121
136
  ```python
122
137
  from datetime import date
123
138
  import logging
@@ -138,12 +153,21 @@ class MyPackage:
138
153
  'max_tested_version': '3.15'
139
154
  }
140
155
  )
156
+
157
+ MyPackage() # The check runs when the class is instantiated
158
+ ```
159
+
160
+ Output:
161
+
162
+ ```text
163
+ INFO:compatibility:You are using my_package 1.0.0 (released: 2025-01-01)
141
164
  ```
142
165
 
143
166
  ### Extended Example
144
167
 
145
168
  Here's a more complete example using all optional features:
146
169
 
170
+ <!-- tested -->
147
171
  ```python
148
172
  from datetime import date
149
173
  import logging
@@ -159,7 +183,7 @@ class MyAdvancedPackage:
159
183
  release_date=date(2025, 1, 15),
160
184
  python_version_support={
161
185
  'min_version': '3.10',
162
- 'incompatible_versions': ['3.9'],
186
+ 'incompatible_versions': ['3.13.alpha'], # e.g. a pre-release with a known bug
163
187
  'max_tested_version': '3.15'
164
188
  },
165
189
  nag_over_update={
@@ -171,8 +195,11 @@ class MyAdvancedPackage:
171
195
  'full': {'Linux', 'MacOS'}, # Fully tested
172
196
  'partial': {'Windows'}, # Should work, less tested
173
197
  'incompatible': set() # No known incompatibilities
174
- }
198
+ },
199
+ warn_eol=True # Warn if the running Python has reached its end of life
175
200
  )
201
+
202
+ MyAdvancedPackage()
176
203
  ```
177
204
 
178
205
  ## Detailed Usage
@@ -192,7 +219,7 @@ class MyClass:
192
219
  compatibility.Check(...) # Runs when instantiated, after logging is configured
193
220
  ```
194
221
 
195
- **Why?** If you call it in `__init__.py`, it runs immediately on import before users can configure their logging levels. This means users might see unwanted DEBUG messages. Calling it in the class constructor lets users set up logging first.
222
+ **Why?** If you call it in `__init__.py`, it runs immediately on import, usually before the application has configured logging. Its messages are then lost: the `compatibility` logger has no handler of its own, so nothing is shown until logging is set up. Calling it in the class constructor lets users set up logging first. It also keeps your package importable on an incompatible system (for example for documentation tools), as the `RuntimeError` is only raised when your class is used.
196
223
 
197
224
  ### Complete Example
198
225
 
@@ -236,16 +263,132 @@ class Salted:
236
263
  These settings ensure the package runs on Python 3.10 or higher, with testing confirmed through Python 3.15.
237
264
  Salted in that specific version is a relatively young package that will receive frequent updates. So beginning a month after the release this will nag the user over looking for an update every second time - provided the user activated logging.
238
265
 
266
+ ## Recipes
267
+
268
+ The test suite runs every example in this section, as well as the Quick Start and the Extended Example.
269
+
270
+ ### Command-Line Tool: Configure Logging First
271
+
272
+ In a command-line tool, configure logging first and create the class that runs the check afterwards. Otherwise the messages are lost:
273
+
274
+ <!-- tested -->
275
+ ```python
276
+ import argparse
277
+ from datetime import date
278
+ import logging
279
+
280
+ import compatibility
281
+
282
+
283
+ class MyTool:
284
+ def __init__(self):
285
+ compatibility.Check(
286
+ package_name='my_tool',
287
+ package_version='1.0.0',
288
+ release_date=date(2025, 1, 1))
289
+
290
+
291
+ def main(argv=None):
292
+ parser = argparse.ArgumentParser(prog='my_tool')
293
+ parser.add_argument('--verbose', action='store_true')
294
+ args = parser.parse_args(argv)
295
+ # Configure logging before anything creates MyTool:
296
+ logging.basicConfig(level=logging.DEBUG if args.verbose else logging.INFO)
297
+ MyTool()
298
+
299
+
300
+ if __name__ == '__main__':
301
+ main()
302
+ ```
303
+
304
+ Output:
305
+
306
+ ```text
307
+ INFO:compatibility:You are using my_tool 1.0.0 (released: 2025-01-01)
308
+ ```
309
+
310
+ ### Warn Instead of Raising
311
+
312
+ By default an incompatible Python version or operating system raises `RuntimeError`. With `on_incompatible='warn'` it only logs a warning, and your package keeps running. Use this if your users should decide whether to continue:
313
+
314
+ <!-- tested -->
315
+ ```python
316
+ from datetime import date
317
+ import logging
318
+
319
+ import compatibility
320
+
321
+ logging.basicConfig(level=logging.INFO)
322
+
323
+
324
+ class MyPackage:
325
+ def __init__(self):
326
+ compatibility.Check(
327
+ package_name='my_package',
328
+ package_version='1.0.0',
329
+ release_date=date(2025, 1, 1),
330
+ python_version_support={
331
+ 'min_version': '3.10',
332
+ 'incompatible_versions': [],
333
+ 'max_tested_version': '3.15'},
334
+ system_support={'full': {'Linux', 'MacOS', 'Windows'}},
335
+ on_incompatible='warn')
336
+
337
+
338
+ MyPackage()
339
+ ```
340
+
341
+ With `on_incompatible='ignore'` the message is only logged at DEBUG level.
342
+
343
+ ### Typed Configuration
344
+
345
+ The parameter dictionaries have matching `TypedDict` types: `PythonVersionSupport`, `SystemSupport`, and `NagOverUpdate`. Annotate your configuration with them, and a type checker such as mypy reports a misspelled or missing key before your code runs:
346
+
347
+ <!-- tested -->
348
+ ```python
349
+ from datetime import date
350
+ import logging
351
+
352
+ import compatibility
353
+ from compatibility import NagOverUpdate, PythonVersionSupport, SystemSupport
354
+
355
+ logging.basicConfig(level=logging.INFO)
356
+
357
+ PYTHON_SUPPORT: PythonVersionSupport = {
358
+ 'min_version': '3.10',
359
+ 'incompatible_versions': [],
360
+ 'max_tested_version': '3.15',
361
+ }
362
+ SYSTEM_SUPPORT: SystemSupport = {'full': {'Linux', 'MacOS', 'Windows'}}
363
+ NAG: NagOverUpdate = {'nag_days_after_release': 90, 'nag_in_hundred': 25}
364
+
365
+
366
+ class MyPackage:
367
+ def __init__(self) -> None:
368
+ compatibility.Check(
369
+ package_name='my_package',
370
+ package_version='1.0.0',
371
+ release_date=date(2025, 1, 1),
372
+ python_version_support=PYTHON_SUPPORT,
373
+ system_support=SYSTEM_SUPPORT,
374
+ nag_over_update=NAG)
375
+
376
+
377
+ MyPackage()
378
+ ```
379
+
239
380
  ## Parameters
240
381
 
241
382
  ### Required Parameters
242
383
 
243
384
  * `package_name`: the name of your package.
244
385
  * `package_version`: the version number of your package as a string.
245
- * `release_date`: requires a `datetime` object (like `date(2021,1,1)`), or a string in the exact format `YYYY-MM-DD`.
386
+ * `release_date`: a `datetime.date` (like `date(2021, 1, 1)`), or a string in the format `YYYY-MM-DD` (like `'2021-01-01'`; leading zeros are optional). A `datetime.datetime` is accepted too; its time is ignored.
246
387
 
247
388
  ### Optional Parameters
248
389
 
390
+ For `python_version_support`, `nag_over_update`, and `system_support`, leaving the parameter out, passing `None`, or passing an empty dictionary `{}` all skip that check. Any other value that is not a dictionary raises a `ValueError`.
391
+
249
392
  * `python_version_support`: requires a dictionary with the three following keys:
250
393
  * `min_version`: a string with the number of the oldest supported version (like `'3.10'`).
251
394
  * `incompatible_versions`: a list of incompatible versions that, by default, raise the `RuntimeError` exception if they try to run your package (configurable with `on_incompatible`).
@@ -258,10 +401,13 @@ Salted in that specific version is a relatively young package that will receive
258
401
  * `full`: The set of operating systems that are tested on production level.
259
402
  * `partial`: The set of systems that should work, but are not as rigorously tested as those with full support. A system found running here logs a warning.
260
403
  * `incompatible`: The set of systems of which you know they will fail to run the code properly. By default, if an OS in this set tries to run the code, this will yield a `RuntimeError` exception (configurable with `on_incompatible`).
404
+
405
+ An operating system may appear in only one of the three sets; listing it in two raises `ParameterContradiction`.
261
406
  * `on_incompatible` (optional): what to do when the running Python version or operating system is incompatible. One of:
262
407
  * `'raise'` (default): raise a `RuntimeError` — the current behaviour.
263
408
  * `'warn'`: log a warning and continue.
264
409
  * `'ignore'`: continue silently (logged at DEBUG only). You take responsibility for running in an unsupported environment.
410
+ * `warn_eol` (optional): set to `True` to log a warning if the running Python version has reached its [end of life](https://devguide.python.org/versions/) and no longer receives security updates. Defaults to `False`. The end-of-life dates are built in, so the check works offline; a Python version newer than the installed release of this library is not reported.
265
411
 
266
412
  **System Support Behavior:**
267
413
 
@@ -288,10 +434,15 @@ However, `min_version` and `max_tested_version` ignore the release level part.
288
434
 
289
435
  ## Avoid running your package with an incompatible version of Python
290
436
 
291
- In the `pyproject.toml` file of your package you can use the [python_requires](https://packaging.python.org/guides/distributing-packages-using-setuptools/#python-requires) parameter (or the `python` field in Poetry's `[tool.poetry.dependencies]`) to tell `pip` about incompatible versions of the interpreter. This should block installation on incompatible systems. However, users can circumvent this by setting the flag `--python-version`. More likely is a system upgrade that installs an incompatible version with the system's package manager.
437
+ In the `pyproject.toml` file of your package, set [`requires-python`](https://packaging.python.org/en/latest/guides/writing-pyproject-toml/#python-requires) in the `[project]` table (for example `requires-python = ">=3.10"`) to tell `pip` about the supported versions of the interpreter. This blocks installation on incompatible systems. However, users can circumvent this with pip's `--ignore-requires-python` flag. More likely is a system upgrade that replaces the interpreter with an incompatible version through the system's package manager.
292
438
 
293
439
  If you define incompatible versions while initializing the `compatibility` package, you add another layer of control. Even if your user ended up with an incompatible interpreter, that will trigger a `RuntimeError` exception once the user tries to run your package.
294
440
 
441
+ This works only if Python gets as far as running the check:
442
+
443
+ * `compatibility` itself runs on Python 3.10 or newer. On an older interpreter it cannot import, so it cannot show any message.
444
+ * Your package's modules are imported before your constructor runs. If one of them uses syntax or a feature that the running Python lacks, the user gets a `SyntaxError` or `ImportError` before the check. For example, `except*` and `typing.Self` need Python 3.11 and `class C[T]:` needs Python 3.12.
445
+
295
446
  ## Logging
296
447
 
297
448
  The `compatibility` package uses Python's standard logging module with a dedicated logger named `'compatibility'`. This allows you to control the verbosity of compatibility messages independently from your application's logging.
@@ -329,7 +480,7 @@ logging.getLogger('compatibility').setLevel(logging.DEBUG)
329
480
  The compatibility package logs at different levels:
330
481
  * **DEBUG**: Informational messages about full platform support
331
482
  * **INFO**: Package version information and update reminders
332
- * **WARNING**: Running newer Python than tested, or partial platform support
483
+ * **WARNING**: Running newer Python than tested, a Python past its end of life (with `warn_eol=True`), or partial platform support
333
484
  * **ERROR**: Incompatible Python version or operating system (also raises exceptions)
334
485
 
335
486
  ### Example Messages
@@ -343,6 +494,7 @@ INFO:compatibility:You are using my_package 1.0.0 (released: 2025-01-01)
343
494
  INFO:compatibility:Your version of my_package was released 120 days ago. Please check for updates.
344
495
  WARNING:compatibility:my_package has only partial support on Windows.
345
496
  WARNING:compatibility:You are running Python 3.16.final, but your version of my_package is only tested up to 3.15. Please check for updates.
497
+ WARNING:compatibility:Python 3.10 reached its end of life (2026-10-01) and no longer receives security updates. Please upgrade to a newer version of Python.
346
498
  ERROR:compatibility:This version of my_package is incompatible with Windows!
347
499
  ```
348
500
 
@@ -351,7 +503,7 @@ An incompatible Python version (below `min_version`, or listed in
351
503
  `on_incompatible='raise'`, additionally raises `RuntimeError`:
352
504
 
353
505
  ```text
354
- ERROR:compatibility:You use Python 3.9.final, but need at least Python 3.10 to run my_package.
506
+ ERROR:compatibility:You use Python 3.10.final, but need at least Python 3.11 to run my_package.
355
507
  ```
356
508
 
357
509
  The same messages appear translated when you select another language (for
@@ -388,7 +540,7 @@ The `compatibility` package may raise the following exceptions:
388
540
  ## Use Cases
389
541
 
390
542
  ### When Your Package Requires Specific Python Versions
391
- Use `min_version` to prevent your package from running on older Python versions that lack required features (like match statements, structural pattern matching, or newer typing features).
543
+ Use `min_version` to stop your package from running on older Python versions that lack features it relies on at runtime, for example a standard library function or behavior added in a later version. A check in your constructor cannot catch syntax your modules use, as that fails on import (see [Avoid running your package with an incompatible version of Python](#avoid-running-your-package-with-an-incompatible-version-of-python)).
392
544
 
393
545
  ### When You Know Specific Python Versions Are Broken
394
546
  Use `incompatible_versions` to block specific Python versions where your package has known issues (e.g., bugs in Python itself, or dependencies that break on certain versions).
@@ -396,8 +548,11 @@ Use `incompatible_versions` to block specific Python versions where your package
396
548
  ### When Users Report Bugs on Untested Python Versions
397
549
  Use `max_tested_version` to warn users when they're running your package on newer Python versions you haven't tested yet. This helps manage expectations and reduces false bug reports.
398
550
 
551
+ ### When Your Users Still Run an Unmaintained Python
552
+ Set `warn_eol=True` to warn users whose Python version has reached its end of life. Such a version gets no security fixes anymore, even if your package still supports it. The warning is opt-in, so it never surprises users of a package that did not ask for it.
553
+
399
554
  ### When You Drop Support for Old Python Versions
400
- After dropping (for example) Python 3.9 support, use this library to give users a clear error message instead of cryptic import errors or runtime failures.
555
+ After dropping (for example) Python 3.10 support, set `min_version` to `'3.11'`. Users who still end up on 3.10 get a clear error message instead of a cryptic runtime failure, as long as your package still imports on 3.10.
401
556
 
402
557
  ### When Your Package Only Works on Certain Operating Systems
403
558
  Use `system_support` to declare which operating systems (Linux, macOS, Windows) are fully supported, partially supported, or incompatible. For example, if your package uses Linux-specific system calls.