Skip to content

Commit ad22b28

Browse files
authored
Add support for Python 3.12 (#308)
There were a couple of minor fixes we had to do in order to support Python 3.12. In addition, we had to use more up-to-date versions of mypy and pylint for Python versions >= 3.12.
1 parent f587578 commit ad22b28

9 files changed

Lines changed: 99 additions & 13 deletions

File tree

.github/workflows/ci.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jobs:
1212
runs-on: ubuntu-latest
1313
strategy:
1414
matrix:
15-
python-version: ['3.8', '3.9', '3.10', '3.11']
15+
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12']
1616

1717
steps:
1818
- uses: actions/checkout@master
@@ -26,6 +26,8 @@ jobs:
2626
run: |
2727
python3 -m pip install --upgrade pip
2828
pip3 install --upgrade coveralls
29+
# Python >=3.12 does not ship setuptools with pip anymore.
30+
pip3 install setuptools
2931
pip3 install -e .[dev]
3032
3133
- name: Run checks

icontract/_recompute.py

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ def _translate_all_expression_to_a_module(
232232
) # type: Union[ast.FunctionDef, ast.AsyncFunctionDef]
233233

234234
module_node = ast.Module(body=[func_def_node])
235-
else:
235+
elif sys.version_info < (3, 12):
236236
func_def_node = ast.FunctionDef(
237237
name=generated_function_name,
238238
args=ast.arguments(
@@ -248,6 +248,24 @@ def _translate_all_expression_to_a_module(
248248
body=block,
249249
)
250250

251+
module_node = ast.Module(body=[func_def_node], type_ignores=[])
252+
else:
253+
func_def_node = ast.FunctionDef(
254+
name=generated_function_name,
255+
args=ast.arguments(
256+
args=args,
257+
posonlyargs=[],
258+
kwonlyargs=[],
259+
kw_defaults=[],
260+
defaults=[],
261+
vararg=None,
262+
kwarg=None,
263+
),
264+
body=block,
265+
decorator_list=[],
266+
type_params=[],
267+
)
268+
251269
module_node = ast.Module(body=[func_def_node], type_ignores=[])
252270
else:
253271
if sys.version_info < (3, 8):
@@ -266,6 +284,23 @@ def _translate_all_expression_to_a_module(
266284
)
267285

268286
module_node = ast.Module(body=[async_func_def_node])
287+
elif sys.version_info < (3, 12):
288+
async_func_def_node = ast.AsyncFunctionDef(
289+
name=generated_function_name,
290+
args=ast.arguments(
291+
args=args,
292+
posonlyargs=[],
293+
kwonlyargs=[],
294+
kw_defaults=[],
295+
defaults=[],
296+
vararg=None,
297+
kwarg=None,
298+
),
299+
decorator_list=[],
300+
body=block,
301+
)
302+
303+
module_node = ast.Module(body=[async_func_def_node], type_ignores=[])
269304
else:
270305
async_func_def_node = ast.AsyncFunctionDef(
271306
name=generated_function_name,
@@ -280,6 +315,7 @@ def _translate_all_expression_to_a_module(
280315
),
281316
decorator_list=[],
282317
body=block,
318+
type_params=[],
283319
)
284320

285321
module_node = ast.Module(body=[async_func_def_node], type_ignores=[])
@@ -925,6 +961,21 @@ def _execute_comprehension(
925961
)
926962

927963
module_node = ast.Module(body=[func_def_node])
964+
elif sys.version_info < (3, 12):
965+
func_def_node = ast.FunctionDef(
966+
name="generator_expr",
967+
args=ast.arguments(
968+
args=args,
969+
posonlyargs=[],
970+
kwonlyargs=[],
971+
kw_defaults=[],
972+
defaults=[],
973+
),
974+
decorator_list=[],
975+
body=[ast.Return(node)],
976+
)
977+
978+
module_node = ast.Module(body=[func_def_node], type_ignores=[])
928979
else:
929980
func_def_node = ast.FunctionDef(
930981
name="generator_expr",
@@ -937,6 +988,7 @@ def _execute_comprehension(
937988
),
938989
decorator_list=[],
939990
body=[ast.Return(node)],
991+
type_params=[],
940992
)
941993

942994
module_node = ast.Module(body=[func_def_node], type_ignores=[])

precommit.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,18 @@ def main() -> int:
9999
pylint_targets.append("tests_3_8")
100100
pylint_targets.append("tests_with_others")
101101

102+
import pylint
103+
import packaging.version
104+
105+
if packaging.version.parse(pylint.__version__) < packaging.version.parse(
106+
"3.3.7"
107+
):
108+
rcfile = "pylint.lt_3.3.7.rc"
109+
else:
110+
rcfile = "pylint.ge_3.3.7.rc"
111+
102112
subprocess.check_call(
103-
["pylint", "--rcfile=pylint.rc"] + pylint_targets, cwd=str(repo_root)
113+
["pylint", f"--rcfile={rcfile}"] + pylint_targets, cwd=str(repo_root)
104114
)
105115

106116
print("Pydocstyle'ing...")

pylint.ge_3.3.7.rc

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[TYPECHECK]
2+
ignored-modules = numpy
3+
ignored-classes = numpy,PurePath
4+
5+
[FORMAT]
6+
max-line-length=120
7+
8+
[MESSAGES CONTROL]
9+
disable=too-few-public-methods,len-as-condition,duplicate-code,no-else-raise,too-many-locals,too-many-branches,too-many-lines,too-many-arguments,too-many-statements,too-many-nested-blocks,too-many-function-args,too-many-instance-attributes,too-many-public-methods,protected-access,consider-using-in,no-member,consider-using-f-string,use-dict-literal,redundant-keyword-arg,no-else-return,too-many-positional-arguments
Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
[TYPECHECK]
22
ignored-modules = numpy
33
ignored-classes = numpy,PurePath
4-
generated-members=bottle\.request\.forms\.decode,bottle\.request\.query\.decode
54

65
[FORMAT]
76
max-line-length=120
87

98
[MESSAGES CONTROL]
109
disable=too-few-public-methods,len-as-condition,duplicate-code,no-else-raise,too-many-locals,too-many-branches,too-many-lines,too-many-arguments,too-many-statements,too-many-nested-blocks,too-many-function-args,too-many-instance-attributes,too-many-public-methods,protected-access,consider-using-in,no-member,consider-using-f-string,use-dict-literal,redundant-keyword-arg,no-else-return
11-

setup.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,13 @@
3636
# fmt: off
3737
'Development Status :: 5 - Production/Stable',
3838
'Intended Audience :: Developers',
39-
'License :: OSI Approved :: MIT License',
4039
'Programming Language :: Python :: 3.6',
4140
'Programming Language :: Python :: 3.7',
4241
'Programming Language :: Python :: 3.8',
4342
'Programming Language :: Python :: 3.9',
4443
'Programming Language :: Python :: 3.10',
4544
'Programming Language :: Python :: 3.11',
45+
'Programming Language :: Python :: 3.12',
4646
# fmt: on
4747
],
4848
license="License :: OSI Approved :: MIT License",
@@ -55,7 +55,8 @@
5555
],
5656
extras_require={
5757
"dev": [
58-
'pylint==3.2.7;python_version>="3.8"',
58+
'pylint==2.17.5;python_version>="3.7" and python_version<"3.12"',
59+
'pylint==4.0.2;python_version>="3.12"',
5960
"tox>=3.0.0",
6061
"pydocstyle>=6.3.0,<7",
6162
"coverage>=6.5.0,<7",
@@ -67,8 +68,9 @@
6768
"typeguard>=2,<5",
6869
"astor==0.8.1",
6970
"numpy>=1,<2",
70-
'mypy==1.14.1;python_version>="3.8"',
71-
'black==24.8.0;python_version>="3.8"',
71+
'mypy==1.5.1;python_version>="3.8" and python_version<"3.12"',
72+
'mypy==1.18.2;python_version>="3.12"',
73+
'black==23.9.1;python_version>="3.8"',
7274
'deal>=4,<5;python_version>="3.8"',
7375
'asyncstdlib==3.9.1;python_version>="3.8"',
7476
]

tests/test_inheritance_postcondition.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -535,11 +535,16 @@ class B(A):
535535
"Can't instantiate abstract class B with abstract methods func",
536536
str(type_err),
537537
)
538-
else:
538+
elif sys.version_info < (3, 12):
539539
self.assertEqual(
540540
"Can't instantiate abstract class B with abstract method func",
541541
str(type_err),
542542
)
543+
else:
544+
self.assertEqual(
545+
"Can't instantiate abstract class B without an implementation for abstract method 'func'",
546+
str(type_err),
547+
)
543548

544549

545550
if __name__ == "__main__":

tests/test_inheritance_precondition.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -587,11 +587,16 @@ class B(A):
587587
"Can't instantiate abstract class B with abstract methods func",
588588
str(type_err),
589589
)
590-
else:
590+
elif sys.version_info < (3, 12):
591591
self.assertEqual(
592592
"Can't instantiate abstract class B with abstract method func",
593593
str(type_err),
594594
)
595+
else:
596+
self.assertEqual(
597+
"Can't instantiate abstract class B without an implementation for abstract method 'func'",
598+
str(type_err),
599+
)
595600

596601
def test_cant_weaken_base_function_without_preconditions(self) -> None:
597602
class A(icontract.DBC):

tests_with_others/test_deal.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
# pylint: disable=missing-docstring
22
# pylint: disable=broad-except
33
# pylint: disable=invalid-name
4-
54
import unittest
65
from typing import Optional
76

@@ -10,7 +9,11 @@
109

1110
class TestDeal(unittest.TestCase):
1211
def test_recursion_handled_in_preconditions(self) -> None:
13-
@deal.pre(lambda _: another_func()) # type: ignore
12+
# NOTE (mristin):
13+
# Mypy 1.5.1 throws here a misc error -- that the untyped decorator forces the function to be untyped as well.
14+
# On the other hand, mypy 1.18.2 can figure this situation, and throws unused-ignore. We ignore both cases
15+
# to be able to support both versions of mypy.
16+
@deal.pre(lambda _: another_func()) # type: ignore[unused-ignore,misc]
1417
@deal.pre(lambda _: yet_another_func())
1518
def some_func() -> bool:
1619
return True

0 commit comments

Comments
 (0)