From 26352a5038fe9db8e8bf4cd5639d7238a93e68cc Mon Sep 17 00:00:00 2001 From: Lorenzo Bolla Date: Mon, 11 Mar 2019 08:42:53 +0000 Subject: [PATCH] Add typing --- MANIFEST.in | 1 + Makefile | 18 ++++++++++++++++++ mypy.ini | 17 +++++++++++++++++ requirements_tests.txt | 3 +++ setup.py | 7 +++++++ tests/test_env.py | 1 - tests/test_include.py | 1 - yamlenv/__init__.py | 6 +++++- yamlenv/env.py | 21 ++++++++++++++++----- yamlenv/loader.py | 11 ++++++++--- yamlenv/py.typed | 0 yamlenv/types.py | 9 +++++++++ 12 files changed, 84 insertions(+), 11 deletions(-) create mode 100644 MANIFEST.in create mode 100644 Makefile create mode 100644 mypy.ini create mode 100644 requirements_tests.txt create mode 100644 yamlenv/py.typed create mode 100644 yamlenv/types.py diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..765671d --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1 @@ +include yamlenv/py.typed diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3d631c2 --- /dev/null +++ b/Makefile @@ -0,0 +1,18 @@ +.PHONY: develop + +develop: + @pip install -qe . + +test: tox mypy flake8 + +tox: requirements_tests + tox + +mypy: requirements_tests + mypy yamlenv + +flake8: requirements_tests + flake8 yamlenv + +requirements_tests: develop + @pip install -qUr requirements_tests.txt diff --git a/mypy.ini b/mypy.ini new file mode 100644 index 0000000..164af9c --- /dev/null +++ b/mypy.ini @@ -0,0 +1,17 @@ +[mypy] +python_version = 3.6 +# strict mode +disallow_subclassing_any = True +disallow_any_generics = True +disallow_untyped_calls = True +disallow_untyped_defs = True +disallow_incomplete_defs = True +check_untyped_defs = True +disallow_untyped_decorators = True +no_implicit_optional = True +warn_redundant_casts = True +warn_unused_ignores = True +warn_return_any = True + +[mypy-setuptools] +ignore_missing_imports = True \ No newline at end of file diff --git a/requirements_tests.txt b/requirements_tests.txt new file mode 100644 index 0000000..1951edd --- /dev/null +++ b/requirements_tests.txt @@ -0,0 +1,3 @@ +flake8 +mypy +tox \ No newline at end of file diff --git a/setup.py b/setup.py index bb1c1fa..c0bb116 100644 --- a/setup.py +++ b/setup.py @@ -15,5 +15,12 @@ install_requires=[ 'PyYAML>=3.12', 'six>=1.10', + 'typing; python_version<"3.6"', + ], + classifiers=[ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "Programming Language :: Python :: 2", + "Programming Language :: Python :: 3", ], ) diff --git a/tests/test_env.py b/tests/test_env.py index 9a41c70..aef792b 100644 --- a/tests/test_env.py +++ b/tests/test_env.py @@ -1,4 +1,3 @@ -# pylint: disable=no-self-use import os import unittest diff --git a/tests/test_include.py b/tests/test_include.py index b095571..5afd070 100644 --- a/tests/test_include.py +++ b/tests/test_include.py @@ -1,4 +1,3 @@ -# pylint: disable=no-self-use import tempfile import unittest diff --git a/yamlenv/__init__.py b/yamlenv/__init__.py index e5fe273..77135f5 100644 --- a/yamlenv/__init__.py +++ b/yamlenv/__init__.py @@ -1,16 +1,20 @@ +import typing as T import yaml -from . import env, loader +from yamlenv import env, loader +from yamlenv.types import Stream __version__ = '0.6.0' def load(stream): + # type: (Stream) -> T.Any data = yaml.load(stream, loader.Loader) return env.interpolate(data) def load_all(stream): + # type: (Stream) -> T.Iterator[T.Any] for data in yaml.load_all(stream, loader.Loader): yield env.interpolate(data) diff --git a/yamlenv/env.py b/yamlenv/env.py index 5660a32..c163692 100644 --- a/yamlenv/env.py +++ b/yamlenv/env.py @@ -1,4 +1,3 @@ -# pylint: disable=undefined-variable import os import re import yaml @@ -10,12 +9,18 @@ from collections import Mapping, Sequence, Set # noqa import six +import typing as T +from yamlenv.types import Cache, Obj, ObjIFunc, Path, WalkType -def objwalk(obj, path=(), memo=None): + +def objwalk(obj, path=None, memo=None): + # type: (Obj, T.Optional[Path], T.Optional[Cache]) -> WalkType + if path is None: + path = tuple() if memo is None: memo = set() - iterator = None + iterator = None # type: T.Optional[ObjIFunc] if isinstance(obj, Mapping): iterator = six.iteritems elif isinstance( @@ -44,6 +49,7 @@ class EnvVar(object): r'\$\{(?P[^:-]+)((?P:?)-(?P.*))?\}') def __init__(self, name, separator, default, string): + # type: (str, str, str, str) -> None self.name = name self.separator = separator self.default = default @@ -51,10 +57,12 @@ def __init__(self, name, separator, default, string): @property def allow_null_default(self): + # type: () -> bool return self.separator == '' @property def value(self): + # type: () -> str value = os.environ.get(self.name) if value: return self.RE.sub(value, self.string) @@ -64,20 +72,23 @@ def value(self): @property def yaml_value(self): + # type: () -> T.Any return yaml.safe_load(self.value) @classmethod def from_string(cls, s): + # type: (str) -> T.Optional[EnvVar] if not isinstance(s, six.string_types): return None data = cls.RE.search(s) if not data: return None - data = data.groupdict() - return cls(data['name'], data['separator'], data['default'], s) + gd = data.groupdict() + return cls(gd['name'], gd['separator'], gd['default'], s) def interpolate(data): + # type: (T.Any) -> Obj for path, obj in objwalk(data): e = EnvVar.from_string(obj) if e is not None: diff --git a/yamlenv/loader.py b/yamlenv/loader.py index 64b76dd..1ab6459 100644 --- a/yamlenv/loader.py +++ b/yamlenv/loader.py @@ -1,6 +1,6 @@ -# pylint: disable=redefined-builtin import os import json +import typing as T import six import yaml @@ -8,7 +8,7 @@ class LoaderMeta(type): - def __new__(mcs, __name__, __bases__, __dict__): + def __new__(mcs, __name__, __bases__, __dict__): # type: ignore """Add include constructer to class.""" # register the include constructor on the class @@ -23,6 +23,7 @@ class Loader(yaml.Loader): """YAML Loader with `!include` constructor.""" def __init__(self, stream, filenames_seen=None): + # type: (T.IO[str], T.Optional[T.Set[str]]) -> None """Initialise Loader.""" try: @@ -34,16 +35,20 @@ def __init__(self, stream, filenames_seen=None): yaml.Loader.__init__(self, stream) def _remeber(self): + # type: () -> T.Callable[[T.IO[str]], Loader] """Create a loader with the same filenames cache.""" def loader(stream): + # type: (T.IO[str]) -> Loader return Loader(stream, self._filenames_seen) return loader def construct_include(self, node): + # type: (T.Any) -> T.Any """Include file referenced at node.""" filename = os.path.abspath( - os.path.join(self._root, self.construct_scalar(node))) + os.path.join( + self._root, self.construct_scalar(node))) # type: ignore extension = os.path.splitext(filename)[1].lstrip('.') if filename in self._filenames_seen: diff --git a/yamlenv/py.typed b/yamlenv/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/yamlenv/types.py b/yamlenv/types.py new file mode 100644 index 0000000..ce39cac --- /dev/null +++ b/yamlenv/types.py @@ -0,0 +1,9 @@ +from typing import Any, Callable, IO, Iterator, Set, Tuple, Union + +Obj = Any +Path = Tuple[Any, ...] +WalkType = Iterator[Tuple[Path, Obj]] +Cache = Set[int] +ObjI = Iterator[Tuple[Any, Any]] +ObjIFunc = Callable[[Obj], ObjI] +Stream = Union[str, IO[str]]