-
Notifications
You must be signed in to change notification settings - Fork 25
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
16 changed files
with
262 additions
and
26 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
# built-in | ||
import ast | ||
from typing import Optional | ||
|
||
# external | ||
import astroid | ||
|
||
# app | ||
from .common import TOKENS, Extractor, Token, infer | ||
|
||
|
||
get_asserts = Extractor() | ||
inner_extractor = Extractor() | ||
|
||
|
||
@get_asserts.register(*TOKENS.ASSERT) | ||
def handle_assert(expr) -> Optional[Token]: | ||
handler = inner_extractor.handlers.get(type(expr.test)) | ||
if handler: | ||
token = handler(expr=expr.test) | ||
if token is not None: | ||
return token | ||
|
||
# astroid inference | ||
if hasattr(expr.test, 'infer'): | ||
for value in infer(expr.test): | ||
if not isinstance(value, astroid.Const): | ||
continue | ||
if value.value: | ||
continue | ||
return Token(value=value.value, line=expr.lineno, col=expr.col_offset) | ||
return None | ||
|
||
|
||
# any constant value in astroid | ||
@inner_extractor.register(astroid.Const) | ||
def handle_const(expr: astroid.Const) -> Optional[Token]: | ||
if expr.value: | ||
return None | ||
return Token(value=expr.value, line=expr.lineno, col=expr.col_offset) | ||
|
||
|
||
# Python <3.8 | ||
# string, binary string | ||
@inner_extractor.register(ast.Str, ast.Bytes) | ||
def handle_str(expr) -> Optional[Token]: | ||
if expr.s: | ||
return None | ||
return Token(value=expr.s, line=expr.lineno, col=expr.col_offset) | ||
|
||
|
||
# Python <3.8 | ||
# True, False, None | ||
@inner_extractor.register(ast.NameConstant) | ||
def handle_name_constant(expr: ast.NameConstant) -> Optional[Token]: | ||
if expr.value: | ||
return None | ||
return Token(value=expr.value, line=expr.lineno, col=expr.col_offset) | ||
|
||
|
||
# positive number | ||
@inner_extractor.register(ast.Num, getattr(ast, 'Constant', None)) | ||
def handle_num(expr) -> Optional[Token]: | ||
if expr.n: | ||
return None | ||
return Token(value=expr.n, line=expr.lineno, col=expr.col_offset) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
# **stub**: generate stub files | ||
|
||
When [linter](lint) analyses a function, it checks all called functions inside it, even if these functions have no explicit contracts. For example: | ||
|
||
```python | ||
import deal | ||
|
||
def a(): | ||
raise ValueError | ||
|
||
@deal.raises(NameError) | ||
def b(): | ||
return a() | ||
``` | ||
|
||
Here deal finds and error: | ||
|
||
```bash | ||
$ python3 -m deal lint tmp.py | ||
tmp.py | ||
8:11 raises contract error (ValueError) | ||
return a() | ||
``` | ||
|
||
However, in the next case deal doesn't report anything: | ||
|
||
```python | ||
import deal | ||
|
||
def a(): | ||
raise ValueError | ||
|
||
def b(): | ||
return a() | ||
|
||
@deal.raises(NameError) | ||
def c(): | ||
return b() | ||
``` | ||
|
||
That's because the exception is raised deep inside the call chain. Analyzing function calls too deep would make deal too slow. The solution is to make contracts for everything in your code that you want to be analyzed. However, when it's about third-party libraries where you can't modify the code, stubs come into play. | ||
|
||
Use `stub` command to generate stubs for a Python file: | ||
|
||
```bash | ||
$ python3 -m deal stub /path/to/a/file.py | ||
``` | ||
|
||
The command above will produce `/path/to/a/file.json` stub. On the next runs linter will use it do detect contracts. | ||
|
||
## Built-in stubs | ||
|
||
Deal comes with some pre-generated stubs that are automatically used: | ||
|
||
+ Standard library (CPython 3.7) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
# built-in | ||
import ast | ||
|
||
# external | ||
import astroid | ||
import pytest | ||
|
||
# project | ||
from deal.linter._extractors import get_asserts | ||
|
||
|
||
@pytest.mark.parametrize('text, expected', [ | ||
('assert 1', ()), | ||
('assert True', ()), | ||
('assert "abc"', ()), | ||
('assert "abc", "do not panic"', ()), | ||
('assert 0', (0, )), | ||
('assert False', (False, )), | ||
('assert ""', ("", )), | ||
]) | ||
def test_get_asserts_simple(text, expected): | ||
tree = astroid.parse(text) | ||
print(tree.repr_tree()) | ||
returns = tuple(r.value for r in get_asserts(body=tree.body)) | ||
assert returns == expected | ||
|
||
tree = ast.parse(text) | ||
print(ast.dump(tree)) | ||
returns = tuple(r.value for r in get_asserts(body=tree.body)) | ||
assert returns == expected | ||
|
||
|
||
@pytest.mark.parametrize('text, expected', [ | ||
('assert 3 - 2', ()), | ||
('assert a', ()), # ignore uninferrable names | ||
('a = object()\nassert a', ()), | ||
('assert 2 - 2', (0, )), # do a simple arithmetic | ||
('a = ""\nassert a', ('', )), | ||
]) | ||
def test_get_asserts_inference(text, expected): | ||
tree = astroid.parse(text) | ||
print(tree.repr_tree()) | ||
returns = tuple(r.value for r in get_asserts(body=tree.body)) | ||
assert returns == expected |
Oops, something went wrong.