Skip to content

Commit 3ed9257

Browse files
chore(python): Officially support Python 3.9 (#108)
* Test other Python versions * why cancelled? * chore(python): Officially support Python >= 3.7 * 🩹workflow * 🩹 * 🩹 * Try generative env list * next try * 🩹 * 🔥Unnecessary use of `TypeGuard` * 🩹 * 🩹For Python 3.9 * Drop py37 and py38 * Update pyproject.toml * 🩹 * isort . * 🩹mypy * 🔥 --------- Co-authored-by: konstantin <[email protected]>
1 parent 2db8392 commit 3ed9257

File tree

4 files changed

+22
-20
lines changed

4 files changed

+22
-20
lines changed

.github/workflows/unittests.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ jobs:
66
runs-on: ${{ matrix.os }}
77
strategy:
88
matrix:
9-
python-version: ["3.10", "3.11", "3.12"]
9+
python-version: ["3.9", "3.10", "3.11", "3.12"]
1010
os: [ubuntu-latest]
1111
steps:
1212
- uses: actions/checkout@v4
@@ -20,7 +20,7 @@ jobs:
2020
pip install tox
2121
- name: Run the Unit Tests via Tox
2222
run: |
23-
TOXENV=py${{ matrix.python-version }}-tests
23+
TOXENV=tests-py${{ matrix.python-version }}
2424
TOXENV=${TOXENV//.}
2525
echo "Running the unit tests via Tox with the environment $TOXENV"
2626
tox -e $TOXENV

pyproject.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
name = "python-generics"
33
description = "A package to determine the values of generic classes through instances or subclasses "
44
license = { text = "MIT" }
5-
requires-python = ">=3.10"
5+
requires-python = ">=3.9"
66
authors = [{ name = "Hochfrequenz Unternehmensberatung GmbH", email = "[email protected]" }]
77
keywords = ["python", "generics"]
88
classifiers = [
@@ -13,6 +13,7 @@ classifiers = [
1313
"Operating System :: OS Independent",
1414
"Programming Language :: Python",
1515
"Programming Language :: Python :: 3 :: Only",
16+
"Programming Language :: Python :: 3.9",
1617
"Programming Language :: Python :: 3.10",
1718
"Programming Language :: Python :: 3.11",
1819
"Programming Language :: Python :: 3.12",
@@ -26,6 +27,7 @@ Homepage = "https://github.com/Hochfrequenz/python-generics"
2627

2728
[tool.black]
2829
line-length = 120
30+
target_version = ["py39", "py310", "py311", "py312"]
2931

3032
[tool.isort]
3133
line_length = 120

src/generics/__init__.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
from typing import Any, Generic
1010
from typing import GenericAlias as TypesGenericAlias # type: ignore[attr-defined]
11-
from typing import Optional, Protocol, TypeGuard, TypeVar
11+
from typing import Optional, Protocol, TypeVar, Union
1212
from typing import _GenericAlias as TypingGenericAlias # type: ignore[attr-defined]
1313
from typing import get_args, get_origin
1414

@@ -22,7 +22,7 @@ class GenericType(Protocol):
2222
__orig_bases__: tuple[type, ...]
2323

2424

25-
def get_type_vars(type_: type | GenericType) -> tuple[TypeVar, ...]:
25+
def get_type_vars(type_: Union[type, GenericType]) -> tuple[TypeVar, ...]:
2626
"""
2727
For a given generic type, return a tuple of its type variables. The type variables are collected through the
2828
supertypes arguments `Generic` if present.
@@ -57,14 +57,14 @@ def get_type_vars(type_: type | GenericType) -> tuple[TypeVar, ...]:
5757
if not _generic_metaclass_executed_on_type(type_):
5858
return ()
5959

60-
for base in type_.__orig_bases__:
60+
for base in type_.__orig_bases__: # type: ignore[union-attr]
6161
if get_origin(base) is Generic:
6262
return get_args(base)
6363

6464
# if we get here, the type has not `Generic` directly as supertype. Therefore, collect the type variables from
6565
# all the supertypes arguments.
6666
type_vars: list[TypeVar] = []
67-
for base in type_.__orig_bases__:
67+
for base in type_.__orig_bases__: # type: ignore[union-attr]
6868
if isinstance(base, (TypingGenericAlias, TypesGenericAlias)):
6969
for arg in get_args(base):
7070
if isinstance(arg, TypeVar) and arg not in type_vars:
@@ -73,7 +73,7 @@ def get_type_vars(type_: type | GenericType) -> tuple[TypeVar, ...]:
7373
return tuple(type_vars)
7474

7575

76-
def _generic_metaclass_executed_on_type(type_: type | GenericType) -> TypeGuard[GenericType]:
76+
def _generic_metaclass_executed_on_type(type_: Union[type, GenericType]) -> bool:
7777
"""
7878
This function determines if the type was processed by a `_GenericAlias` with all its `__mro_entries__` magic.
7979
I.e. if the type has `Generic` as supertype or something like `A[T]` in its supertypes.
@@ -111,7 +111,7 @@ def _find_super_type_trace(type_: type, search_for_type: type) -> Optional[list[
111111

112112

113113
def _process_inputs_of_get_filled_type(
114-
type_or_instance: Any, type_var_defining_super_type: type, type_var_or_position: TypeVar | int
114+
type_or_instance: Any, type_var_defining_super_type: type, type_var_or_position: Union[TypeVar, int]
115115
) -> tuple[type, type, int]:
116116
"""
117117
This function processes the inputs of `get_filled_type`. It returns a tuple of the filled type, the super type and
@@ -142,7 +142,7 @@ def _process_inputs_of_get_filled_type(
142142

143143
# pylint: disable=too-many-branches, too-many-locals
144144
def get_filled_type(
145-
type_or_instance: Any, type_var_defining_super_type: type, type_var_or_position: TypeVar | int
145+
type_or_instance: Any, type_var_defining_super_type: type, type_var_or_position: Union[TypeVar, int]
146146
) -> Any:
147147
"""
148148
Determines the type of the `type_var_or_position` defined by the type `type_var_defining_super_type`.
@@ -187,7 +187,7 @@ def get_filled_type(
187187
continue
188188
if not _generic_metaclass_executed_on_type(type_):
189189
raise TypeError(f"Could not determine the type in {filled_type!r}: {type_!r} is not generic")
190-
for orig_base in type_.__orig_bases__:
190+
for orig_base in type_.__orig_bases__: # type: ignore[attr-defined]
191191
if get_origin(orig_base) == type_trace[-reversed_index + 1]:
192192
orig_base_args = get_args(orig_base)
193193
if len(orig_base_args) < type_var_index:

tox.ini

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
[tox]
22
envlist =
3-
py311-tests
4-
py312-tests
3+
tests-py{39,310,311,312}
54
linting
65
coverage
76
type_check
@@ -11,7 +10,7 @@ skipsdist = True
1110
[testenv]
1211
commands = python -m pip install --upgrade pip
1312

14-
[testenv:py312-tests]
13+
[testenv:tests-py312]
1514
# the tests environment is called by the Github action that runs the unit tests
1615
deps =
1716
-r requirements.txt
@@ -20,18 +19,19 @@ setenv = PYTHONPATH = {toxinidir}/src
2019
commands =
2120
python -m pytest --basetemp={envtmpdir} {posargs}
2221

23-
[testenv:py311-tests]
22+
[testenv:tests-py{39,310,311}]
2423
# the tests environment is called by the Github action that runs the unit tests
2524
deps =
26-
{[testenv:py312-tests]deps}
25+
-r requirements.txt
26+
-r dev_requirements/requirements-tests.txt
2727
setenv = PYTHONPATH = {toxinidir}/src
2828
commands =
2929
python -m pytest --basetemp={envtmpdir} --ignore=unittests/test_py_312.py {posargs}
3030

3131
[testenv:linting]
3232
# the linting environment is called by the Github Action that runs the linter
3333
deps =
34-
{[testenv:py312-tests]deps}
34+
{[testenv:tests-py312]deps}
3535
-r dev_requirements/requirements-linting.txt
3636
# add your fixtures like e.g. pytest_datafiles here
3737
setenv = PYTHONPATH = {toxinidir}/src
@@ -44,7 +44,7 @@ commands =
4444
# the type_check environment checks the type hints using mypy
4545
setenv = PYTHONPATH = {toxinidir}/src
4646
deps =
47-
{[testenv:py312-tests]deps}
47+
{[testenv:tests-py312]deps}
4848
-r dev_requirements/requirements-type_check.txt
4949
commands =
5050
mypy --show-error-codes src/generics
@@ -55,7 +55,7 @@ commands =
5555
# the coverage environment is called by the Github Action that runs the coverage measurement
5656
changedir = unittests
5757
deps =
58-
{[testenv:py312-tests]deps}
58+
{[testenv:tests-py312]deps}
5959
-r dev_requirements/requirements-coverage.txt
6060
setenv = PYTHONPATH = {toxinidir}/src
6161
commands =
@@ -67,7 +67,7 @@ commands =
6767
[testenv:dev]
6868
# the dev environment contains everything you need to start developing on your local machine.
6969
deps =
70-
{[testenv:py312-tests]deps}
70+
{[testenv:tests-py312]deps}
7171
{[testenv:linting]deps}
7272
{[testenv:type_check]deps}
7373
{[testenv:coverage]deps}

0 commit comments

Comments
 (0)