Skip to content

Commit

Permalink
Added support for Python 3.9 and 3.10 (#236)
Browse files Browse the repository at this point in the history
This patch introduces Python versions 3.9 and 3.10 in continuous
integration.

Fixes #235.
  • Loading branch information
mristin authored Nov 25, 2021
1 parent 65f16f5 commit 3410fb8
Show file tree
Hide file tree
Showing 9 changed files with 59 additions and 45 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.6, 3.7, 3.8]
python-version: ['3.6', '3.7', '3.8', '3.9', '3.10']

steps:
- uses: actions/checkout@master
Expand Down
3 changes: 1 addition & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
venv3/
venv/
venv*/
.mypy_cache/
.idea/
*.pyc
Expand Down
12 changes: 6 additions & 6 deletions icontract/_metaclass.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Define the metaclass necessary to inherit the contracts from the base classes."""
import abc
import inspect
import platform
import sys
import weakref
from typing import List, MutableMapping, Any, Callable, Optional, cast, Set, Type, \
TypeVar # pylint: disable=unused-import
Expand Down Expand Up @@ -316,12 +316,12 @@ class DBCMeta(abc.ABCMeta):
# instead of ``mcs``.
# pylint: disable=bad-mcs-classmethod-argument

if platform.python_version_tuple() < ('3', ):
raise NotImplementedError("Python versions below not supported, got: {}".format(platform.python_version()))
if sys.version_info < (3, ):
raise NotImplementedError("Python versions below not supported, got: {}".format(sys.version_info))

if platform.python_version_tuple() <= ('3', '5'):
if sys.version_info < (3, 6):
# pylint: disable=arguments-differ
def __new__(mlcs, name, bases, namespace): # type: ignore
def __new__(mlcs, name, bases, namespace):
"""Create a class with inherited preconditions, postconditions and invariants."""
_dbc_decorate_namespace(bases, namespace)

Expand All @@ -335,7 +335,7 @@ def __new__(mlcs, name, bases, namespace): # type: ignore
# This usually works since icontract-hypothesis does not use DBCMeta,
# but blows up since icontract creates DBC with DBCMeta meta-class at the import time.
if cls.__module__ != __name__:
_register_for_hypothesis(cls) # type: ignore
_register_for_hypothesis(cls)

return cls
else:
Expand Down
58 changes: 30 additions & 28 deletions icontract/_recompute.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import copy
import functools
import inspect
import platform
import sys
import uuid
from typing import (Any, Mapping, Dict, List, Optional, Union, Tuple, Set, Callable, cast, Iterable, TypeVar) # pylint: disable=unused-import
Expand Down Expand Up @@ -163,11 +162,11 @@ def _translate_all_expression_to_a_module(generator_exp: ast.GeneratorExp, gener

args = [ast.arg(arg=name, annotation=None) for name in sorted(name_to_value.keys())]

if platform.python_version_tuple() < ('3', '5'):
raise NotImplementedError("Python versions below 3.5 not supported, got: {}".format(platform.python_version()))
if sys.version_info < (3, 5):
raise NotImplementedError("Python versions below 3.5 not supported, got: {}".format(sys.version_info))

if not is_async:
if platform.python_version_tuple() < ('3', '8'):
if sys.version_info < (3, 8):
func_def_node = ast.FunctionDef(
name=generated_function_name,
args=ast.arguments(args=args, kwonlyargs=[], kw_defaults=[], defaults=[], vararg=None, kwarg=None),
Expand All @@ -185,23 +184,23 @@ def _translate_all_expression_to_a_module(generator_exp: ast.GeneratorExp, gener

module_node = ast.Module(body=[func_def_node], type_ignores=[])
else:
if platform.python_version_tuple() < ('3', '8'):
func_def_node = ast.AsyncFunctionDef(
if sys.version_info < (3, 8):
async_func_def_node = ast.AsyncFunctionDef(
name=generated_function_name,
args=ast.arguments(args=args, kwonlyargs=[], kw_defaults=[], defaults=[], vararg=None, kwarg=None),
decorator_list=[],
body=block)

module_node = ast.Module(body=[func_def_node])
module_node = ast.Module(body=[async_func_def_node])
else:
func_def_node = ast.AsyncFunctionDef(
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=[func_def_node], type_ignores=[])
module_node = ast.Module(body=[async_func_def_node], type_ignores=[])

ast.fix_missing_locations(module_node)

Expand Down Expand Up @@ -664,16 +663,18 @@ def visit_NamedExpr(self, node: ast.NamedExpr) -> Any:

return value

def visit_Index(self, node: ast.Index) -> Any:
"""Visit the node's ``value``."""
result = self.visit(node=node.value)
if sys.version_info < (3, 9):

