From e433ec5bd526ed2e87279a4fcc4c4e7fef168f26 Mon Sep 17 00:00:00 2001 From: Gram Date: Mon, 20 Apr 2020 15:49:58 +0200 Subject: [PATCH 01/13] +mypy env --- pyproject.toml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 922d717c..6436c623 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,6 +20,11 @@ from = {format = "poetry", path = "pyproject.toml"} envs = ["docs"] command = "sphinx-build -W docs docs/build" +[tool.dephell.typing] +from = {format = "poetry", path = "pyproject.toml"} +envs = ["main", "tests"] +command = "mypy --ignore-missing-imports --allow-redefinition deal/" + [tool.flakehell.plugins] deal = ["+*"] @@ -70,19 +75,22 @@ typeguard = "*" vaa = ">=0.2.1" [tool.poetry.dev-dependencies] +# tests coverage = "*" marshmallow = "*" +mypy = "*" pytest = "*" pytest-cov = "*" urllib3 = "*" +# docs m2r = "*" recommonmark = "*" sphinx = "*" sphinx-rtd-theme = "*" [tool.poetry.extras] -tests = ["coverage", "marshmallow", "pytest", "pytest-cov", "urllib3"] +tests = ["coverage", "marshmallow", "mypy", "pytest", "pytest-cov", "urllib3"] docs = ["m2r", "recommonmark", "sphinx", "sphinx-rtd-theme", "urllib3"] [tool.poetry.plugins."flake8.extension"] From ea9535baedc8ec3f39e57507c4ca40cfa0ba919b Mon Sep 17 00:00:00 2001 From: Gram Date: Mon, 20 Apr 2020 16:05:35 +0200 Subject: [PATCH 02/13] fix some types --- deal/_decorators/base.py | 2 +- deal/linter/_checker.py | 4 ++-- deal/linter/_cli.py | 4 ++-- deal/linter/_extractors/common.py | 10 ++++++++-- deal/linter/_extractors/exceptions.py | 7 +++++-- deal/linter/_rules.py | 4 ++-- 6 files changed, 20 insertions(+), 11 deletions(-) diff --git a/deal/_decorators/base.py b/deal/_decorators/base.py index 287197c1..a29a5948 100644 --- a/deal/_decorators/base.py +++ b/deal/_decorators/base.py @@ -29,7 +29,7 @@ def __init__(self, validator: Callable, *, message: str = None, self.exception = self.exception(message) # type: ignore @staticmethod - def _make_validator(validator, message: str): + def _make_validator(validator, message: str = None): # implicitly wrap in vaa all external validators with suppress(TypeError): return vaa.wrap(validator, simple=False) diff --git a/deal/linter/_checker.py b/deal/linter/_checker.py index b29c127a..d41035f8 100644 --- a/deal/linter/_checker.py +++ b/deal/linter/_checker.py @@ -10,10 +10,10 @@ class Checker: name = 'deal' version = '1.0.0' - _tree = None + _tree = None # type: ast.Module _rules = rules - def __init__(self, tree: ast.AST, file_tokens=None, filename: str = 'stdin'): + def __init__(self, tree: ast.Module, file_tokens=None, filename: str = 'stdin'): self._tree = tree self._filename = filename diff --git a/deal/linter/_cli.py b/deal/linter/_cli.py index 6be7a24b..3e8f3322 100644 --- a/deal/linter/_cli.py +++ b/deal/linter/_cli.py @@ -3,7 +3,7 @@ from argparse import ArgumentParser from pathlib import Path from textwrap import dedent, indent -from typing import Iterable, Iterator, Union +from typing import Iterable, Iterator, Sequence, Union from ._checker import Checker @@ -65,7 +65,7 @@ def get_parser() -> ArgumentParser: return parser -def main(argv: Iterable) -> int: +def main(argv: Sequence[str]) -> int: parser = get_parser() args = parser.parse_args(argv) prev = None diff --git a/deal/linter/_extractors/common.py b/deal/linter/_extractors/common.py index 630b55fb..e1cfacbc 100644 --- a/deal/linter/_extractors/common.py +++ b/deal/linter/_extractors/common.py @@ -57,8 +57,14 @@ def get_name(expr) -> Optional[str]: return expr.name if isinstance(expr, astroid.Attribute): - return get_name(expr.expr) + '.' + expr.attrname + left = get_name(expr.expr) + if left is None: + return None + return left + '.' + expr.attrname if isinstance(expr, ast.Attribute): - return get_name(expr.value) + '.' + expr.attr + left = get_name(expr.value) + if left is None: + return None + return left + '.' + expr.attr return None diff --git a/deal/linter/_extractors/exceptions.py b/deal/linter/_extractors/exceptions.py index 09b175cd..43106729 100644 --- a/deal/linter/_extractors/exceptions.py +++ b/deal/linter/_extractors/exceptions.py @@ -19,8 +19,11 @@ def get_exceptions(body: list, *, dive: bool = True) -> Iterator[Token]: # explicit raise if isinstance(expr, TOKENS.RAISE): name = get_name(expr.exc) - # raise instance - if not name and isinstance(expr.exc, TOKENS.CALL): + if not name: + # raised a value, too tricky + if not isinstance(expr.exc, TOKENS.CALL): + continue + # raised an instance of an exception name = get_name(expr.exc.func) if not name or name[0].islower(): continue diff --git a/deal/linter/_rules.py b/deal/linter/_rules.py index 0a645dc2..e1a79a57 100644 --- a/deal/linter/_rules.py +++ b/deal/linter/_rules.py @@ -65,10 +65,10 @@ def _check(self, func: Func, contract: Contract) -> Iterator[Error]: value=str(token.value), ) if isinstance(result, str): - yield Error(text=result, **error_info) + yield Error(text=result, **error_info) # type: ignore continue if not result: - yield Error(text=self.message, **error_info) + yield Error(text=self.message, **error_info) # type: ignore @register From b514908bad8e15f89941bd5a7cda8d738a244659 Mon Sep 17 00:00:00 2001 From: Gram Date: Mon, 20 Apr 2020 19:15:22 +0200 Subject: [PATCH 03/13] fix more types --- deal/_decorators/base.py | 8 +++++--- deal/_decorators/offline.py | 2 +- deal/_decorators/raises.py | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/deal/_decorators/base.py b/deal/_decorators/base.py index a29a5948..39ffd449 100644 --- a/deal/_decorators/base.py +++ b/deal/_decorators/base.py @@ -3,7 +3,7 @@ from asyncio import iscoroutinefunction from contextlib import suppress from functools import update_wrapper -from typing import Callable, Type +from typing import Callable import vaa @@ -16,8 +16,8 @@ class Base: exception: ExceptionType = ContractError - def __init__(self, validator: Callable, *, message: str = None, - exception: Type[ExceptionType] = None, debug: bool = False): + def __init__(self, validator, *, message: str = None, + exception: ExceptionType = None, debug: bool = False): """ Step 1. Set contract (validator). """ @@ -30,6 +30,8 @@ def __init__(self, validator: Callable, *, message: str = None, @staticmethod def _make_validator(validator, message: str = None): + if validator is None: + return None # implicitly wrap in vaa all external validators with suppress(TypeError): return vaa.wrap(validator, simple=False) diff --git a/deal/_decorators/offline.py b/deal/_decorators/offline.py index 1dd73a42..8bf61f50 100644 --- a/deal/_decorators/offline.py +++ b/deal/_decorators/offline.py @@ -15,7 +15,7 @@ def __init__(self, *, message: str = None, exception: ExceptionType = None, debu Step 1. Init params. """ super().__init__( - validator=None, + validator=None, # type: ignore message=message, exception=exception, debug=debug, diff --git a/deal/_decorators/raises.py b/deal/_decorators/raises.py index bac3a944..1ae3146f 100644 --- a/deal/_decorators/raises.py +++ b/deal/_decorators/raises.py @@ -16,7 +16,7 @@ def __init__(self, *exceptions, message: str = None, exception: ExceptionType = """ self.exceptions: Tuple[Type[Exception], ...] = exceptions super().__init__( - validator=None, + validator=None, # type: ignore message=message, exception=exception, debug=debug, From a34ea7726b9245aab403f0467821fce292586b47 Mon Sep 17 00:00:00 2001 From: Gram Date: Mon, 20 Apr 2020 19:40:38 +0200 Subject: [PATCH 04/13] ignore more types --- deal/_decorators/inv.py | 6 +++--- deal/_imports.py | 18 +++++++++--------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/deal/_decorators/inv.py b/deal/_decorators/inv.py index 91e77469..af9574c6 100644 --- a/deal/_decorators/inv.py +++ b/deal/_decorators/inv.py @@ -65,7 +65,7 @@ def __setattr__(self, name: str, value): class Invariant(Base): exception: ExceptionType = InvContractError - def validate(self, obj) -> None: + def validate(self, obj) -> None: # type: ignore """ Step 6. Process contract (validator) """ @@ -81,7 +81,7 @@ def validate_chain(self, *args, **kwargs) -> None: self.validate(*args, **kwargs) self.child_validator(*args, **kwargs) - def __call__(self, _class: type): + def __call__(self, _class: type): # type: ignore """ Step 2. Return wrapped class. """ @@ -89,7 +89,7 @@ def __call__(self, _class: type): # if already invarianted if hasattr(_class, '_validate_base'): - self.child_validator = _class._validate_base + self.child_validator = _class._validate_base # type: ignore patched_class = type( _class.__name__, (_class, ), diff --git a/deal/_imports.py b/deal/_imports.py index 96246612..157fa469 100644 --- a/deal/_imports.py +++ b/deal/_imports.py @@ -3,7 +3,7 @@ import sys from _frozen_importlib_external import PathFinder from types import ModuleType -from typing import Callable, Optional, List +from typing import Any, Callable, Optional, List from .linter._extractors.common import get_name from . import _aliases @@ -60,10 +60,10 @@ def exec_module(self, module: ModuleType) -> None: @staticmethod def _get_contracts(tree: ast.Module) -> List[ast.AST]: - for node in tree.body: - if not type(node) is ast.Expr: + for node in tree.body: # type: Any + if type(node) is not ast.Expr: continue - if not type(node.value) is ast.Call: + if type(node.value) is not ast.Call: continue if get_name(node.value.func) != 'deal.module_load': continue @@ -74,12 +74,12 @@ def _get_contracts(tree: ast.Module) -> List[ast.AST]: def _exec_contract(cls, node: ast.AST) -> Optional[Callable]: """Get AST node and return a contract function """ - if type(node) is ast.Call and not node.args: - return cls._exec_contract(node.func) + if type(node) is ast.Call and not node.args: # type: ignore + return cls._exec_contract(node.func) # type: ignore if not isinstance(node, ast.Attribute): return None - if node.value.id != 'deal': + if node.value.id != 'deal': # type: ignore return None contract = getattr(_aliases, node.attr, None) if contract is None: @@ -109,7 +109,7 @@ def activate(debug: bool = False) -> bool: if DealFinder in sys.meta_path: return False index = sys.meta_path.index(PathFinder) - sys.meta_path[index] = DealFinder + sys.meta_path[index] = DealFinder # type: ignore return True @@ -118,6 +118,6 @@ def deactivate() -> bool: """ if DealFinder not in sys.meta_path: return False - index = sys.meta_path.index(DealFinder) + index = sys.meta_path.index(DealFinder) # type: ignore sys.meta_path[index] = PathFinder return True From 9326f17d9f662f1121069c11e58614f37b846711 Mon Sep 17 00:00:00 2001 From: Gram Date: Mon, 20 Apr 2020 19:46:00 +0200 Subject: [PATCH 05/13] sort imports --- deal/__init__.py | 4 ++-- deal/_decorators/base.py | 1 + deal/_imports.py | 9 ++++++--- deal/linter/__init__.py | 1 + deal/linter/__main__.py | 3 +++ deal/linter/_checker.py | 4 +++- deal/linter/_cli.py | 2 ++ deal/linter/_contract.py | 3 +++ deal/linter/_error.py | 2 ++ deal/linter/_extractors/__init__.py | 1 + deal/linter/_extractors/common.py | 2 ++ deal/linter/_extractors/contracts.py | 7 +++++-- deal/linter/_extractors/exceptions.py | 5 ++++- deal/linter/_extractors/imports.py | 5 ++++- deal/linter/_extractors/prints.py | 5 ++++- deal/linter/_extractors/returns.py | 5 ++++- deal/linter/_func.py | 3 +++ deal/linter/_rules.py | 6 ++++-- pyproject.toml | 8 +++++++- tests/test_case.py | 9 +++++++-- tests/test_chain.py | 5 ++++- tests/test_decorators/helpers.py | 1 + tests/test_decorators/test_ensure.py | 6 +++++- tests/test_decorators/test_inv.py | 5 ++++- tests/test_decorators/test_offline.py | 6 +++++- tests/test_decorators/test_post.py | 6 +++++- tests/test_decorators/test_pre.py | 6 +++++- tests/test_decorators/test_raises.py | 6 +++++- tests/test_decorators/test_reason.py | 4 ++++ tests/test_decorators/test_silent.py | 6 +++++- tests/test_imports.py | 5 ++++- tests/test_linter/test_checker.py | 2 ++ tests/test_linter/test_cli.py | 3 +++ tests/test_linter/test_contract.py | 6 +++++- tests/test_linter/test_error.py | 1 + tests/test_linter/test_extractors/test_contracts.py | 5 ++++- tests/test_linter/test_extractors/test_exceptions.py | 5 ++++- tests/test_linter/test_extractors/test_imports.py | 5 ++++- tests/test_linter/test_extractors/test_prints.py | 5 ++++- tests/test_linter/test_extractors/test_returns.py | 5 ++++- tests/test_linter/test_func.py | 3 +++ tests/test_linter/test_main.py | 1 + tests/test_linter/test_rules.py | 5 ++++- tests/test_schemes.py | 4 +++- tests/test_state.py | 6 +++++- 45 files changed, 162 insertions(+), 35 deletions(-) diff --git a/deal/__init__.py b/deal/__init__.py index a7ca1b85..3761e473 100644 --- a/deal/__init__.py +++ b/deal/__init__.py @@ -23,12 +23,12 @@ # app from ._aliases import ( chain, ensure, inv, invariant, offline, post, - pre, pure, raises, reason, require, safe, silent, + pre, pure, raises, reason, require, safe, silent ) from ._exceptions import * # noQA +from ._imports import activate, module_load from ._schemes import Scheme from ._state import reset, switch -from ._imports import module_load, activate from ._testing import TestCase, cases diff --git a/deal/_decorators/base.py b/deal/_decorators/base.py index 39ffd449..33ddd982 100644 --- a/deal/_decorators/base.py +++ b/deal/_decorators/base.py @@ -5,6 +5,7 @@ from functools import update_wrapper from typing import Callable +# external import vaa # app diff --git a/deal/_imports.py b/deal/_imports.py index 157fa469..2694a6b3 100644 --- a/deal/_imports.py +++ b/deal/_imports.py @@ -1,13 +1,16 @@ # built-in import ast import sys -from _frozen_importlib_external import PathFinder from types import ModuleType -from typing import Any, Callable, Optional, List +from typing import Any, Callable, List, Optional -from .linter._extractors.common import get_name +# project +from _frozen_importlib_external import PathFinder + +# app from . import _aliases from ._state import state +from .linter._extractors.common import get_name def _enabled(debug: bool = False) -> bool: diff --git a/deal/linter/__init__.py b/deal/linter/__init__.py index 935225e5..a7312adf 100644 --- a/deal/linter/__init__.py +++ b/deal/linter/__init__.py @@ -1,3 +1,4 @@ +# app from ._checker import Checker diff --git a/deal/linter/__main__.py b/deal/linter/__main__.py index bf0deaa9..7f6b0ef9 100644 --- a/deal/linter/__main__.py +++ b/deal/linter/__main__.py @@ -1,6 +1,9 @@ +# built-in import sys +# app from ._cli import main + if __name__ == '__main__': exit(main(sys.argv[1:])) diff --git a/deal/linter/_checker.py b/deal/linter/_checker.py index d41035f8..796d1ced 100644 --- a/deal/linter/_checker.py +++ b/deal/linter/_checker.py @@ -1,10 +1,12 @@ +# built-in import ast import typing from pathlib import Path +# app from ._error import Error from ._func import Func -from ._rules import rules, Required +from ._rules import Required, rules class Checker: diff --git a/deal/linter/_cli.py b/deal/linter/_cli.py index 3e8f3322..eef1aba8 100644 --- a/deal/linter/_cli.py +++ b/deal/linter/_cli.py @@ -1,3 +1,4 @@ +# built-in import ast import json from argparse import ArgumentParser @@ -5,6 +6,7 @@ from textwrap import dedent, indent from typing import Iterable, Iterator, Sequence, Union +# app from ._checker import Checker diff --git a/deal/linter/_contract.py b/deal/linter/_contract.py index 2da4eae8..2f521090 100644 --- a/deal/linter/_contract.py +++ b/deal/linter/_contract.py @@ -1,9 +1,12 @@ +# built-in import ast import builtins import enum +# external import astroid +# app from ._extractors import get_name diff --git a/deal/linter/_error.py b/deal/linter/_error.py index 439f6194..722ce728 100644 --- a/deal/linter/_error.py +++ b/deal/linter/_error.py @@ -1,5 +1,7 @@ +# built-in import typing + ERROR_FORMAT = 'DEAL{code:03d}: {text}' diff --git a/deal/linter/_extractors/__init__.py b/deal/linter/_extractors/__init__.py index 3ed1f351..1afa3f4a 100644 --- a/deal/linter/_extractors/__init__.py +++ b/deal/linter/_extractors/__init__.py @@ -1,3 +1,4 @@ +# app from .common import get_name from .contracts import get_contracts from .exceptions import get_exceptions diff --git a/deal/linter/_extractors/common.py b/deal/linter/_extractors/common.py index e1cfacbc..0e49e920 100644 --- a/deal/linter/_extractors/common.py +++ b/deal/linter/_extractors/common.py @@ -1,7 +1,9 @@ +# built-in import ast from types import SimpleNamespace from typing import Optional +# external import astroid diff --git a/deal/linter/_extractors/contracts.py b/deal/linter/_extractors/contracts.py index e475fdee..9bb4060b 100644 --- a/deal/linter/_extractors/contracts.py +++ b/deal/linter/_extractors/contracts.py @@ -1,8 +1,11 @@ -from typing import Tuple, Iterator +# built-in +from typing import Iterator, Tuple +# external import astroid -from .common import get_name, TOKENS +# app +from .common import TOKENS, get_name SUPPORTED_CONTRACTS = {'deal.post', 'deal.raises', 'deal.silent'} diff --git a/deal/linter/_extractors/exceptions.py b/deal/linter/_extractors/exceptions.py index 43106729..a7a1dfcb 100644 --- a/deal/linter/_extractors/exceptions.py +++ b/deal/linter/_extractors/exceptions.py @@ -1,10 +1,13 @@ +# built-in import ast import builtins from typing import Iterator +# external import astroid -from .common import traverse, Token, TOKENS, get_name +# app +from .common import TOKENS, Token, get_name, traverse def get_exceptions(body: list, *, dive: bool = True) -> Iterator[Token]: diff --git a/deal/linter/_extractors/imports.py b/deal/linter/_extractors/imports.py index f12ca8cc..28b80738 100644 --- a/deal/linter/_extractors/imports.py +++ b/deal/linter/_extractors/imports.py @@ -1,9 +1,12 @@ +# built-in import ast from typing import Iterator +# external import astroid -from .common import traverse, Token +# app +from .common import Token, traverse def get_imports(body: list) -> Iterator[Token]: diff --git a/deal/linter/_extractors/prints.py b/deal/linter/_extractors/prints.py index 59953b6b..583d4695 100644 --- a/deal/linter/_extractors/prints.py +++ b/deal/linter/_extractors/prints.py @@ -1,9 +1,12 @@ +# built-in import ast from typing import Iterator +# external import astroid -from .common import Token, get_name, traverse, TOKENS +# app +from .common import TOKENS, Token, get_name, traverse def get_prints(body: list) -> Iterator[Token]: diff --git a/deal/linter/_extractors/returns.py b/deal/linter/_extractors/returns.py index ee1131ee..e1ffd134 100644 --- a/deal/linter/_extractors/returns.py +++ b/deal/linter/_extractors/returns.py @@ -1,9 +1,12 @@ +# built-in import ast from typing import Iterator +# external import astroid -from .common import traverse, Token, TOKENS +# app +from .common import TOKENS, Token, traverse def get_returns(body: list) -> Iterator[Token]: diff --git a/deal/linter/_func.py b/deal/linter/_func.py index ac8a2a6a..50c67ba0 100644 --- a/deal/linter/_func.py +++ b/deal/linter/_func.py @@ -1,9 +1,12 @@ +# built-in import ast from pathlib import Path from typing import Iterable, List +# external import astroid +# app from ._contract import Category, Contract from ._extractors import get_contracts diff --git a/deal/linter/_rules.py b/deal/linter/_rules.py index e1a79a57..78aaecb2 100644 --- a/deal/linter/_rules.py +++ b/deal/linter/_rules.py @@ -1,10 +1,12 @@ +# built-in import enum from typing import Iterator +# app +from ._contract import Category, Contract from ._error import Error -from ._extractors import get_exceptions, get_returns, get_imports, get_prints +from ._extractors import get_exceptions, get_imports, get_prints, get_returns from ._func import Func -from ._contract import Category, Contract rules = [] diff --git a/pyproject.toml b/pyproject.toml index 6436c623..88cdb2db 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,6 +25,11 @@ from = {format = "poetry", path = "pyproject.toml"} envs = ["main", "tests"] command = "mypy --ignore-missing-imports --allow-redefinition deal/" +[tool.dephell.isort] +from = {format = "poetry", path = "pyproject.toml"} +envs = ["main", "tests"] +command = "isort -rc -y ." + [tool.flakehell.plugins] deal = ["+*"] @@ -77,6 +82,7 @@ vaa = ">=0.2.1" [tool.poetry.dev-dependencies] # tests coverage = "*" +isort = {version = "*", extras = ["pyproject"]} marshmallow = "*" mypy = "*" pytest = "*" @@ -90,7 +96,7 @@ sphinx = "*" sphinx-rtd-theme = "*" [tool.poetry.extras] -tests = ["coverage", "marshmallow", "mypy", "pytest", "pytest-cov", "urllib3"] +tests = ["coverage", "isort", "marshmallow", "mypy", "pytest", "pytest-cov", "urllib3"] docs = ["m2r", "recommonmark", "sphinx", "sphinx-rtd-theme", "urllib3"] [tool.poetry.plugins."flake8.extension"] diff --git a/tests/test_case.py b/tests/test_case.py index 47fe7736..ca86f729 100644 --- a/tests/test_case.py +++ b/tests/test_case.py @@ -1,7 +1,12 @@ -import deal -import pytest +# built-in from typing import NoReturn +# external +import pytest + +# project +import deal + @deal.raises(ZeroDivisionError) @deal.pre(lambda a, b: a > 0) diff --git a/tests/test_chain.py b/tests/test_chain.py index f03356ab..7208e011 100644 --- a/tests/test_chain.py +++ b/tests/test_chain.py @@ -1,7 +1,10 @@ -import deal +# external import pytest import urllib3 +# project +import deal + def test_chained_contract_decorator(): diff --git a/tests/test_decorators/helpers.py b/tests/test_decorators/helpers.py index c24c8d62..bce6df34 100644 --- a/tests/test_decorators/helpers.py +++ b/tests/test_decorators/helpers.py @@ -1,3 +1,4 @@ +# built-in import asyncio diff --git a/tests/test_decorators/test_ensure.py b/tests/test_decorators/test_ensure.py index 22576bbf..137ca7c9 100644 --- a/tests/test_decorators/test_ensure.py +++ b/tests/test_decorators/test_ensure.py @@ -1,6 +1,10 @@ -import deal +# external import pytest +# project +import deal + +# app from .helpers import run_sync diff --git a/tests/test_decorators/test_inv.py b/tests/test_decorators/test_inv.py index 4e027555..cde77429 100644 --- a/tests/test_decorators/test_inv.py +++ b/tests/test_decorators/test_inv.py @@ -1,6 +1,9 @@ -import deal +# external import pytest +# project +import deal + def test_setting_object_attribute_fulfills_contract(): @deal.inv(lambda obj: obj.x > 0) diff --git a/tests/test_decorators/test_offline.py b/tests/test_decorators/test_offline.py index f035f97d..2c7dd19a 100644 --- a/tests/test_decorators/test_offline.py +++ b/tests/test_decorators/test_offline.py @@ -1,7 +1,11 @@ -import deal +# external import pytest import urllib3 +# project +import deal + +# app from .helpers import run_sync diff --git a/tests/test_decorators/test_post.py b/tests/test_decorators/test_post.py index bdcfe611..45d1a177 100644 --- a/tests/test_decorators/test_post.py +++ b/tests/test_decorators/test_post.py @@ -1,6 +1,10 @@ -import deal +# external import pytest +# project +import deal + +# app from .helpers import run_sync diff --git a/tests/test_decorators/test_pre.py b/tests/test_decorators/test_pre.py index 709dc85c..f4627c74 100644 --- a/tests/test_decorators/test_pre.py +++ b/tests/test_decorators/test_pre.py @@ -1,6 +1,10 @@ -import deal +# external import pytest +# project +import deal + +# app from .helpers import run_sync diff --git a/tests/test_decorators/test_raises.py b/tests/test_decorators/test_raises.py index d91ae9f6..d53348a0 100644 --- a/tests/test_decorators/test_raises.py +++ b/tests/test_decorators/test_raises.py @@ -1,7 +1,11 @@ -import deal +# external import pytest import urllib3 +# project +import deal + +# app from .helpers import run_sync diff --git a/tests/test_decorators/test_reason.py b/tests/test_decorators/test_reason.py index 8e27b1fe..48907029 100644 --- a/tests/test_decorators/test_reason.py +++ b/tests/test_decorators/test_reason.py @@ -1,6 +1,10 @@ +# external import pytest + +# project import deal +# app from .helpers import run_sync diff --git a/tests/test_decorators/test_silent.py b/tests/test_decorators/test_silent.py index 9994d73e..23f1d95e 100644 --- a/tests/test_decorators/test_silent.py +++ b/tests/test_decorators/test_silent.py @@ -1,6 +1,10 @@ -import deal +# external import pytest +# project +import deal + +# app from .helpers import run_sync diff --git a/tests/test_imports.py b/tests/test_imports.py index faa1167f..f46ebe03 100644 --- a/tests/test_imports.py +++ b/tests/test_imports.py @@ -1,12 +1,15 @@ +# built-in import ast from pathlib import Path from textwrap import dedent +# external import pytest +# project import deal -from deal.linter._extractors.common import get_name from deal._imports import DealLoader, deactivate +from deal.linter._extractors.common import get_name def test_get_contracts(): diff --git a/tests/test_linter/test_checker.py b/tests/test_linter/test_checker.py index 2bc11d39..5fa18c2f 100644 --- a/tests/test_linter/test_checker.py +++ b/tests/test_linter/test_checker.py @@ -1,6 +1,8 @@ +# built-in import ast from pathlib import Path +# project from deal.linter import Checker diff --git a/tests/test_linter/test_cli.py b/tests/test_linter/test_cli.py index dd15014f..5286cf0b 100644 --- a/tests/test_linter/test_cli.py +++ b/tests/test_linter/test_cli.py @@ -1,7 +1,10 @@ +# built-in from pathlib import Path +# external import pytest +# project from deal.linter._cli import get_errors, get_paths, main diff --git a/tests/test_linter/test_contract.py b/tests/test_linter/test_contract.py index 15a16960..fbcab89f 100644 --- a/tests/test_linter/test_contract.py +++ b/tests/test_linter/test_contract.py @@ -1,11 +1,15 @@ +# built-in import ast from textwrap import dedent +# external import astroid -from deal.linter._contract import Contract, Category +# project +from deal.linter._contract import Category, Contract from deal.linter._func import Func + TEXT = """ import deal diff --git a/tests/test_linter/test_error.py b/tests/test_linter/test_error.py index 691627cb..2264a2f4 100644 --- a/tests/test_linter/test_error.py +++ b/tests/test_linter/test_error.py @@ -1,3 +1,4 @@ +# project from deal.linter._error import Error diff --git a/tests/test_linter/test_extractors/test_contracts.py b/tests/test_linter/test_extractors/test_contracts.py index faf50bfb..4ad0e582 100644 --- a/tests/test_linter/test_extractors/test_contracts.py +++ b/tests/test_linter/test_extractors/test_contracts.py @@ -1,9 +1,12 @@ +# built-in import ast -import astroid from textwrap import dedent +# external +import astroid import pytest +# project from deal.linter._extractors import get_contracts diff --git a/tests/test_linter/test_extractors/test_exceptions.py b/tests/test_linter/test_extractors/test_exceptions.py index 6d2121e0..c45d3ff3 100644 --- a/tests/test_linter/test_extractors/test_exceptions.py +++ b/tests/test_linter/test_extractors/test_exceptions.py @@ -1,9 +1,12 @@ +# built-in import ast -import astroid from textwrap import dedent +# external +import astroid import pytest +# project from deal.linter._extractors import get_exceptions diff --git a/tests/test_linter/test_extractors/test_imports.py b/tests/test_linter/test_extractors/test_imports.py index b7631295..0bb7ebb3 100644 --- a/tests/test_linter/test_extractors/test_imports.py +++ b/tests/test_linter/test_extractors/test_imports.py @@ -1,8 +1,11 @@ +# built-in import ast -import astroid +# external +import astroid import pytest +# project from deal.linter._extractors import get_imports diff --git a/tests/test_linter/test_extractors/test_prints.py b/tests/test_linter/test_extractors/test_prints.py index f5e3eece..10b0b148 100644 --- a/tests/test_linter/test_extractors/test_prints.py +++ b/tests/test_linter/test_extractors/test_prints.py @@ -1,8 +1,11 @@ +# built-in import ast -import astroid +# external +import astroid import pytest +# project from deal.linter._extractors import get_prints diff --git a/tests/test_linter/test_extractors/test_returns.py b/tests/test_linter/test_extractors/test_returns.py index ef90f851..35db98e0 100644 --- a/tests/test_linter/test_extractors/test_returns.py +++ b/tests/test_linter/test_extractors/test_returns.py @@ -1,8 +1,11 @@ +# built-in import ast -import astroid +# external +import astroid import pytest +# project from deal.linter._extractors import get_returns diff --git a/tests/test_linter/test_func.py b/tests/test_linter/test_func.py index afa62b21..cc6fc9d3 100644 --- a/tests/test_linter/test_func.py +++ b/tests/test_linter/test_func.py @@ -1,7 +1,10 @@ +# built-in import ast +# external import astroid +# project from deal.linter._func import Func diff --git a/tests/test_linter/test_main.py b/tests/test_linter/test_main.py index 1ec9c132..ff5cad54 100644 --- a/tests/test_linter/test_main.py +++ b/tests/test_linter/test_main.py @@ -1,3 +1,4 @@ +# built-in import subprocess import sys diff --git a/tests/test_linter/test_rules.py b/tests/test_linter/test_rules.py index ab1ac9bc..c091e8a6 100644 --- a/tests/test_linter/test_rules.py +++ b/tests/test_linter/test_rules.py @@ -1,10 +1,13 @@ +# built-in import ast from textwrap import dedent +# external import astroid +# project from deal.linter._func import Func -from deal.linter._rules import CheckRaises, CheckReturns, CheckImports, CheckPrints +from deal.linter._rules import CheckImports, CheckPrints, CheckRaises, CheckReturns def test_check_returns(): diff --git a/tests/test_schemes.py b/tests/test_schemes.py index aa284d63..917ed83d 100644 --- a/tests/test_schemes.py +++ b/tests/test_schemes.py @@ -1,8 +1,10 @@ +# external import marshmallow +import pytest import vaa +# project import deal -import pytest @vaa.marshmallow diff --git a/tests/test_state.py b/tests/test_state.py index a22b981b..e28f17f2 100644 --- a/tests/test_state.py +++ b/tests/test_state.py @@ -1,7 +1,11 @@ -import deal +# external import pytest + +# project +import deal from deal._imports import deactivate +# app from .test_decorators.helpers import run_sync From 8257831d331601d9a77d0ea04255448bedf776a3 Mon Sep 17 00:00:00 2001 From: Gram Date: Mon, 20 Apr 2020 20:00:00 +0200 Subject: [PATCH 06/13] do fallback on ast from astroid --- deal/linter/_checker.py | 15 ++++++++++----- deal/linter/_rules.py | 3 ++- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/deal/linter/_checker.py b/deal/linter/_checker.py index 796d1ced..ad508ac5 100644 --- a/deal/linter/_checker.py +++ b/deal/linter/_checker.py @@ -3,6 +3,8 @@ import typing from pathlib import Path +from astroid import AstroidSyntaxError + # app from ._error import Error from ._func import Func @@ -23,13 +25,16 @@ def run(self) -> typing.Iterator[tuple]: for error in self.get_errors(): yield tuple(error) + (type(self),) # type: ignore - def get_errors(self) -> typing.Iterator[Error]: + def get_funcs(self) -> typing.List['Func']: if self._filename == 'stdin': - funcs = Func.from_ast(tree=self._tree) - else: - funcs = Func.from_path(path=Path(self._filename)) + return Func.from_ast(tree=self._tree) + try: + return Func.from_path(path=Path(self._filename)) + except AstroidSyntaxError: + return Func.from_ast(tree=self._tree) - for func in funcs: + def get_errors(self) -> typing.Iterator[Error]: + for func in self.get_funcs(): for rule in self._rules: if rule.required != Required.FUNC: continue diff --git a/deal/linter/_rules.py b/deal/linter/_rules.py index 78aaecb2..a5a6716f 100644 --- a/deal/linter/_rules.py +++ b/deal/linter/_rules.py @@ -1,4 +1,5 @@ # built-in +import ast import enum from typing import Iterator @@ -28,7 +29,7 @@ class CheckImports: message = 'do not use `from deal import ...`, use `import deal` instead' required = Required.MODULE - def __call__(self, tree) -> Iterator[Error]: + def __call__(self, tree: ast.Module) -> Iterator[Error]: for token in get_imports(tree.body): if token.value != 'deal': continue From b5f5c00647b7b336ee87055c455ff9b574b2f6d7 Mon Sep 17 00:00:00 2001 From: Gram Date: Mon, 20 Apr 2020 20:02:53 +0200 Subject: [PATCH 07/13] provide actual plugin version --- deal/linter/_checker.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/deal/linter/_checker.py b/deal/linter/_checker.py index ad508ac5..93ee7b36 100644 --- a/deal/linter/_checker.py +++ b/deal/linter/_checker.py @@ -13,14 +13,18 @@ class Checker: name = 'deal' - version = '1.0.0' - _tree = None # type: ast.Module _rules = rules def __init__(self, tree: ast.Module, file_tokens=None, filename: str = 'stdin'): self._tree = tree self._filename = filename + @property + def version(self): + import deal + + return deal.__version__ + def run(self) -> typing.Iterator[tuple]: for error in self.get_errors(): yield tuple(error) + (type(self),) # type: ignore From a5be101230e7ac01dff7e4fea028bd053bc275e9 Mon Sep 17 00:00:00 2001 From: Gram Date: Mon, 20 Apr 2020 20:31:00 +0200 Subject: [PATCH 08/13] test get_name --- .../test_extractors/test_common.py | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 tests/test_linter/test_extractors/test_common.py diff --git a/tests/test_linter/test_extractors/test_common.py b/tests/test_linter/test_extractors/test_common.py new file mode 100644 index 00000000..0483bad0 --- /dev/null +++ b/tests/test_linter/test_extractors/test_common.py @@ -0,0 +1,27 @@ +# built-in +import ast + +# external +import astroid +import pytest + +# project +from deal.linter._extractors.common import get_name + + +@pytest.mark.parametrize('text, expected', [ + ('name', 'name'), + ('left.right', 'left.right'), + + ('left().right', None), +]) +def test_get_name(text, expected): + tree = ast.parse(text) + print(ast.dump(tree)) + expr = tree.body[0].value + assert get_name(expr=expr) == expected + + tree = astroid.parse(text) + print(tree.repr_tree()) + expr = tree.body[0].value + assert get_name(expr=expr) == expected From 714d4b2432a0720c20758b93c3ade5a64479d45a Mon Sep 17 00:00:00 2001 From: Gram Date: Mon, 20 Apr 2020 20:31:08 +0200 Subject: [PATCH 09/13] test version --- tests/test_linter/test_checker.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/test_linter/test_checker.py b/tests/test_linter/test_checker.py index 5fa18c2f..f5528da7 100644 --- a/tests/test_linter/test_checker.py +++ b/tests/test_linter/test_checker.py @@ -45,3 +45,8 @@ def test_astroid_path(tmp_path: Path): (13, 10, 'DEAL012: raises contract error (KeyError)', Checker), ] assert errors == expected + + +def test_version(): + version = Checker(tree=None, filename='stdin').version + assert not set(version) - set('0123456789.') From 1a89e95864ce7e7ad6ee5f8dd18f8c3adfb777e7 Mon Sep 17 00:00:00 2001 From: Gram Date: Mon, 20 Apr 2020 20:31:19 +0200 Subject: [PATCH 10/13] test tricky raise --- tests/test_linter/test_extractors/test_exceptions.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_linter/test_extractors/test_exceptions.py b/tests/test_linter/test_extractors/test_exceptions.py index c45d3ff3..a801cf74 100644 --- a/tests/test_linter/test_extractors/test_exceptions.py +++ b/tests/test_linter/test_extractors/test_exceptions.py @@ -16,6 +16,7 @@ ('raise UnknownError', ('UnknownError', )), ('raise ValueError("lol")', (ValueError, )), ('raise unknown()', ()), + ('raise 1 + 2', ()), ('assert False', (AssertionError, )), ('12 / 0', (ZeroDivisionError, )), ('exit(13)', (SystemExit, )), From 9098f4228c5c652f2bd5d192a67622535af0dd8e Mon Sep 17 00:00:00 2001 From: Gram Date: Tue, 21 Apr 2020 11:58:11 +0200 Subject: [PATCH 11/13] test invalid syntax --- tests/test_linter/test_checker.py | 33 ++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/tests/test_linter/test_checker.py b/tests/test_linter/test_checker.py index f5528da7..e760c3a6 100644 --- a/tests/test_linter/test_checker.py +++ b/tests/test_linter/test_checker.py @@ -22,16 +22,17 @@ def test2(): raise KeyError """.strip() +EXPECTED = [ + (6, 11, 'DEAL011: post contract error (-1)', Checker), + (11, 8, 'DEAL012: raises contract error (ZeroDivisionError)', Checker), + (13, 10, 'DEAL012: raises contract error (KeyError)', Checker), +] + def test_stdin(): checker = Checker(tree=ast.parse(TEXT)) errors = list(checker.run()) - expected = [ - (6, 11, 'DEAL011: post contract error (-1)', Checker), - (11, 8, 'DEAL012: raises contract error (ZeroDivisionError)', Checker), - (13, 10, 'DEAL012: raises contract error (KeyError)', Checker), - ] - assert errors == expected + assert errors == EXPECTED def test_astroid_path(tmp_path: Path): @@ -39,12 +40,20 @@ def test_astroid_path(tmp_path: Path): path.write_text(TEXT) checker = Checker(tree=ast.parse(TEXT), filename=str(path)) errors = list(checker.run()) - expected = [ - (6, 11, 'DEAL011: post contract error (-1)', Checker), - (11, 8, 'DEAL012: raises contract error (ZeroDivisionError)', Checker), - (13, 10, 'DEAL012: raises contract error (KeyError)', Checker), - ] - assert errors == expected + assert errors == EXPECTED + + +def test_get_funcs_invalid_syntax(tmp_path: Path): + """ + Atom IDE flake8 plugin can call flake8 with AST with correct syntax but with path + to code with invalid syntax. In that case, we should ignore the file and fallback + to the passed AST. + """ + path = tmp_path / 'test.py' + path.write_text('1/') + checker = Checker(tree=ast.parse(TEXT), filename=str(path)) + errors = list(checker.run()) + assert errors == EXPECTED def test_version(): From 1997064d89f93343ad93e419e65b65090af7c6a1 Mon Sep 17 00:00:00 2001 From: Gram Date: Tue, 21 Apr 2020 11:58:45 +0200 Subject: [PATCH 12/13] test typing on ci --- .travis.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.travis.yml b/.travis.yml index 73640e54..e6a346c5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -41,3 +41,8 @@ matrix: env: ENV=flake8 script: - dephell venv run --env=$ENV + + - python: "3.7" + env: ENV=typing + script: + - dephell venv run --env=$ENV From 94e208fa3a7b45353662953a92685be6500dd6c5 Mon Sep 17 00:00:00 2001 From: Gram Date: Tue, 21 Apr 2020 12:08:07 +0200 Subject: [PATCH 13/13] fix isort broken flake8 --- deal/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deal/__init__.py b/deal/__init__.py index 3761e473..d36f57fa 100644 --- a/deal/__init__.py +++ b/deal/__init__.py @@ -23,7 +23,7 @@ # app from ._aliases import ( chain, ensure, inv, invariant, offline, post, - pre, pure, raises, reason, require, safe, silent + pre, pure, raises, reason, require, safe, silent, ) from ._exceptions import * # noQA from ._imports import activate, module_load @@ -46,7 +46,6 @@ 'offline', 'post', 'pre', - 'pure', 'raises', 'reason', 'safe', @@ -54,6 +53,7 @@ # aliases 'invariant', + 'pure', 'require', # module level