From 43a5519531cc8cc44199c9251d0fd4e6208b5bdb Mon Sep 17 00:00:00 2001 From: Bradley Dice Date: Thu, 2 Apr 2020 11:02:05 -0500 Subject: [PATCH] Add typing to signac.core.json. (#313) * Add typing to signac.core.json. * Run mypy in continuous integration. * Remove unused FileNotFoundError (likely Python 2 legacy compatibility). * Ignore missing imports in mypy checks. * Define mypy config. * Ignore typing in syncutil dircmp_deep. * Fix error in print statement parentheses. * Add type ignores. * Remove unused format argument. * Ignore errors in custom logging level. * Run mypy on . instead of just signac package. * Update changelog. * Rename style-check to pre-checks. * Update signac/syncutil.py Co-Authored-By: Carl Simon Adorf * Update signac/syncutil.py Co-authored-by: Carl Simon Adorf --- .circleci/config.yml | 25 +++++++++++++++---------- changelog.txt | 8 ++++++++ setup.cfg | 3 +++ signac/common/errors.py | 4 ---- signac/common/host.py | 2 +- signac/contrib/filterparse.py | 2 +- signac/contrib/indexing.py | 2 +- signac/contrib/project.py | 2 +- signac/core/h5store.py | 4 ++-- signac/core/json.py | 5 +++-- signac/errors.py | 2 -- signac/syncutil.py | 9 +++++---- 12 files changed, 40 insertions(+), 28 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index e13b7c980..4642e8c89 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -8,7 +8,7 @@ orbs: win: circleci/windows@2.2.0 # Enables Windows executors jobs: - style-check: + pre-checks: docker: - image: circleci/python:3.8 @@ -19,18 +19,23 @@ jobs: - run: name: install-dependencies command: | - pip install --progress-bar off --user -U flake8==3.7.1 pydocstyle==5.0.2 + pip install --progress-bar off --user -U flake8==3.7.1 pydocstyle==5.0.2 mypy==0.770 - run: - name: style-check-code + name: check-style command: | python -m flake8 --show-source . - run: - name: style-check-doc-strings + name: check-docstyle command: | pydocstyle + - run: + name: check-type-hints + command: | + mypy . + linux-python-38: &linux-template docker: - image: circleci/python:3.8 @@ -180,25 +185,25 @@ workflows: version: 2 test: jobs: - - style-check + - pre-checks - linux-python-38: requires: - - style-check + - pre-checks - linux-python-37: requires: - - style-check + - pre-checks - linux-python-36: requires: - - style-check + - pre-checks - linux-python-35: requires: - - style-check + - pre-checks - linux-pypy-3: requires: - linux-python-36 - windows-python-38: requires: - - style-check + - pre-checks - check-metadata: filters: branches: diff --git a/changelog.txt b/changelog.txt index 8d555db16..dae415e26 100644 --- a/changelog.txt +++ b/changelog.txt @@ -10,6 +10,14 @@ Version 1 next ---- +Added ++++++ + +- Type annotations are validated during continuous integration (#313). + +Fixed ++++++ + - Fix the ``signac config verify`` command (previously broken) (#301, #302). [1.4.0] -- 2020-02-28 diff --git a/setup.cfg b/setup.cfg index d9858ae0a..86e0a4636 100644 --- a/setup.cfg +++ b/setup.cfg @@ -18,6 +18,9 @@ exclude = configobj,passlib,cite.py,conf.py match = jsondict.py ignore = D105, D107, D203, D213 +[mypy] +ignore_missing_imports = True + [coverage:run] source = signac omit = diff --git a/signac/common/errors.py b/signac/common/errors.py index dbcd0454f..6ca7d290f 100644 --- a/signac/common/errors.py +++ b/signac/common/errors.py @@ -22,9 +22,5 @@ class ExportError(Error, RuntimeError): pass -class FileNotFoundError(Error, FileNotFoundError): - pass - - class FetchError(FileNotFoundError): pass diff --git a/signac/common/host.py b/signac/common/host.py index 299f4bd0f..c131ccfd1 100644 --- a/signac/common/host.py +++ b/signac/common/host.py @@ -17,7 +17,7 @@ SESSION_PASSWORD_HASH_CACHE = SimpleKeyring() -SESSION_USERNAME_CACHE = dict() +SESSION_USERNAME_CACHE = dict() # type: ignore """ THIS MODULE IS DEPRECATED! diff --git a/signac/contrib/filterparse.py b/signac/contrib/filterparse.py index af247d57f..b6f480fd5 100644 --- a/signac/contrib/filterparse.py +++ b/signac/contrib/filterparse.py @@ -56,7 +56,7 @@ def _cast(x): "Attempt to interpret x with the correct type." try: if x in CAST_MAPPING_WARNING: - print("Did you mean {}?".format(CAST_MAPPING_WARNING[x], file=sys.stderr)) + print("Did you mean {}?".format(CAST_MAPPING_WARNING[x]), file=sys.stderr) return CAST_MAPPING[x] except KeyError: try: diff --git a/signac/contrib/indexing.py b/signac/contrib/indexing.py index 0f125bfed..611264cbb 100644 --- a/signac/contrib/indexing.py +++ b/signac/contrib/indexing.py @@ -159,7 +159,7 @@ class RegexFileCrawler(BaseCrawler): MyCrawler.define('.*\/a_(?P\d+)\.txt', 'TextFile') """ "Mapping of compiled regex objects and associated formats." - definitions = dict() + definitions = dict() # type: ignore @classmethod @deprecated(deprecated_in="1.3", removed_in="2.0", current_version=__version__, diff --git a/signac/contrib/project.py b/signac/contrib/project.py index 105c0cc09..00be65260 100644 --- a/signac/contrib/project.py +++ b/signac/contrib/project.py @@ -1195,7 +1195,7 @@ def check(self): if corrupted: logger.error( "At least one job appears to be corrupted. Call Project.repair() " - "to try to fix errors.".format(len(corrupted))) + "to try to fix errors.") raise JobsCorruptedError(corrupted) def repair(self, fn_statepoints=None, index=None, job_ids=None): diff --git a/signac/core/h5store.py b/signac/core/h5store.py index 0171e53dc..e41d02581 100644 --- a/signac/core/h5store.py +++ b/signac/core/h5store.py @@ -507,8 +507,8 @@ class H5StoreManager(DictManager): :param prefix: The directory prefix shared by all stores managed by this class. """ - cls = H5Store - suffix = '.h5' + cls = H5Store # type: ignore + suffix = '.h5' # type: ignore @staticmethod def _validate_key(key): diff --git a/signac/core/json.py b/signac/core/json.py index 0d71f12bd..7c3ff47b0 100644 --- a/signac/core/json.py +++ b/signac/core/json.py @@ -4,6 +4,7 @@ import logging from json import load, loads, JSONEncoder from json.decoder import JSONDecodeError +from typing import Any, Dict, Optional logger = logging.getLogger(__name__) @@ -21,7 +22,7 @@ class CustomJSONEncoder(JSONEncoder): an object that is otherwise not serializable, by calling the object's `_as_dict()` method. """ - def default(self, o): + def default(self, o: Any) -> Dict[str, Any]: if NUMPY: if isinstance(o, numpy.number): return o.item() @@ -35,7 +36,7 @@ def default(self, o): return super(CustomJSONEncoder, self).default(o) -def dumps(o, sort_keys=False, indent=None): +def dumps(o: Any, sort_keys: bool = False, indent: Optional[int] = None) -> str: return CustomJSONEncoder(sort_keys=sort_keys, indent=indent).encode(o) diff --git a/signac/errors.py b/signac/errors.py index 8b413d28a..49873ced5 100644 --- a/signac/errors.py +++ b/signac/errors.py @@ -10,7 +10,6 @@ from .common.errors import ConfigError from .common.errors import AuthenticationError from .common.errors import ExportError -from .common.errors import FileNotFoundError from .common.errors import FetchError from .contrib.errors import DestinationExistsError @@ -67,7 +66,6 @@ class KeyTypeError(TypeError): 'ConfigError', 'AuthenticationError', 'ExportError', - 'FileNotFoundError', 'FetchError', 'DestinationExistsError', 'JobsCorruptedError', diff --git a/signac/syncutil.py b/signac/syncutil.py index 88c728a17..a16d913e7 100644 --- a/signac/syncutil.py +++ b/signac/syncutil.py @@ -11,14 +11,14 @@ logger = logging.getLogger('sync') logging.addLevelName(LEVEL_MORE, 'MORE') -logging.MORE = LEVEL_MORE +logging.MORE = LEVEL_MORE # type: ignore def log_more(msg, *args, **kwargs): logger.log(LEVEL_MORE, msg, *args, **kwargs) -logger.more = log_more +logger.more = log_more # type: ignore def copytree(src, dst, copy_function=shutil.copy2, symlinks=False): @@ -48,13 +48,14 @@ def copytree(src, dst, copy_function=shutil.copy2, symlinks=False): class dircmp_deep(dircmp): - def phase3(self): # Find out differences between common files xx = filecmp.cmpfiles(self.left, self.right, self.common_files, shallow=False) self.same_files, self.diff_files, self.funny_files = xx methodmap = dict(dircmp.methodmap) - methodmap['samefiles'] = methodmap['diff_files'] = phase3 + # The type check for the following line must be ignored. + # See: https://github.com/python/mypy/issues/708 + methodmap['same_files'] = methodmap['diff_files'] = phase3 # type: ignore class _DocProxy(object):