From 479896da02eedb012db758215b844fe133550fea Mon Sep 17 00:00:00 2001 From: Jaime Gonzalez <76098522+Jtachan@users.noreply.github.com> Date: Wed, 17 Dec 2025 10:21:57 +0100 Subject: [PATCH 01/17] Docs typos --- docs/enum.md | 2 +- docs/index.md | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/docs/enum.md b/docs/enum.md index 13dab06..66fad10 100644 --- a/docs/enum.md +++ b/docs/enum.md @@ -61,7 +61,7 @@ True IntFlag is the same as Flag, but its members are also integers and can be used anywhere that an integer can be used. -> **None:** [`__str__()`](https://docs.python.org/3/reference/datamodel.html#object.__str__) is now `int.__str__()` to better support the replacement of existing constants use-case. +> **Note:** [`__str__()`](https://docs.python.org/3/reference/datamodel.html#object.__str__) is now `int.__str__()` to better support the replacement of existing constants use-case. [`__format__()`](https://docs.python.org/3/reference/datamodel.html#object.__format__) was already `int.__format__()` for that same reason. ## [_enum._**ReprEnum**](https://docs.python.org/3/library/enum.html#enum.ReprEnum) diff --git a/docs/index.md b/docs/index.md index dd9e940..857c010 100644 --- a/docs/index.md +++ b/docs/index.md @@ -46,7 +46,3 @@ Alternatively, any pip-install-git command can be called over the repository. ```commandline pip install git+https://github.com/Jtachan/PyBackport.git ``` - ---- -## Modules index - - [enum](enum.md) From 82598e3a2f4a6f07c9a5072e6b357eb1c9f3fa38 Mon Sep 17 00:00:00 2001 From: Jaime Gonzalez <76098522+Jtachan@users.noreply.github.com> Date: Wed, 17 Dec 2025 10:54:51 +0100 Subject: [PATCH 02/17] Better shield for python --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 8042acb..bfc2d75 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ ![tests_badge](https://github.com/Jtachan/PyBackport/actions/workflows/unittests.yml/badge.svg) [![PyPI Version](https://img.shields.io/pypi/v/PyBackport)](https://pypi.org/project/PyBackport/) -[![Python Version](https://img.shields.io/badge/python-3.8+-blue)](https://www.python.org/downloads/) +[![Python Version](https://img.shields.io/badge/python-3.8%20|%203.9%20|%203.10%20|%203.11-blue)](https://www.python.org/downloads/) [![MIT License](https://img.shields.io/github/license/Jtachan/PyBackport)](https://github.com/Jtachan/PyBackport/blob/master/LICENSE) [![PyPI Downloads](https://img.shields.io/pypi/dm/PyBackport)](https://pypi.org/project/PyBackport/) [![Docs](https://img.shields.io/badge/Read_the_docs-blue)](https://Jtachan.github.io/PyBackport/) @@ -19,7 +19,6 @@ class Animal(enum.StrEnum): CAT = "cat" ``` - ## Setup Install the package via pip. From 827e7ba52529d143913d94146f852df178026231 Mon Sep 17 00:00:00 2001 From: Jtachan Date: Wed, 11 Feb 2026 16:35:55 +0100 Subject: [PATCH 03/17] Docs: typo --- docs/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.md b/docs/index.md index 857c010..9fc82f2 100644 --- a/docs/index.md +++ b/docs/index.md @@ -5,7 +5,7 @@ _Python Backport_ main goal is to backport functionalities from newer python releases. It allows using its modules just as the original ones, with the only difference at the import. -Any backported (or experimental) functionality can be imported with the module `py_back`. +Any backported functionality can be imported with the module `py_back`. ```python from py_back import enum From 8c747a8d47d41a05c2dc589c417b2a6598311d50 Mon Sep 17 00:00:00 2001 From: Jtachan Date: Wed, 11 Feb 2026 16:38:06 +0100 Subject: [PATCH 04/17] Defined docs requirements --- .github/workflows/docs.yml | 9 +-------- docs/requirements.txt | 2 ++ 2 files changed, 3 insertions(+), 8 deletions(-) create mode 100644 docs/requirements.txt diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 6b9d1ac..b4f678d 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -13,12 +13,5 @@ jobs: - uses: actions/setup-python@v6 with: python-version: 3.x - - run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV - - uses: actions/cache@v3 - with: - key: mkdocs-material-${{ env.cache_id }} - path: .cache - restore-keys: | - mkdocs-material- - - run: pip install mkdocs-material + - run: pip install -r ./docs/requirements.txt - run: mkdocs gh-deploy --force diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 0000000..9c81b6a --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,2 @@ +mkdocs-material +mkdocstrings-python \ No newline at end of file From 13dc84745db05d528457839abbffcb5999b3e75b Mon Sep 17 00:00:00 2001 From: Jtachan Date: Wed, 11 Feb 2026 16:40:22 +0100 Subject: [PATCH 05/17] Mkdocstrings handler configuration --- mkdocs.yml | 39 +++++++++++++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/mkdocs.yml b/mkdocs.yml index 5679069..2820d8e 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -45,7 +45,38 @@ markdown_extensions: permalink: true plugins: - search - -extra: - version: - provider: mike + - autorefs + - mkdocstrings: + handlers: + python: # https://mkdocstrings.github.io/python/usage/ + options: + # General options + show_bases: true + show_source: false + # Headings + heading_level: 1 + parameter_headings: true + show_root_heading: true + show_root_full_path: true + show_root_members_full_path: false + show_category_heading: false + # Members + inherited_members: true + members_order: source + filters: + - "!^_.*$" + group_by_category: true + show_submodules: true + # Docstrings + docstring_style: numpy + docstring_section_style: table + merge_init_into_class: true + # Signatures + annotations_path: source + line_length: 88 + modernize_annotations: true + overloads_only: true + show_signature: true + show_signature_annotations: true + separate_signature: true + show_overloads: true From 97c41b9335e6d7a6c8fbf8e76e3c9b340917a905 Mon Sep 17 00:00:00 2001 From: Jtachan Date: Wed, 11 Feb 2026 19:09:29 +0100 Subject: [PATCH 06/17] Reorganized enum documentation --- docs/enum.md | 68 +----------------------- src/py_back/enum/__init__.py | 29 ++++++----- src/py_back/enum/_back_enum.py | 95 +++++++++++++++++++++++++++++++--- 3 files changed, 104 insertions(+), 88 deletions(-) diff --git a/docs/enum.md b/docs/enum.md index 66fad10..29c0f79 100644 --- a/docs/enum.md +++ b/docs/enum.md @@ -1,70 +1,4 @@ -# PyBackport: Enumerations - -`py_back` allows using the `enum` module just as the original, with the only difference at the import. - -```python -from py_back import enum - - -class Number(enum.IntEnum): - """Enumeration using the original 'IntEnum' call""" - ONE = enum.auto() - TWO = 2 - - -class Animal(enum.StrEnum): - """Supported original 'StrEnum' for python versions < 3.11""" - CAT = enum.auto() - DOG = "dog" -``` - ---- - -## [_enum._**IntEnum**](https://docs.python.org/3/library/enum.html#enum.IntEnum) - -> Backported from version 3.11: `str()` and `format()` - -_IntEnum_ is the same as _Enum_, but its members are also integers and can be used anywhere that an integer can be used. - -> **Note:** [`__str__()`](https://docs.python.org/3/reference/datamodel.html#object.__str__) is now `int.__str__()` to better support the replacement of existing constants use-case. -[`__format__()`](https://docs.python.org/3/reference/datamodel.html#object.__format__) was already `int.__format__()` for that same reason. - -## [_enum._**StrEnum**](https://docs.python.org/3.11/library/enum.html#enum.StrEnum) - -> Backported from version 3.11 - -_StrEnum_ is the same as [_Enum_](https://docs.python.org/3.12/library/enum.html#enum.Enum), but its members are also strings and can be used in most of the same places that a string can be used. - -```pycon ->>> from py_back import enum ->>> class Animal(enum.StrEnum): -... CAT = enum.auto() -... DOG = "dog" -... ->>> Animal.CAT -cat ->>> Animal.DOG.title() -'Dog' ->>> Animal.CAT == "cat" -True ->>> Animal.CAT + Animal.DOG -'catdog' ->>> " and ".join(list(Animals)) -'cat and dog' -``` - -**Note**: Using [`auto`](https://docs.python.org/3.12/library/enum.html#enum.auto) results in the lower-cased member name as the value. - -## [_enum._**IntFlag**](https://docs.python.org/3/library/enum.html#enum.IntFlag) - -> Backported from version 3.11: `str()` and `format()` - -IntFlag is the same as Flag, but its members are also integers and can be used anywhere that an integer can be used. - -> **Note:** [`__str__()`](https://docs.python.org/3/reference/datamodel.html#object.__str__) is now `int.__str__()` to better support the replacement of existing constants use-case. -[`__format__()`](https://docs.python.org/3/reference/datamodel.html#object.__format__) was already `int.__format__()` for that same reason. - -## [_enum._**ReprEnum**](https://docs.python.org/3/library/enum.html#enum.ReprEnum) +::: py_back.enum > Backported from version 3.11 diff --git a/src/py_back/enum/__init__.py b/src/py_back/enum/__init__.py index b162802..7551b64 100644 --- a/src/py_back/enum/__init__.py +++ b/src/py_back/enum/__init__.py @@ -1,20 +1,23 @@ """Module to backport 'enum' classes depending on the system python.""" import sys -import warnings -from enum import Enum, EnumMeta, Flag, auto, unique -__all__ = ["Enum", "EnumMeta", "EnumType", "Flag", "IntEnum", "IntFlag", "ReprEnum", "StrEnum", "auto", "unique"] +__all__ = [ + "Enum", + "EnumMeta", + "EnumType", + "Flag", + "IntEnum", + "IntFlag", + "ReprEnum", + "StrEnum", + "auto", + "unique", +] -if sys.version_info >= (3, 11): - from enum import EnumType, IntEnum, IntFlag, ReprEnum, StrEnum +if sys.version_info < (3, 11): + from enum import Enum, EnumMeta, Flag, auto, unique - warnings.warn( - "Importing from the standard enum library: " - "EnumType, IntEnum, IntFlag, ReprEnum, StrEnum\n" - "Consider 'from enum import ...' instead of 'from py_back.enum " - "import ...'", - stacklevel=2, - ) -else: from ._back_enum import EnumType, IntEnum, IntFlag, ReprEnum, StrEnum +else: + from enum import * diff --git a/src/py_back/enum/_back_enum.py b/src/py_back/enum/_back_enum.py index 4308884..3ed7846 100644 --- a/src/py_back/enum/_back_enum.py +++ b/src/py_back/enum/_back_enum.py @@ -1,4 +1,24 @@ -"""Backported enum types. Each class defines from which python version it was backported.""" +"""Backported enum types. + +All enumerations can be imported just like the ones provided by python. +```python +from py_back import enum + +class Number(enum.IntEnum): + \"\"\"Enumeration using the original 'IntEnum' call\"\"\" + ONE = enum.auto() + TWO = 2 + + +class Animal(enum.StrEnum): + \"\"\"Supported original 'StrEnum' for python versions < 3.11\"\"\" + CAT = enum.auto() + DOG = "dog" +``` + +Importing the `py_back.enum` module ensures that only the required classes are +backported. +""" from __future__ import annotations @@ -6,16 +26,17 @@ from py_back import builtins -__all__ = ["EnumType", "IntEnum", "IntFlag", "ReprEnum", "StrEnum"] - # New in Python 3.11 EnumType = enum.EnumMeta class ReprEnum(enum.Enum): - """Updates 'repr', leaving 'str' and 'format' to the builtin class. + """Updates `repr`, leaving `str` and `format` to the builtin class. + + _ReprEnum_ uses the repr() of Enum, but the str() of the mixed-in data type. + The class is used for any builtin type enum. - Backported from py3.11. + Backported from Python 3.11. """ def __str__(self) -> str: @@ -30,21 +51,79 @@ def __format__(self, format_spec: str) -> str: class IntEnum(ReprEnum, enum.IntEnum): """Enum where members are also (and must be) ints. - Backported from py3.11 leaving the str & format to the builtin class. + `IntEnum` is the same as `Enum`, but its members are also integers and + can be used anywhere that an integer can be used. + + Backports + --------- + Python 3.11: + Class inherits from `ReprEnum` to leave the `str()` and `format()` to + the builtin class. + + Notes + ----- + [`__str__()`](https://docs.python.org/3/reference/datamodel.html#object.__str__) + is now `int.__str__()` to better support the replacement of existing + constants use-case. + [`__format__()`](https://docs.python.org/3/reference/datamodel.html#object.__format__) + was already `int.__format__()` for that same reason. """ class IntFlag(ReprEnum, enum.IntFlag): """Support for integer-based Flags. - Backported from py3.11 leaving the str & format to the builtin class. + IntFlag is the same as Flag, but its members are also integers and can be + used anywhere that an integer can be used. + + Backports + --------- + Python 3.11: + Class inherits from `ReprEnum` to leave the `str()` and `format()` to + the builtin class. + + Notes + ----- + [`__str__()`](https://docs.python.org/3/reference/datamodel.html#object.__str__) + is now `int.__str__()` to better support the replacement of existing + constants use-case. + [`__format__()`](https://docs.python.org/3/reference/datamodel.html#object.__format__) + was already `int.__format__()` for that same reason. """ class StrEnum(builtins.str, ReprEnum): """Enum where members are also (and must be) strings. - Backported from py3.11. + `StrEnum` is the same as + [`Enum`](https://docs.python.org/3.12/library/enum.html#enum.Enum), but + its members are also strings and can be used in most of the same places + that a string can be used. + + Examples + -------- + ```pycon + >>> from py_back import enum + >>> class Animal(enum.StrEnum): + ... CAT = enum.auto() + ... DOG = "dog" + ... + >>> Animal.CAT + cat + >>> Animal.DOG.title() + 'Dog' + >>> Animal.CAT == "cat" + True + >>> Animal.CAT + Animal.DOG + 'catdog' + >>> " and ".join(list(Animals)) + 'cat and dog' + ``` + + Notes + ----- + Using [`auto`](https://docs.python.org/3.12/library/enum.html#enum.auto) + results in the lower-cased member name as the value. """ def __new__(cls, *values) -> StrEnum: From f78f6f8f35ceb37750f93b8a919681b5e1e3c679 Mon Sep 17 00:00:00 2001 From: Jtachan Date: Wed, 11 Feb 2026 19:12:28 +0100 Subject: [PATCH 07/17] Updated ruff rules --- docs/requirements.txt | 3 ++- ruff.toml | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 9c81b6a..1f1a032 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,3 @@ mkdocs-material -mkdocstrings-python \ No newline at end of file +mkdocstrings-python +ruff \ No newline at end of file diff --git a/ruff.toml b/ruff.toml index 0d05419..901db84 100644 --- a/ruff.toml +++ b/ruff.toml @@ -1,5 +1,5 @@ output-format = "concise" -line-length = 120 +line-length = 88 [format] docstring-code-format = true From c257fafeae9659cf044e2e05dc30a9e6476627e6 Mon Sep 17 00:00:00 2001 From: Jtachan <76098522+Jtachan@users.noreply.github.com> Date: Thu, 12 Feb 2026 10:00:13 +0100 Subject: [PATCH 08/17] Rules: mkdocs style and ruff checks (also on specific files) --- mkdocs.yml | 4 ++-- ruff.toml | 12 +++++++++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/mkdocs.yml b/mkdocs.yml index 2820d8e..2612bf9 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -8,7 +8,7 @@ nav: - Enum: "enum.md" theme: - name: material + name: readthedocs palette: - media: "(prefers-color-scheme: light)" scheme: default @@ -61,7 +61,7 @@ plugins: show_root_members_full_path: false show_category_heading: false # Members - inherited_members: true + inherited_members: false members_order: source filters: - "!^_.*$" diff --git a/ruff.toml b/ruff.toml index 901db84..8af77e5 100644 --- a/ruff.toml +++ b/ruff.toml @@ -49,11 +49,21 @@ ignore = [ "PLR0917", # too-many-positional-arguments "PLC1901", # compare-to-empty-string "PLR2004", # magic-value-comparison + "RUF067", # non empty init module ] [lint.per-file-ignores] "tests/*" = [ - "A004","PLC0415", + "A004", + "PLC0415", +] +"src/**/builtins/*" = [ + "A001", # Builtin variable shadowing + "N801", # Invalid class name (not CamelCase) +] +"src/**/__init__.py" = [ + "F403", # Unable to detect undefined names wiht `from ... import *` + "F405", # Unable to detect (un)defined names due to `from ... import *` ] [lint.flake8-annotations] From 513af79b04812044f6bc42a8dadb5a78c65175f6 Mon Sep 17 00:00:00 2001 From: Jtachan <76098522+Jtachan@users.noreply.github.com> Date: Thu, 12 Feb 2026 10:00:55 +0100 Subject: [PATCH 09/17] Docs: Finalized (and tested) enum docs --- docs/enum.md | 5 ----- src/py_back/enum/__init__.py | 28 ++++++++++++++++++----- src/py_back/enum/_back_enum.py | 41 ++++++++++++---------------------- 3 files changed, 37 insertions(+), 37 deletions(-) diff --git a/docs/enum.md b/docs/enum.md index 29c0f79..b575cf5 100644 --- a/docs/enum.md +++ b/docs/enum.md @@ -1,6 +1 @@ ::: py_back.enum - -> Backported from version 3.11 - -_ReprEnum_ uses the repr() of Enum, but the str() of the mixed-in data type. -The class is used for any builtin type enum. diff --git a/src/py_back/enum/__init__.py b/src/py_back/enum/__init__.py index 7551b64..54c2b9f 100644 --- a/src/py_back/enum/__init__.py +++ b/src/py_back/enum/__init__.py @@ -1,4 +1,24 @@ -"""Module to backport 'enum' classes depending on the system python.""" +"""Backported enum types. + +All enumerations can be imported just like the ones provided by python. +```python +from py_back import enum + +class Number(enum.IntEnum): + \"\"\"Enumeration using the original 'IntEnum' call\"\"\" + ONE = enum.auto() + TWO = 2 + + +class Animal(enum.StrEnum): + \"\"\"Supported original 'StrEnum' for python versions < 3.11\"\"\" + CAT = enum.auto() + DOG = "dog" +``` + +Importing the `py_back.enum` module ensures that only the required classes are +backported. +""" import sys @@ -15,9 +35,7 @@ "unique", ] -if sys.version_info < (3, 11): - from enum import Enum, EnumMeta, Flag, auto, unique +from enum import * +if sys.version_info < (3, 11): from ._back_enum import EnumType, IntEnum, IntFlag, ReprEnum, StrEnum -else: - from enum import * diff --git a/src/py_back/enum/_back_enum.py b/src/py_back/enum/_back_enum.py index 3ed7846..07a8dab 100644 --- a/src/py_back/enum/_back_enum.py +++ b/src/py_back/enum/_back_enum.py @@ -1,42 +1,29 @@ -"""Backported enum types. - -All enumerations can be imported just like the ones provided by python. -```python -from py_back import enum - -class Number(enum.IntEnum): - \"\"\"Enumeration using the original 'IntEnum' call\"\"\" - ONE = enum.auto() - TWO = 2 - - -class Animal(enum.StrEnum): - \"\"\"Supported original 'StrEnum' for python versions < 3.11\"\"\" - CAT = enum.auto() - DOG = "dog" -``` - -Importing the `py_back.enum` module ensures that only the required classes are -backported. -""" +"""Module to backport 'enum' classes depending on the system python.""" from __future__ import annotations import enum +import sys from py_back import builtins # New in Python 3.11 -EnumType = enum.EnumMeta +if sys.version_info < (3, 11): + EnumType = enum.EnumMeta +else: + EnumType = enum.EnumType -class ReprEnum(enum.Enum): +class ReprEnum(EnumType): """Updates `repr`, leaving `str` and `format` to the builtin class. - _ReprEnum_ uses the repr() of Enum, but the str() of the mixed-in data type. + `ReprEnum` uses the `repr()` of Enum, but the `str()` of the mixed-in data type. The class is used for any builtin type enum. - Backported from Python 3.11. + Backports + --------- + **Python 3.11**: + The class was first defined. """ def __str__(self) -> str: @@ -56,7 +43,7 @@ class IntEnum(ReprEnum, enum.IntEnum): Backports --------- - Python 3.11: + **Python 3.11**: Class inherits from `ReprEnum` to leave the `str()` and `format()` to the builtin class. @@ -78,7 +65,7 @@ class IntFlag(ReprEnum, enum.IntFlag): Backports --------- - Python 3.11: + **Python 3.11**: Class inherits from `ReprEnum` to leave the `str()` and `format()` to the builtin class. From 16c2af36da151d590e1da528e1a9732aa6ad8225 Mon Sep 17 00:00:00 2001 From: Jtachan <76098522+Jtachan@users.noreply.github.com> Date: Thu, 12 Feb 2026 10:01:16 +0100 Subject: [PATCH 10/17] Tests: Enum (updated with new rules) --- tests/test_enums.py | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/tests/test_enums.py b/tests/test_enums.py index 01f5c70..df5212a 100644 --- a/tests/test_enums.py +++ b/tests/test_enums.py @@ -1,27 +1,10 @@ """Test the 'enums' module.""" import sys -import warnings import pytest -def test_import_warnings(): - """Checking the warning is printed for all versions that don't backport the classes.""" - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter("always") - from py_back import enum # noqa: F401 - - if sys.version_info >= (3, 11): - match_text = "Consider 'from enum import ...' instead of 'from py_back.enum import ...'" - assert len(w) == 1, "Expected a warning but none was raised" - assert issubclass(w[-1].category, UserWarning) - assert match_text in str(w[-1].message) - else: - assert len(w) == 0 - - -@pytest.mark.skipif(sys.version_info >= (3, 11), reason="ReprEnum is available from python 3.11") def test_repr_enum(): """Tests for enum representations. @@ -50,7 +33,6 @@ class Number(IntEnum): assert str(Number.TWO) == "2" -@pytest.mark.skipif(sys.version_info >= (3, 11), reason="StrEnum is available from python 3.11") def test_str_enum(): """Testing py_back.enum.StrEnum.""" from py_back import enum From 0da211d099e0281cd260ae954312e060c99e6147 Mon Sep 17 00:00:00 2001 From: Jtachan <76098522+Jtachan@users.noreply.github.com> Date: Thu, 12 Feb 2026 10:03:25 +0100 Subject: [PATCH 11/17] Tests: Enum (updated with new rules) --- tests/test_builtins.py | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/tests/test_builtins.py b/tests/test_builtins.py index 1212f36..fd4f93f 100644 --- a/tests/test_builtins.py +++ b/tests/test_builtins.py @@ -1,27 +1,6 @@ """Test the 'builtins' module.""" -import sys -import warnings -import pytest - - -def test_import_warnings(): - """Checking the warning is printed for all versions that don't backport the classes.""" - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter("always") - from py_back import builtins # noqa: F401 - - if sys.version_info >= (3, 9): - match_text = "Consider 'from builtins import ...' instead of 'from py_back.builtins import ...'" - assert len(w) == 1, "Expected a warning but none was raised" - assert issubclass(w[-1].category, UserWarning) - assert match_text in str(w[-1].message) - else: - assert len(w) == 0 - - -@pytest.mark.skipif(sys.version_info >= (3, 9), reason="Features included at python 3.9") def test_backported_str(): """Here are tested all methods backported for the 'str' class.""" from py_back.builtins import str @@ -38,7 +17,6 @@ def test_backported_str(): assert str("TmpDirMixin").removesuffix("Tests") == "TmpDirMixin" -@pytest.mark.skipif(sys.version_info >= (3, 9), reason="Features included at python 3.9") def test_backported_dict(): """Here are tested all methods backported for the 'str' class.""" from py_back.builtins import dict From 12236f3c02c4cd806e9687c3e7f103189dc457eb Mon Sep 17 00:00:00 2001 From: Jtachan <76098522+Jtachan@users.noreply.github.com> Date: Thu, 12 Feb 2026 10:03:37 +0100 Subject: [PATCH 12/17] Tests: builtins (updated with new rules) --- tests/test_enums.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/test_enums.py b/tests/test_enums.py index df5212a..9e537a1 100644 --- a/tests/test_enums.py +++ b/tests/test_enums.py @@ -1,9 +1,5 @@ """Test the 'enums' module.""" -import sys - -import pytest - def test_repr_enum(): """Tests for enum representations. From f75ceda0f3d337ba01e007be2902bc280785d623 Mon Sep 17 00:00:00 2001 From: Jtachan <76098522+Jtachan@users.noreply.github.com> Date: Thu, 12 Feb 2026 10:12:31 +0100 Subject: [PATCH 13/17] Ruff rules and attached license at main `__init__.py` --- ruff.toml | 2 ++ src/py_back/__init__.py | 13 ++++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/ruff.toml b/ruff.toml index 8af77e5..32b530a 100644 --- a/ruff.toml +++ b/ruff.toml @@ -59,9 +59,11 @@ ignore = [ ] "src/**/builtins/*" = [ "A001", # Builtin variable shadowing + "A004", # Shadowing a Python builtin through import "N801", # Invalid class name (not CamelCase) ] "src/**/__init__.py" = [ + "F401", # Imported but unused (false positive) "F403", # Unable to detect undefined names wiht `from ... import *` "F405", # Unable to detect (un)defined names due to `from ... import *` ] diff --git a/src/py_back/__init__.py b/src/py_back/__init__.py index 3d36aad..afa5357 100644 --- a/src/py_back/__init__.py +++ b/src/py_back/__init__.py @@ -1,3 +1,14 @@ -"""Python package to port functionalities from newer python versions back to older ones.""" +"""Backporting functionalities from newer python versions back to older ones. + +The software was created by Jtachan and with MIT license. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" __version__ = "0.3.0-1" From 0d3235c51b63efaea65359c46be9f85bd01aaa5a Mon Sep 17 00:00:00 2001 From: Jtachan <76098522+Jtachan@users.noreply.github.com> Date: Thu, 12 Feb 2026 10:48:42 +0100 Subject: [PATCH 14/17] Docs: Reorganized builtins --- docs/builtins.md | 58 +---------------- docs/enum.md | 2 + mkdocs.yml | 6 +- src/py_back/builtins/__init__.py | 35 ++++++---- src/py_back/builtins/_back_builtins.py | 90 +++++++++++++++++++------- 5 files changed, 96 insertions(+), 95 deletions(-) diff --git a/docs/builtins.md b/docs/builtins.md index 0804224..22b3bbc 100644 --- a/docs/builtins.md +++ b/docs/builtins.md @@ -1,59 +1,5 @@ # PyBackport: Builtins -`py_back` allows using the `builtins` module just as the original. -However, they must be imported and initialized by converting the instance: +::: py_back.builtins -```pycon -# Python version lower than 3.9 ->>> from py_back.builtins import str - ->>> my_string = str("Hello world!") ->>> print(my_string.removesuffix("!")) -Hello world -``` - -## str - -### [_str_.**removeprefix**(prefix, /)](https://docs.python.org/3/library/stdtypes.html#str.removeprefix) - -If the string starts with the prefix string, return string[len(prefix):]. Otherwise, return a copy of the original string: - -```pycon -from py_back.builtins import str - ->>> str('TestHook').removeprefix('Test') -'Hook' - ->>> str('BaseTestCase').removeprefix('Test') -'BaseTestCase' -``` - -> Backported from python 3.9. - -### [_str_.**removesuffix**(suffix, /)](https://docs.python.org/3/library/stdtypes.html#str.removesuffix) - -If the string ends with the suffix string and that suffix is not empty, return string[:-len(suffix)]. Otherwise, return a copy of the original string: - -```pycon -from py_back.builtins import str - ->>> str('MiscTests').removesuffix('Tests') -'Misc' - ->>> str('TmpDirMixin').removesuffix('Tests') -'TmpDirMixin' -``` - -## dict - -### [d | other](https://docs.python.org/3/library/stdtypes.html#typesmapping:~:text=values()%0AFalse-,d%20%7C%20other,-Create%20a%20new) - -Create a new dictionary with the merged keys and values of d and other, which must both be dictionaries. The values of other take priority when d and other share keys. - -> Backported from python 3.9 - -### [d |= other](https://docs.python.org/3/library/stdtypes.html#typesmapping:~:text=in%20version%203.9.-,d%20%7C%3D%20other,-Update%20the%20dictionary) - -Update the dictionary d with keys and values from other, which may be either a [mapping](https://docs.python.org/3/glossary.html#term-mapping) or an [iterable](https://docs.python.org/3/glossary.html#term-iterable) of key/value pairs. The values of other take priority when d and other share keys. - -> Backported from python 3.9 +::: py_back.builtins._back_builtins diff --git a/docs/enum.md b/docs/enum.md index b575cf5..08281b4 100644 --- a/docs/enum.md +++ b/docs/enum.md @@ -1 +1,3 @@ +# PyBackport: Enumerations + ::: py_back.enum diff --git a/mkdocs.yml b/mkdocs.yml index 2612bf9..57c0492 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -54,7 +54,7 @@ plugins: show_bases: true show_source: false # Headings - heading_level: 1 + heading_level: 2 parameter_headings: true show_root_heading: true show_root_full_path: true @@ -63,8 +63,8 @@ plugins: # Members inherited_members: false members_order: source - filters: - - "!^_.*$" +# filters: +# - "!^_.*$" group_by_category: true show_submodules: true # Docstrings diff --git a/src/py_back/builtins/__init__.py b/src/py_back/builtins/__init__.py index 88817ee..09e3bbd 100644 --- a/src/py_back/builtins/__init__.py +++ b/src/py_back/builtins/__init__.py @@ -1,17 +1,26 @@ -"""Module to backport 'builtins' classes depending on the system python.""" +"""Module to backport 'builtins' classes depending on the system python. + +`py_back` allows using the `builtins` module just as the original. + +```pycon +# Python version lower than 3.9 +>>> from py_back.builtins import str +>>> my_string = str("Hello world!") +>>> print(my_string.removesuffix("!")) +Hello world +``` + +!!! Note + Python builtins don't require to be imported, but the backported builtins do not + follow this rule. This results in the inconvenience that backported instances must + be defined with the constructor provided by `py_back`, even if they interact with + other not backported builtins. +""" import sys -import warnings -from builtins import * # noqa: F403 +from builtins import * -__all__ = [name for name in dir() if not name.startswith("_")] +__all__ = ["str", "dict"] + list({name for name in dir() if not name.startswith("_")}) -if sys.version_info >= (3, 9): - warnings.warn( - "Importing from the standard builtins library: dict, str\n" - "Consider 'from builtins import ...' instead of 'from py_back.builtins " - "import ...'", - stacklevel=2, - ) -else: - from ._back_builtins import dict, str # noqa: F401, A004 +if sys.version_info < (3, 9): + from ._back_builtins import dict, str diff --git a/src/py_back/builtins/_back_builtins.py b/src/py_back/builtins/_back_builtins.py index 603813d..d3c4101 100644 --- a/src/py_back/builtins/_back_builtins.py +++ b/src/py_back/builtins/_back_builtins.py @@ -7,51 +7,85 @@ __all__ = ["dict", "str"] -class dict(builtins.dict): # noqa: A001, N801 +class dict(builtins.dict): """Backport for 'dict' class. - dict() -> new empty dictionary - dict(mapping) -> new dictionary initialized from a mapping object's - (key, value) pairs - dict(iterable) -> new dictionary initialized as if via: - d = {} - for k, v in iterable: - d[k] = v - dict(**kwargs) -> new dictionary initialized with the name=value pairs - in the keyword argument list. For example: dict(one=1, two=2) +
    +
  • dict() -> new empty dictionary.
  • +
  • dict(mapping) -> new dictionary initialized from a mapping + object's (key, value) pairs.
  • +
  • dict(iterable) -> new dictionary initialized as if.
  • +
  • dict(**kwargs) -> new dictionary initialized with the + name=value pairs in the keyword argument list. E.G.: + dict(one=1, two=2)
  • +
Backports --------- - Py 3.9 - d | other - d |= other + **Python 3.9**: + Operators [`d | other`](https://docs.python.org/3/library/stdtypes.html#typesmapping:~:text=values()%0AFalse-,d%20%7C%20other,-Create%20a%20new) + and [`d |= other`](https://docs.python.org/3/library/stdtypes.html#typesmapping:~:text=in%20version%203.9.-,d%20%7C%3D%20other,-Update%20the%20dictionary). """ def __or__(self, other: builtins.dict) -> dict: - """Return self|other, which is equivalent to self.update(other).""" + """Return `self | other`, which is equivalent to `self.update(other)`. + + Create a new dictionary with the merged keys and values of `d` and `other`, + which must both be dictionaries. The values of `other` take priority when `d` + and `other` share keys. + + The operation `other | d` is also supported. + + Notes + ----- + The dictionary defined as `other` does not need to be a backported instance. + + Examples + -------- + ```pycon + >>> from py_back.builtins import dict + >>> dict({"key_0": 1}) | {"key_1": 2} + {"key_0": 1, "key_1": 2} + ``` + """ d = self.copy() d.update(other) return d + def __ror__(self, other: builtins.dict) -> dict: + return self | other + -class str(builtins.str): # noqa: A001, N801 - """Backport for 'str' class. +class str(builtins.str): + """Backport for `str` class. - str(object='') -> str - str(bytes_or_buffer[, encoding[, errors]]) -> str +
    +
  • str(object='') -> str
  • +
  • str(bytes_or_buffer[, encoding[, errors]]) -> + str
  • +
Backports --------- - Py 3.9 - str.removeprefix(prefix, /) - str.removesuffix(suffix, /) + **Python 3.9**: + Methods `str.removeprefix(prefix, /)` and `str.removesuffix(suffix, /)` """ def removeprefix(self, prefix: str) -> str: """Backport logic to remove prefix from str. - If the string starts with the prefix string, return string[len(prefix):]. + If the string starts with the prefix string, return `string[len(prefix):]`. Otherwise, return a copy of the original string. + + Examples + -------- + ```pycon + from py_back.builtins import str + >>> str('TestHook').removeprefix('Test') + 'Hook' + >>> str('BaseTestCase').removeprefix('Test') + 'BaseTestCase' + ``` """ if prefix == self[: len(prefix)]: return self[len(prefix) :] @@ -61,7 +95,17 @@ def removesuffix(self, suffix: str) -> str: """Backport logic to remove suffix from str. If the string ends with the suffix string and that suffix is not empty, - return string[:-len(suffix)]. Otherwise, return a copy of the original string. + return `string[:-len(suffix)]`. Otherwise, return a copy of the original string. + + Examples + -------- + ```pycon + from py_back.builtins import str + >>> str('MiscTests').removesuffix('Tests') + 'Misc' + >>> str('TmpDirMixin').removesuffix('Tests') + 'TmpDirMixin' + ``` """ if suffix == self[-len(suffix) :]: return self[: -len(suffix)] From ddb9c1919e712c0feb5102468cc3e9ccd9e26564 Mon Sep 17 00:00:00 2001 From: Jtachan <76098522+Jtachan@users.noreply.github.com> Date: Thu, 12 Feb 2026 11:00:40 +0100 Subject: [PATCH 15/17] Fix: Docs, linters and tests --- .github/workflows/CI.yml | 1 + docs/builtins.md | 2 -- src/py_back/builtins/__init__.py | 5 +++-- tests/test_builtins.py | 7 +++++-- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 4115cba..7c3b285 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -21,6 +21,7 @@ jobs: - "3.11" - "3.12" - "3.13" + - "3.14" steps: - uses: actions/checkout@v5 diff --git a/docs/builtins.md b/docs/builtins.md index 22b3bbc..c4d5464 100644 --- a/docs/builtins.md +++ b/docs/builtins.md @@ -1,5 +1,3 @@ # PyBackport: Builtins ::: py_back.builtins - -::: py_back.builtins._back_builtins diff --git a/src/py_back/builtins/__init__.py b/src/py_back/builtins/__init__.py index 09e3bbd..acd7eea 100644 --- a/src/py_back/builtins/__init__.py +++ b/src/py_back/builtins/__init__.py @@ -18,9 +18,10 @@ """ import sys -from builtins import * -__all__ = ["str", "dict"] + list({name for name in dir() if not name.startswith("_")}) +__all__ = ["dict", "str"] + +from builtins import * if sys.version_info < (3, 9): from ._back_builtins import dict, str diff --git a/tests/test_builtins.py b/tests/test_builtins.py index fd4f93f..2044c91 100644 --- a/tests/test_builtins.py +++ b/tests/test_builtins.py @@ -1,5 +1,7 @@ """Test the 'builtins' module.""" +import sys + def test_backported_str(): """Here are tested all methods backported for the 'str' class.""" @@ -7,8 +9,9 @@ def test_backported_str(): old_str = "Hello world!" - for backported_method in ("removeprefix", "removesuffix"): - assert hasattr(old_str, backported_method) is False + if sys.version_info < (3, 9): + for backported_method in ("removeprefix", "removesuffix"): + assert hasattr(old_str, backported_method) is False assert str("TestHook").removeprefix("Test") == "Hook" assert str("BaseTestCase").removeprefix("Test") == "BaseTestCase" From 15e4ec6b3af649b43b842eb5b347eb626c4ace93 Mon Sep 17 00:00:00 2001 From: Jtachan <76098522+Jtachan@users.noreply.github.com> Date: Thu, 12 Feb 2026 11:18:30 +0100 Subject: [PATCH 16/17] Fix: Tests --- .github/workflows/CI.yml | 2 +- ruff.toml | 3 +-- src/py_back/enum/__init__.py | 4 +++- src/py_back/enum/_back_enum.py | 19 ++++++------------- tests/test_builtins.py | 6 ++---- tests/test_enums.py | 14 +++++++------- 6 files changed, 20 insertions(+), 28 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 7c3b285..5326b84 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -1,4 +1,4 @@ -name: CI Tests +name: Tests # Controls when the workflow will run on: diff --git a/ruff.toml b/ruff.toml index 32b530a..bd8e751 100644 --- a/ruff.toml +++ b/ruff.toml @@ -54,8 +54,7 @@ ignore = [ [lint.per-file-ignores] "tests/*" = [ - "A004", - "PLC0415", + "A004", # Shadowing builtins name ] "src/**/builtins/*" = [ "A001", # Builtin variable shadowing diff --git a/src/py_back/enum/__init__.py b/src/py_back/enum/__init__.py index 54c2b9f..05e3917 100644 --- a/src/py_back/enum/__init__.py +++ b/src/py_back/enum/__init__.py @@ -38,4 +38,6 @@ class Animal(enum.StrEnum): from enum import * if sys.version_info < (3, 11): - from ._back_enum import EnumType, IntEnum, IntFlag, ReprEnum, StrEnum + from ._back_enum import IntEnum, IntFlag, ReprEnum, StrEnum + + EnumType = EnumMeta diff --git a/src/py_back/enum/_back_enum.py b/src/py_back/enum/_back_enum.py index 07a8dab..7841a7d 100644 --- a/src/py_back/enum/_back_enum.py +++ b/src/py_back/enum/_back_enum.py @@ -2,19 +2,12 @@ from __future__ import annotations -import enum -import sys +import enum as _enum -from py_back import builtins +from ..builtins import str as _str -# New in Python 3.11 -if sys.version_info < (3, 11): - EnumType = enum.EnumMeta -else: - EnumType = enum.EnumType - -class ReprEnum(EnumType): +class ReprEnum(_enum.Enum): """Updates `repr`, leaving `str` and `format` to the builtin class. `ReprEnum` uses the `repr()` of Enum, but the `str()` of the mixed-in data type. @@ -35,7 +28,7 @@ def __format__(self, format_spec: str) -> str: return self.value.__format__(format_spec) -class IntEnum(ReprEnum, enum.IntEnum): +class IntEnum(ReprEnum, _enum.IntEnum): """Enum where members are also (and must be) ints. `IntEnum` is the same as `Enum`, but its members are also integers and @@ -57,7 +50,7 @@ class IntEnum(ReprEnum, enum.IntEnum): """ -class IntFlag(ReprEnum, enum.IntFlag): +class IntFlag(ReprEnum, _enum.IntFlag): """Support for integer-based Flags. IntFlag is the same as Flag, but its members are also integers and can be @@ -79,7 +72,7 @@ class IntFlag(ReprEnum, enum.IntFlag): """ -class StrEnum(builtins.str, ReprEnum): +class StrEnum(_str, ReprEnum): """Enum where members are also (and must be) strings. `StrEnum` is the same as diff --git a/tests/test_builtins.py b/tests/test_builtins.py index 2044c91..0070367 100644 --- a/tests/test_builtins.py +++ b/tests/test_builtins.py @@ -2,11 +2,11 @@ import sys +from py_back.builtins import dict, str + def test_backported_str(): """Here are tested all methods backported for the 'str' class.""" - from py_back.builtins import str - old_str = "Hello world!" if sys.version_info < (3, 9): @@ -22,8 +22,6 @@ def test_backported_str(): def test_backported_dict(): """Here are tested all methods backported for the 'str' class.""" - from py_back.builtins import dict - my_dict = dict({"a": 1, "b": 2}) result = my_dict | {"c": 3} assert my_dict == {"a": 1, "b": 2} diff --git a/tests/test_enums.py b/tests/test_enums.py index 9e537a1..4d817c2 100644 --- a/tests/test_enums.py +++ b/tests/test_enums.py @@ -1,5 +1,7 @@ """Test the 'enums' module.""" +from py_back import enum + def test_repr_enum(): """Tests for enum representations. @@ -8,19 +10,18 @@ def test_repr_enum(): due to the creation of ReprEnum. The used classes are obtained from the documentation examples. """ - from py_back.enum import IntEnum, IntFlag, auto - class Color(IntFlag): - RED = auto() - GREEN = auto() + class Color(enum.IntFlag): + RED = enum.auto() + GREEN = enum.auto() assert Color.RED & 2 is Color(0) assert repr(Color.RED | 2) in {"", ""} assert str(Color.RED) == "1" - class Number(IntEnum): + class Number(enum.IntEnum): ONE = 1 - TWO = auto() + TWO = enum.auto() THREE = 3 assert Number.TWO == 2 @@ -31,7 +32,6 @@ class Number(IntEnum): def test_str_enum(): """Testing py_back.enum.StrEnum.""" - from py_back import enum class Animal(enum.StrEnum): DOG = enum.auto() From 87e2bbfd9323d1c90699e074c8be0b2ba120766e Mon Sep 17 00:00:00 2001 From: Jtachan <76098522+Jtachan@users.noreply.github.com> Date: Thu, 12 Feb 2026 11:24:50 +0100 Subject: [PATCH 17/17] Bump version --- src/py_back/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/py_back/__init__.py b/src/py_back/__init__.py index afa5357..d52dc33 100644 --- a/src/py_back/__init__.py +++ b/src/py_back/__init__.py @@ -11,4 +11,4 @@ SOFTWARE. """ -__version__ = "0.3.0-1" +__version__ = "0.3.1"