diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 973a79a..54a97bc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.8', '3.9', '3.10', '3.11'] + python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] steps: - uses: actions/checkout@master @@ -26,6 +26,8 @@ jobs: run: | python3 -m pip install --upgrade pip pip3 install --upgrade coveralls + # Python >=3.12 does not ship setuptools with pip anymore. + pip3 install setuptools pip3 install -e .[dev] - name: Run checks diff --git a/icontract/_recompute.py b/icontract/_recompute.py index ebe0793..6b8a090 100644 --- a/icontract/_recompute.py +++ b/icontract/_recompute.py @@ -232,7 +232,7 @@ def _translate_all_expression_to_a_module( ) # type: Union[ast.FunctionDef, ast.AsyncFunctionDef] module_node = ast.Module(body=[func_def_node]) - else: + elif sys.version_info < (3, 12): func_def_node = ast.FunctionDef( name=generated_function_name, args=ast.arguments( @@ -248,6 +248,24 @@ def _translate_all_expression_to_a_module( body=block, ) + module_node = ast.Module(body=[func_def_node], type_ignores=[]) + else: + func_def_node = ast.FunctionDef( + name=generated_function_name, + args=ast.arguments( + args=args, + posonlyargs=[], + kwonlyargs=[], + kw_defaults=[], + defaults=[], + vararg=None, + kwarg=None, + ), + body=block, + decorator_list=[], + type_params=[], + ) + module_node = ast.Module(body=[func_def_node], type_ignores=[]) else: if sys.version_info < (3, 8): @@ -266,6 +284,23 @@ def _translate_all_expression_to_a_module( ) module_node = ast.Module(body=[async_func_def_node]) + elif sys.version_info < (3, 12): + async_func_def_node = ast.AsyncFunctionDef( + name=generated_function_name, + args=ast.arguments( + args=args, + posonlyargs=[], + kwonlyargs=[], + kw_defaults=[], + defaults=[], + vararg=None, + kwarg=None, + ), + decorator_list=[], + body=block, + ) + + module_node = ast.Module(body=[async_func_def_node], type_ignores=[]) else: async_func_def_node = ast.AsyncFunctionDef( name=generated_function_name, @@ -280,6 +315,7 @@ def _translate_all_expression_to_a_module( ), decorator_list=[], body=block, + type_params=[], ) module_node = ast.Module(body=[async_func_def_node], type_ignores=[]) @@ -925,6 +961,21 @@ def _execute_comprehension( ) module_node = ast.Module(body=[func_def_node]) + elif sys.version_info < (3, 12): + func_def_node = ast.FunctionDef( + name="generator_expr", + args=ast.arguments( + args=args, + posonlyargs=[], + kwonlyargs=[], + kw_defaults=[], + defaults=[], + ), + decorator_list=[], + body=[ast.Return(node)], + ) + + module_node = ast.Module(body=[func_def_node], type_ignores=[]) else: func_def_node = ast.FunctionDef( name="generator_expr", @@ -937,6 +988,7 @@ def _execute_comprehension( ), decorator_list=[], body=[ast.Return(node)], + type_params=[], ) module_node = ast.Module(body=[func_def_node], type_ignores=[]) diff --git a/precommit.py b/precommit.py index 1c7bf5c..6952cda 100755 --- a/precommit.py +++ b/precommit.py @@ -99,8 +99,18 @@ def main() -> int: pylint_targets.append("tests_3_8") pylint_targets.append("tests_with_others") + import pylint + import packaging.version + + if packaging.version.parse(pylint.__version__) < packaging.version.parse( + "3.3.7" + ): + rcfile = "pylint.lt_3.3.7.rc" + else: + rcfile = "pylint.ge_3.3.7.rc" + subprocess.check_call( - ["pylint", "--rcfile=pylint.rc"] + pylint_targets, cwd=str(repo_root) + ["pylint", f"--rcfile={rcfile}"] + pylint_targets, cwd=str(repo_root) ) print("Pydocstyle'ing...") diff --git a/pylint.ge_3.3.7.rc b/pylint.ge_3.3.7.rc new file mode 100644 index 0000000..be36cc8 --- /dev/null +++ b/pylint.ge_3.3.7.rc @@ -0,0 +1,9 @@ +[TYPECHECK] +ignored-modules = numpy +ignored-classes = numpy,PurePath + +[FORMAT] +max-line-length=120 + +[MESSAGES CONTROL] +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 diff --git a/pylint.rc b/pylint.lt_3.3.7.rc similarity index 86% rename from pylint.rc rename to pylint.lt_3.3.7.rc index 6e41e58..802f7ec 100644 --- a/pylint.rc +++ b/pylint.lt_3.3.7.rc @@ -1,11 +1,9 @@ [TYPECHECK] ignored-modules = numpy ignored-classes = numpy,PurePath -generated-members=bottle\.request\.forms\.decode,bottle\.request\.query\.decode [FORMAT] max-line-length=120 [MESSAGES CONTROL] 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 - diff --git a/setup.py b/setup.py index 3e76a71..f1847a2 100644 --- a/setup.py +++ b/setup.py @@ -36,13 +36,13 @@ # fmt: off 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', - 'License :: OSI Approved :: MIT License', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', # fmt: on ], license="License :: OSI Approved :: MIT License", @@ -55,7 +55,8 @@ ], extras_require={ "dev": [ - 'pylint==3.2.7;python_version>="3.8"', + 'pylint==2.17.5;python_version>="3.7" and python_version<"3.12"', + 'pylint==4.0.2;python_version>="3.12"', "tox>=3.0.0", "pydocstyle>=6.3.0,<7", "coverage>=6.5.0,<7", @@ -67,8 +68,9 @@ "typeguard>=2,<5", "astor==0.8.1", "numpy>=1,<2", - 'mypy==1.14.1;python_version>="3.8"', - 'black==24.8.0;python_version>="3.8"', + 'mypy==1.5.1;python_version>="3.8" and python_version<"3.12"', + 'mypy==1.18.2;python_version>="3.12"', + 'black==23.9.1;python_version>="3.8"', 'deal>=4,<5;python_version>="3.8"', 'asyncstdlib==3.9.1;python_version>="3.8"', ] diff --git a/tests/test_inheritance_postcondition.py b/tests/test_inheritance_postcondition.py index 24f924a..0f550ca 100644 --- a/tests/test_inheritance_postcondition.py +++ b/tests/test_inheritance_postcondition.py @@ -535,11 +535,16 @@ class B(A): "Can't instantiate abstract class B with abstract methods func", str(type_err), ) - else: + elif sys.version_info < (3, 12): self.assertEqual( "Can't instantiate abstract class B with abstract method func", str(type_err), ) + else: + self.assertEqual( + "Can't instantiate abstract class B without an implementation for abstract method 'func'", + str(type_err), + ) if __name__ == "__main__": diff --git a/tests/test_inheritance_precondition.py b/tests/test_inheritance_precondition.py index a358fba..84d700d 100644 --- a/tests/test_inheritance_precondition.py +++ b/tests/test_inheritance_precondition.py @@ -587,11 +587,16 @@ class B(A): "Can't instantiate abstract class B with abstract methods func", str(type_err), ) - else: + elif sys.version_info < (3, 12): self.assertEqual( "Can't instantiate abstract class B with abstract method func", str(type_err), ) + else: + self.assertEqual( + "Can't instantiate abstract class B without an implementation for abstract method 'func'", + str(type_err), + ) def test_cant_weaken_base_function_without_preconditions(self) -> None: class A(icontract.DBC): diff --git a/tests_with_others/test_deal.py b/tests_with_others/test_deal.py index 5a151ca..226e926 100644 --- a/tests_with_others/test_deal.py +++ b/tests_with_others/test_deal.py @@ -1,7 +1,6 @@ # pylint: disable=missing-docstring # pylint: disable=broad-except # pylint: disable=invalid-name - import unittest from typing import Optional @@ -10,7 +9,11 @@ class TestDeal(unittest.TestCase): def test_recursion_handled_in_preconditions(self) -> None: - @deal.pre(lambda _: another_func()) # type: ignore + # NOTE (mristin): + # Mypy 1.5.1 throws here a misc error -- that the untyped decorator forces the function to be untyped as well. + # On the other hand, mypy 1.18.2 can figure this situation, and throws unused-ignore. We ignore both cases + # to be able to support both versions of mypy. + @deal.pre(lambda _: another_func()) # type: ignore[unused-ignore,misc] @deal.pre(lambda _: yet_another_func()) def some_func() -> bool: return True