# Please see "NOTE ABOUT PLACEHOLDERS AND RE-COMPUTATION"
if result is PLACEHOLDER:
return PLACEHOLDER
def visit_Index(self, node: ast.Index) -> Any:
"""Visit the node's ``value``."""
result = self.visit(node=node.value)

self.recomputed_values[node] = result
return result
# Please see "NOTE ABOUT PLACEHOLDERS AND RE-COMPUTATION"
if result is PLACEHOLDER:
return PLACEHOLDER

self.recomputed_values[node] = result
return result

def visit_Slice(self, node: ast.Slice) -> Union[slice, Placeholder]:
"""Visit ``lower``, ``upper`` and ``step`` and recompute the node as a ``slice``."""
Expand All @@ -698,15 +699,17 @@ def visit_Slice(self, node: ast.Slice) -> Union[slice, Placeholder]:
self.recomputed_values[node] = result
return result

def visit_ExtSlice(self, node: ast.ExtSlice) -> Union[Tuple[Any, ...], Placeholder]:
"""Visit each dimension of the advanced slicing and assemble the dimensions in a tuple."""
result = tuple(self.visit(node=dim) for dim in node.dims)
if sys.version_info < (3, 9):

if any(value is PLACEHOLDER for value in result):
return PLACEHOLDER
def visit_ExtSlice(self, node: ast.ExtSlice) -> Union[Tuple[Any, ...], Placeholder]:
"""Visit each dimension of the advanced slicing and assemble the dimensions in a tuple."""
result = tuple(self.visit(node=dim) for dim in node.dims)

self.recomputed_values[node] = result
return result
if any(value is PLACEHOLDER for value in result):
return PLACEHOLDER

self.recomputed_values[node] = result
return result

