diff --git a/.gitignore b/.gitignore index 76c77c6..f00bf05 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ __pycache__/ /setup.py *.egg-info /dist/ +.mypy_cache/ diff --git a/.travis.yml b/.travis.yml index 4a2f296..62313e0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -42,3 +42,6 @@ matrix: - python: "3.7" env: ENV=flake8 + + - python: "3.7" + env: ENV=typing diff --git a/dephell_setuptools/_base.py b/dephell_setuptools/_base.py index 51f9054..99fa3f4 100644 --- a/dephell_setuptools/_base.py +++ b/dephell_setuptools/_base.py @@ -1,8 +1,9 @@ # built-in from pathlib import Path -from typing import Union +from typing import Any, Dict, Union # app +from ._cached_property import cached_property from ._constants import FIELDS @@ -21,7 +22,7 @@ def _normalize_path(path: Union[str, Path], default_name: str) -> Path: return path @staticmethod - def _clean(data: dict): + def _clean(data: Dict[str, Any]) -> Dict[str, Any]: result = dict() for k, v in data.items(): if k not in FIELDS: @@ -37,3 +38,7 @@ def _clean(data: dict): result['keywords'] = sum((kw.split() for kw in result['keywords']), []) return result + + @cached_property + def content(self) -> Dict[str, Any]: + raise NotImplementedError diff --git a/dephell_setuptools/_cfg.py b/dephell_setuptools/_cfg.py index 9788a5c..29b5b9e 100644 --- a/dephell_setuptools/_cfg.py +++ b/dephell_setuptools/_cfg.py @@ -2,13 +2,14 @@ from configparser import ConfigParser from copy import deepcopy from pathlib import Path -from typing import Dict, List, Optional, Union +from typing import Any, Dict, Union # external from setuptools.config import ConfigMetadataHandler, ConfigOptionsHandler # app from ._base import BaseReader +from ._cached_property import cached_property from ._constants import FIELDS @@ -16,8 +17,8 @@ class CfgReader(BaseReader): def __init__(self, path: Union[str, Path]): self.path = self._normalize_path(path, default_name='setup.cfg') - @property - def content(self) -> Optional[Dict[str, Union[List, Dict]]]: + @cached_property + def content(self) -> Dict[str, Any]: path = self.path if path.name == 'setup.py': path = path.parent / 'setup.cfg' @@ -27,7 +28,7 @@ def content(self) -> Optional[Dict[str, Union[List, Dict]]]: parser = ConfigParser() parser.read(str(path)) - options = deepcopy(parser._sections) + options = deepcopy(parser._sections) # type: ignore for section, content in options.items(): for k, v in content.items(): options[section][k] = ('', v) diff --git a/dephell_setuptools/_cmd.py b/dephell_setuptools/_cmd.py index a9bcc75..ab14980 100644 --- a/dephell_setuptools/_cmd.py +++ b/dephell_setuptools/_cmd.py @@ -7,9 +7,11 @@ from distutils.core import Command from pathlib import Path from tempfile import NamedTemporaryFile +from typing import Any, Dict # app from ._base import BaseReader +from ._cached_property import cached_property from ._constants import FIELDS @@ -24,8 +26,8 @@ def cd(path: Path): class CommandReader(BaseReader): - @property - def content(self): + @cached_property + def content(self) -> Dict[str, Any]: # generate a temporary json file which contains the metadata output_json = NamedTemporaryFile() cmd = [ @@ -44,11 +46,11 @@ def content(self): env={'PYTHONPATH': str(Path(__file__).parent.parent)}, ) if result.returncode != 0: - return None + raise RuntimeError(result.stderr.decode().split('\n')[-1]) with open(output_json.name) as stream: - result = json.load(stream) - return self._clean(result) + content = json.load(stream) + return self._clean(content) class JSONCommand(Command): diff --git a/dephell_setuptools/_manager.py b/dephell_setuptools/_manager.py index 8ccdaa4..0833e6a 100644 --- a/dephell_setuptools/_manager.py +++ b/dephell_setuptools/_manager.py @@ -1,9 +1,10 @@ # built-in from logging import getLogger from pathlib import Path -from typing import Any, Callable, Iterable, Union +from typing import Any, Callable, Dict, Iterable, Union, Type # app +from ._base import BaseReader from ._cfg import CfgReader from ._cmd import CommandReader from ._pkginfo import PkgInfoReader @@ -22,8 +23,8 @@ def read_setup(*, path: Union[str, Path], error_handler: Callable[[Exception], Any] = logger.exception, - readers: Iterable = ALL_READERS): - result = dict() + readers: Iterable[Type[BaseReader]] = ALL_READERS) -> Dict[str, Any]: + result = dict() # type: Dict[str, Any] for reader in readers: try: content = reader(path=path).content diff --git a/dephell_setuptools/_pkginfo.py b/dephell_setuptools/_pkginfo.py index 3b35ae6..49a7574 100644 --- a/dephell_setuptools/_pkginfo.py +++ b/dephell_setuptools/_pkginfo.py @@ -1,20 +1,22 @@ # built-in import json import subprocess +from typing import Any, Dict # app from ._base import BaseReader +from ._cached_property import cached_property class PkgInfoReader(BaseReader): - @property - def content(self): + @cached_property + def content(self) -> Dict[str, Any]: if self.path.is_file() and self.path.name != 'setup.py': - return None + raise NameError('cannot parse non setup.py named files') cmd = ['pkginfo', '--json', str(self.path)] result = subprocess.run(cmd, stderr=subprocess.PIPE, stdout=subprocess.PIPE) if result.returncode != 0: - return None + raise RuntimeError(result.stderr.decode().split('\n')[-1]) content = json.loads(result.stdout.decode()) return self._clean(content) diff --git a/dephell_setuptools/_static.py b/dephell_setuptools/_static.py index 19fc592..01e3801 100644 --- a/dephell_setuptools/_static.py +++ b/dephell_setuptools/_static.py @@ -1,6 +1,6 @@ # built-in import ast -from typing import Any, Dict, List, Optional, Union +from typing import Any, Dict, Optional # app from ._base import BaseReader @@ -9,9 +9,9 @@ class StaticReader(BaseReader): @cached_property - def content(self) -> Optional[Dict[str, Union[List, Dict]]]: + def content(self) -> Dict[str, Any]: if not self.call: - return None + raise LookupError('cannot find setup()') result = self._get_call_kwargs(self.call) return self._clean(result) diff --git a/pyproject.toml b/pyproject.toml index 009eca8..3515aa6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,11 @@ from = {format = "pip", path = "requirements-flake.txt"} python = ">=3.6" command = "flake8" -# -- FLIT -- # +[tool.dephell.typing] +from = {format = "flit", path = "pyproject.toml"} +envs = ["main", "dev"] +command = "mypy --ignore-missing-imports --allow-redefinition dephell_setuptools" + [tool.flit.metadata] module="dephell_setuptools" @@ -40,6 +44,7 @@ classifiers=[ [tool.flit.metadata.requires-extra] dev = [ + "mypy", "pkginfo", "pytest", ]