From 9b8adc3bf58694b139b75e19f51e31cff572d32d Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Wed, 3 Jun 2020 16:55:50 -0500 Subject: [PATCH 1/8] Add PEP561 type annotation stubs for a few objects --- pytools/__init__.pyi | 20 ++++++++++++++++++++ pytools/py.typed | 0 setup.py | 2 ++ 3 files changed, 22 insertions(+) create mode 100644 pytools/__init__.pyi create mode 100644 pytools/py.typed diff --git a/pytools/__init__.pyi b/pytools/__init__.pyi new file mode 100644 index 0000000..f2961ac --- /dev/null +++ b/pytools/__init__.pyi @@ -0,0 +1,20 @@ +from typing import Any, Iterable, Optional, Set, TypeVar +from numbers import Number + +T = TypeVar("T") + +def one(iterable: Iterable[T]) -> T: ... +def is_single_valued(iterable: Iterable[T], equality_pred: Callable[[T, T], bool] = ...) -> bool: ... +all_equal = is_single_valued +def all_roughly_equal(iterable: Iterable[Number], threshold: Number) -> bool: ... +def single_valued(iterable: Iterable[T], equality_pred: Callable[[T, T], bool] = ...) -> T: ... + +def memoize(*args: T, **kwargs: Any) -> T: ... +def memoize_method(method: T) -> T: ... + +class UniqueNameGenerator: + def __init__(self, existing_names: Optional[Set[str]] = ..., forced_prefix: str = ...) -> None: ... + def __call__(self, based_on: str = ...) -> str: ... + def is_name_conflicting(self, name: str) -> bool: ... + def add_name(self, name: str) -> bool: ... + def add_names(self, name: Iterable[str]) -> bool: ... diff --git a/pytools/py.typed b/pytools/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/setup.py b/setup.py index adba753..1023096 100644 --- a/setup.py +++ b/setup.py @@ -48,6 +48,8 @@ setup(name="pytools", "numpy>=1.6.0", ], + package_data={"pytools": ["py.typed", "__init__.pyi"]}, + author="Andreas Kloeckner", url="http://pypi.python.org/pypi/pytools", author_email="inform@tiker.net", -- GitLab From 627b828a0883c2611a7bffce4563e5acf7c9b294 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Wed, 3 Jun 2020 17:04:42 -0500 Subject: [PATCH 2/8] Fix a typo --- pytools/__init__.pyi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytools/__init__.pyi b/pytools/__init__.pyi index f2961ac..dbcf641 100644 --- a/pytools/__init__.pyi +++ b/pytools/__init__.pyi @@ -17,4 +17,4 @@ class UniqueNameGenerator: def __call__(self, based_on: str = ...) -> str: ... def is_name_conflicting(self, name: str) -> bool: ... def add_name(self, name: str) -> bool: ... - def add_names(self, name: Iterable[str]) -> bool: ... + def add_names(self, names: Iterable[str]) -> bool: ... -- GitLab From 859f6f663cab832e0ab1d71ef20653398411607d Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Wed, 3 Jun 2020 17:11:40 -0500 Subject: [PATCH 3/8] Add missing import --- pytools/__init__.pyi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytools/__init__.pyi b/pytools/__init__.pyi index f2961ac..3b9e776 100644 --- a/pytools/__init__.pyi +++ b/pytools/__init__.pyi @@ -1,4 +1,4 @@ -from typing import Any, Iterable, Optional, Set, TypeVar +from typing import Any, Callable, Iterable, Optional, Set, TypeVar from numbers import Number T = TypeVar("T") -- GitLab From ac30447faaa2fe243726833918f8101444cd6935 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Wed, 3 Jun 2020 22:22:51 -0500 Subject: [PATCH 4/8] Drop Py2 support, add inline annotations --- .github/workflows/ci.yml | 20 +++++++++++++--- .gitlab-ci.yml | 23 +++++++----------- pytools/__init__.py | 52 +++++++++++++++++++++++++++------------- pytools/__init__.pyi | 20 ---------------- setup.cfg | 13 ++++++++++ setup.py | 5 +--- 6 files changed, 76 insertions(+), 57 deletions(-) delete mode 100644 pytools/__init__.pyi diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fff93d2..ce53fa8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,7 +18,7 @@ jobs: - uses: actions/setup-python@v1 with: - python-version: '3.x' + python-version: '3.x' - name: "Main Script" run: | curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/prepare-and-run-flake8.sh @@ -32,19 +32,33 @@ jobs: - uses: actions/setup-python@v1 with: - python-version: '3.x' + python-version: '3.x' - name: "Main Script" run: | EXTRA_INSTALL="pymbolic matplotlib" curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/prepare-and-run-pylint.sh . ./prepare-and-run-pylint.sh "$(basename $GITHUB_REPOSITORY)" test/test_*.py + mypy: + name: Mypy + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - + uses: actions/setup-python@v1 + with: + python-version: '3.x' + - name: "Main Script" + run: | + curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/prepare-and-run-mypy.sh + . ./prepare-and-run-mypy.sh python3 mypy" + pytest: name: Pytest on Py${{ matrix.python-version }} runs-on: ubuntu-latest strategy: matrix: - python-version: [2.7, 3.6, 3.7, 3.8, pypy2, pypy3] + python-version: [3.6, 3.7, 3.8, pypy3] steps: - uses: actions/checkout@v2 - diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 04de811..aae441a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,17 +1,3 @@ -Python 2.7: - script: - - py_version=2.7 - - export EXTRA_INSTALL="numpy" - - curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/build-and-test-py-project.sh - - ". ./build-and-test-py-project.sh" - tags: - - python2.7 - except: - - tags - artifacts: - reports: - junit: test/pytest.xml - Python 3: script: - py_version=3 @@ -35,6 +21,15 @@ Flake8: except: - tags +Mypy: + script: + - curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/prepare-and-run-mypy.sh + - ". ./prepare-and-run-mypy.sh python3 mypy" + tags: + - python3 + except: + - tags + Pylint: script: - EXTRA_INSTALL="pymbolic matplotlib" diff --git a/pytools/__init__.py b/pytools/__init__.py index d3dcb77..bdaace8 100644 --- a/pytools/__init__.py +++ b/pytools/__init__.py @@ -26,10 +26,12 @@ THE SOFTWARE. """ +from functools import reduce import operator import sys import logging -from functools import reduce +from typing import Any, Callable, Dict, Iterable, List, Optional, Set, Tuple, TypeVar, Union + import six from six.moves import range, zip, intern, input @@ -152,6 +154,13 @@ Sorting in natural order .. autofunction:: natsorted """ +# {{{ type variables + +T = TypeVar("T") +F = TypeVar('F', bound=Callable[..., Any]) + +# }}} + # {{{ math -------------------------------------------------------------------- @@ -236,7 +245,7 @@ class RecordWithoutPickling(object): will be individually derived from this class. """ - __slots__ = [] + __slots__: List[str] = [] def __init__(self, valuedict=None, exclude=None, **kwargs): assert self.__class__ is not Record @@ -293,7 +302,7 @@ class RecordWithoutPickling(object): class Record(RecordWithoutPickling): - __slots__ = [] + __slots__: List[str] = [] def __getstate__(self): return dict( @@ -417,7 +426,7 @@ class DependentDictionary(object): # {{{ assertive accessors -def one(iterable): +def one(iterable: Iterable[T]) -> T: """Return the first entry of *iterable*. Assert that *iterable* has only that one entry. """ @@ -439,7 +448,10 @@ def one(iterable): return v -def is_single_valued(iterable, equality_pred=operator.eq): +def is_single_valued( + iterable: Iterable[T], + equality_pred: Callable[[T, T], bool] = operator.eq + ) -> bool: it = iter(iterable) try: first_item = next(it) @@ -460,7 +472,10 @@ def all_roughly_equal(iterable, threshold): equality_pred=lambda a, b: abs(a-b) < threshold) -def single_valued(iterable, equality_pred=operator.eq): +def single_valued( + iterable: Iterable[T], + equality_pred: Callable[[T, T], bool] = operator.eq + ) -> T: """Return the first entry of *iterable*; Assert that other entries are the same with the first entry of *iterable*. """ @@ -484,7 +499,7 @@ def single_valued(iterable, equality_pred=operator.eq): # {{{ memoization / attribute storage -def memoize(*args, **kwargs): +def memoize(*args: F, **kwargs: Any) -> F: """Stores previously computed function values in a cache. Two keyword-only arguments are supported: @@ -498,6 +513,8 @@ def memoize(*args, **kwargs): use_kw = bool(kwargs.pop('use_kwargs', False)) + default_key_func: Optional[Callable[..., Any]] + if use_kw: def default_key_func(*inner_args, **inner_kwargs): return inner_args, frozenset(six.iteritems(inner_kwargs)) @@ -600,7 +617,7 @@ def memoize_on_first_arg(function, cache_dict_name=None): return new_wrapper -def memoize_method(method): +def memoize_method(method: F) -> F: """Supports cache deletion via ``method_name.clear_cache(self)``. .. note:: @@ -1831,7 +1848,8 @@ def generate_unique_names(prefix): try_num += 1 -def generate_numbered_unique_names(prefix, num=None): +def generate_numbered_unique_names( + prefix: str, num: Optional[int] = None) -> Iterable[Tuple[int, str]]: if num is None: yield (0, prefix) num = 0 @@ -1853,18 +1871,20 @@ class UniqueNameGenerator(object): .. automethod:: add_names .. automethod:: __call__ """ - def __init__(self, existing_names=None, forced_prefix=""): + def __init__(self, + existing_names: Optional[Set[str]] = None, + forced_prefix: str = ""): if existing_names is None: existing_names = set() self.existing_names = existing_names.copy() self.forced_prefix = forced_prefix - self.prefix_to_counter = {} + self.prefix_to_counter: Dict[str, int] = {} - def is_name_conflicting(self, name): + def is_name_conflicting(self, name: str) -> bool: return name in self.existing_names - def _name_added(self, name): + def _name_added(self, name: str) -> None: """Callback to alert subclasses when a name has been added. .. note:: @@ -1874,7 +1894,7 @@ class UniqueNameGenerator(object): """ pass - def add_name(self, name): + def add_name(self, name: str) -> None: if self.is_name_conflicting(name): raise ValueError("name '%s' conflicts with existing names") if not name.startswith(self.forced_prefix): @@ -1883,11 +1903,11 @@ class UniqueNameGenerator(object): self.existing_names.add(name) self._name_added(name) - def add_names(self, names): + def add_names(self, names: Iterable[str]) -> None: for name in names: self.add_name(name) - def __call__(self, based_on="id"): + def __call__(self, based_on: str = "id") -> str: based_on = self.forced_prefix + based_on counter = self.prefix_to_counter.get(based_on, None) diff --git a/pytools/__init__.pyi b/pytools/__init__.pyi deleted file mode 100644 index c45b427..0000000 --- a/pytools/__init__.pyi +++ /dev/null @@ -1,20 +0,0 @@ -from typing import Any, Callable, Iterable, Optional, Set, TypeVar -from numbers import Number - -T = TypeVar("T") - -def one(iterable: Iterable[T]) -> T: ... -def is_single_valued(iterable: Iterable[T], equality_pred: Callable[[T, T], bool] = ...) -> bool: ... -all_equal = is_single_valued -def all_roughly_equal(iterable: Iterable[Number], threshold: Number) -> bool: ... -def single_valued(iterable: Iterable[T], equality_pred: Callable[[T, T], bool] = ...) -> T: ... - -def memoize(*args: T, **kwargs: Any) -> T: ... -def memoize_method(method: T) -> T: ... - -class UniqueNameGenerator: - def __init__(self, existing_names: Optional[Set[str]] = ..., forced_prefix: str = ...) -> None: ... - def __call__(self, based_on: str = ...) -> str: ... - def is_name_conflicting(self, name: str) -> bool: ... - def add_name(self, name: str) -> bool: ... - def add_names(self, names: Iterable[str]) -> bool: ... diff --git a/setup.cfg b/setup.cfg index 4f28d74..9ce1073 100644 --- a/setup.cfg +++ b/setup.cfg @@ -2,5 +2,18 @@ ignore = E126,E127,E128,E123,E226,E241,E242,E265,E402,W503,E731 max-line-length=85 exclude=pytools/arithmetic_container.py,pytools/decorator.py + [wheel] universal = 1 + +[mypy] +ignore_missing_imports = True + +[mypy-pytools.mpiwrap] +ignore_errors = True + +[mypy-pytools.persistent_dict] +ignore_errors = True + +[mypy-pytools.stopwatch] +ignore_errors = True diff --git a/setup.py b/setup.py index 1023096..9461320 100644 --- a/setup.py +++ b/setup.py @@ -25,9 +25,6 @@ setup(name="pytools", 'Natural Language :: English', 'Programming Language :: Python', 'Programming Language :: Python', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.6', - 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', @@ -48,7 +45,7 @@ setup(name="pytools", "numpy>=1.6.0", ], - package_data={"pytools": ["py.typed", "__init__.pyi"]}, + package_data={"pytools": ["py.typed"]}}, author="Andreas Kloeckner", url="http://pypi.python.org/pypi/pytools", -- GitLab From 510ec5768cf5aa063420c33cf18b674e30b8c4d9 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Wed, 3 Jun 2020 22:25:34 -0500 Subject: [PATCH 5/8] Add missing file --- run-mypy.sh | 3 +++ 1 file changed, 3 insertions(+) create mode 100755 run-mypy.sh diff --git a/run-mypy.sh b/run-mypy.sh new file mode 100755 index 0000000..43e6485 --- /dev/null +++ b/run-mypy.sh @@ -0,0 +1,3 @@ +#! /bin/bash + +mypy pytools -- GitLab From 647b6d40c3d6acb8e83f2c513e05e752345a10cc Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Wed, 3 Jun 2020 22:27:26 -0500 Subject: [PATCH 6/8] CI fixes --- pytools/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pytools/__init__.py b/pytools/__init__.py index bdaace8..bb10a22 100644 --- a/pytools/__init__.py +++ b/pytools/__init__.py @@ -30,7 +30,8 @@ from functools import reduce import operator import sys import logging -from typing import Any, Callable, Dict, Iterable, List, Optional, Set, Tuple, TypeVar, Union +from typing import ( + Any, Callable, Dict, Iterable, List, Optional, Set, Tuple, TypeVar) import six -- GitLab From 6dbd780b8ed16e4cf6f3e54080ac7db0ad4e125f Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Wed, 3 Jun 2020 22:28:39 -0500 Subject: [PATCH 7/8] Fix syntax error --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 9461320..145ddd6 100644 --- a/setup.py +++ b/setup.py @@ -45,7 +45,7 @@ setup(name="pytools", "numpy>=1.6.0", ], - package_data={"pytools": ["py.typed"]}}, + package_data={"pytools": ["py.typed"]}, author="Andreas Kloeckner", url="http://pypi.python.org/pypi/pytools", -- GitLab From 0cd10e36b6b0ac9ec5627f40fde88a71efcf1c32 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Wed, 3 Jun 2020 22:31:32 -0500 Subject: [PATCH 8/8] Add a pylint annotation --- pytools/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytools/__init__.py b/pytools/__init__.py index bb10a22..0567920 100644 --- a/pytools/__init__.py +++ b/pytools/__init__.py @@ -517,7 +517,7 @@ def memoize(*args: F, **kwargs: Any) -> F: default_key_func: Optional[Callable[..., Any]] if use_kw: - def default_key_func(*inner_args, **inner_kwargs): + def default_key_func(*inner_args, **inner_kwargs): # noqa pylint:disable=function-redefined return inner_args, frozenset(six.iteritems(inner_kwargs)) else: default_key_func = None -- GitLab