def visit_Subscript(self, node: ast.Subscript) -> Any:
"""Visit the ``slice`` and a ``value`` and get the element."""
Expand Down Expand Up @@ -780,11 +783,10 @@ def _execute_comprehension(self, node: Union[ast.ListComp, ast.SetComp, ast.Gene

args = [ast.arg(arg=name, annotation=None) for name in sorted(self._name_to_value.keys())]

if platform.python_version_tuple() < ('3', ):
raise NotImplementedError("Python versions below 3 not supported, got: {}".format(
platform.python_version()))
if sys.version_info < (3, ):
raise NotImplementedError("Python versions below 3 not supported, got: {}".format(sys.version_info))

if platform.python_version_tuple() < ('3', '8'):
if sys.version_info < (3, 8):
func_def_node = ast.FunctionDef(
name="generator_expr",
args=ast.arguments(args=args, kwonlyargs=[], kw_defaults=[], defaults=[]),
Expand Down
6 changes: 4 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@
'License :: OSI Approved :: MIT License',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8'
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10'
# yapf: enable
],
license='License :: OSI Approved :: MIT License',
Expand All @@ -50,7 +52,7 @@
install_requires=install_requires,
extras_require={
'dev': [
'mypy==0.910', 'pylint==2.9.6', 'yapf==0.20.2', 'tox>=3.0.0', 'pydocstyle>=2.1.1,<3', 'coverage>=4.5.1,<5',
'mypy==0.910', 'pylint==2.9.6', 'yapf==0.20.2', 'tox>=3.0.0', 'pydocstyle>=6.1.1,<7', 'coverage>=4.5.1,<5',
'docutils>=0.14,<1', 'pygments>=2.2.0,<3', 'dpcontracts==0.6.0', 'tabulate>=0.8.7,<1',
'py-cpuinfo>=5.0.0,<6', 'typeguard>=2,<3', 'astor==0.8.1', 'numpy>=1,<2'
] + (['deal==4.1.0'] if sys.version_info >= (3, 8) else []) + (['asyncstdlib==3.9.1']
Expand Down
3 changes: 2 additions & 1 deletion tests/test_checkers.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ def some_func(x: int, y: int) -> None:
type_error = error

assert type_error is not None
self.assertEqual("some_func() takes 2 positional arguments but 3 were given", str(type_error))
self.assertRegex(
str(type_error), r"^([a-zA-Z_0-9<>.]+\.)?some_func\(\) takes 2 positional arguments but 3 were given$")

def test_that_result_in_kwargs_raises_an_error(self) -> None:
@icontract.ensure(lambda result: result > 0)
Expand Down
6 changes: 5 additions & 1 deletion tests/test_inheritance_postcondition.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# pylint: disable=no-self-use

import abc
import sys
import textwrap
import unittest
from typing import Optional # pylint: disable=unused-import
Expand Down Expand Up @@ -478,7 +479,10 @@ class B(A):
type_err = err

self.assertIsNotNone(type_err)
self.assertEqual("Can't instantiate abstract class B with abstract methods func", str(type_err))
if sys.version_info < (3, 9):
self.assertEqual("Can't instantiate abstract class B with abstract methods func", str(type_err))
else:
self.assertEqual("Can't instantiate abstract class B with abstract method func", str(type_err))


if __name__ == '__main__':
Expand Down
6 changes: 5 additions & 1 deletion tests/test_inheritance_precondition.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# pylint: disable=no-self-use

import abc
import sys
import textwrap
import unittest
from typing import Optional, Sequence, cast # pylint: disable=unused-import
Expand Down Expand Up @@ -526,7 +527,10 @@ class B(A):
type_err = err

self.assertIsNotNone(type_err)
self.assertEqual("Can't instantiate abstract class B with abstract methods func", str(type_err))
if sys.version_info < (3, 9):
self.assertEqual("Can't instantiate abstract class B with abstract methods func", str(type_err))
else:
self.assertEqual("Can't instantiate abstract class B with abstract method func", str(type_err))

def test_cant_weaken_base_function_without_preconditions(self) -> None:
class A(icontract.DBC):
Expand Down
8 changes: 5 additions & 3 deletions tests/test_precondition.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import textwrap
import time
import unittest
from typing import Optional, Callable, Any # pylint: disable=unused-import
from typing import Optional # pylint: disable=unused-import

import icontract
from icontract._globals import CallableT
Expand Down Expand Up @@ -611,7 +611,8 @@ def some_func() -> None:
type_error = err

self.assertIsNotNone(type_error)
self.assertEqual('some_func() takes 0 positional arguments but 1 was given', str(type_error))
self.assertRegex(
str(type_error), r'^([a-zA-Z_0-9<>.]+\.)?some_func\(\) takes 0 positional arguments but 1 was given$')

def test_unexpected_keyword_argument(self) -> None:
@icontract.require(lambda: True)
Expand All @@ -626,7 +627,8 @@ def some_func() -> None:
type_error = err

self.assertIsNotNone(type_error)
self.assertEqual("some_func() got an unexpected keyword argument 'x'", str(type_error))
self.assertRegex(
str(type_error), r"^([a-zA-Z_0-9<>.]+\.)?some_func\(\) got an unexpected keyword argument 'x'$")


if __name__ == '__main__':
Expand Down

0 comments on commit 3410fb8

Please sign in to comment.