diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fff93d262e3997fca316a9747b08bf78f7b4ebd4..ce53fa814662ee22de440f72f4f14d82b5a41d95 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 04de81190a88c3ce7f247aace052551281fb9f4d..aae441a228fac6ba22e456ef156d763e536e49ad 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 d3dcb7722808e72c5166e1c3fc45a6e23992c1a1..0567920e03400cc3557c91040d13f882c48df037 100644 --- a/pytools/__init__.py +++ b/pytools/__init__.py @@ -26,10 +26,13 @@ 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) + import six from six.moves import range, zip, intern, input @@ -152,6 +155,13 @@ Sorting in natural order .. autofunction:: natsorted """ +# {{{ type variables + +T = TypeVar("T") +F = TypeVar('F', bound=Callable[..., Any]) + +# }}} + # {{{ math -------------------------------------------------------------------- @@ -236,7 +246,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 +303,7 @@ class RecordWithoutPickling(object): class Record(RecordWithoutPickling): - __slots__ = [] + __slots__: List[str] = [] def __getstate__(self): return dict( @@ -417,7 +427,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 +449,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 +473,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 +500,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,8 +514,10 @@ 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): + 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 @@ -600,7 +618,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 +1849,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 +1872,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 +1895,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 +1904,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/py.typed b/pytools/py.typed new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/run-mypy.sh b/run-mypy.sh new file mode 100755 index 0000000000000000000000000000000000000000..43e64854d17710b3e5835c6fe8f2624b9708f162 --- /dev/null +++ b/run-mypy.sh @@ -0,0 +1,3 @@ +#! /bin/bash + +mypy pytools diff --git a/setup.cfg b/setup.cfg index 4f28d744a43e004359603baf770d76abca656fa4..9ce1073847ce374bb30f029a4d97a2ca931df549 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 adba753c7690f5c7a258d324d3799a0aa98618ca..145ddd6f34dabc21bb79e3b1b56379f4aef219b5 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,6 +45,8 @@ setup(name="pytools", "numpy>=1.6.0", ], + package_data={"pytools": ["py.typed"]}, + author="Andreas Kloeckner", url="http://pypi.python.org/pypi/pytools", author_email="inform@tiker.net",