Skip to content

Commit 77b1a81

Browse files
authored
Move plugin code into plugin.py (#3)
1 parent 165b4eb commit 77b1a81

File tree

3 files changed

+67
-65
lines changed

3 files changed

+67
-65
lines changed

pynamodb_mypy/__init__.py

Lines changed: 1 addition & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,6 @@
1-
from typing import Callable
2-
from typing import Optional
31
from typing import Type
42

5-
import mypy.types
6-
from mypy.nodes import NameExpr
7-
from mypy.nodes import TypeInfo
8-
from mypy.plugin import FunctionContext
9-
from mypy.plugin import Plugin
10-
11-
ATTR_FULL_NAME = 'pynamodb.attributes.Attribute'
12-
NULL_ATTR_WRAPPER_FULL_NAME = 'pynamodb.attributes._NullableAttributeWrapper'
13-
14-
15-
class PynamodbPlugin(Plugin):
16-
@staticmethod
17-
def _get_attribute_underlying_type(attribute_class: TypeInfo) -> Optional[mypy.types.Type]:
18-
"""
19-
e.g. for `class MyAttribute(Attribute[int])`, this will return `int`.
20-
"""
21-
for base_instance in attribute_class.bases:
22-
if base_instance.type.fullname() == ATTR_FULL_NAME:
23-
return base_instance.args[0]
24-
return None
25-
26-
def get_function_hook(self, fullname: str) -> Optional[Callable[[FunctionContext], mypy.types.Type]]:
27-
symbol_table_node = self.lookup_fully_qualified(fullname)
28-
if not symbol_table_node:
29-
return None
30-
31-
if isinstance(symbol_table_node.node, TypeInfo):
32-
underlying_type = self._get_attribute_underlying_type(symbol_table_node.node)
33-
if underlying_type:
34-
_underlying_type = underlying_type # https://github.com/python/mypy/issues/4297
35-
return lambda ctx: _attribute_instantiation_hook(ctx, _underlying_type)
36-
37-
return None
38-
39-
40-
def _attribute_instantiation_hook(ctx: FunctionContext,
41-
underlying_type: mypy.types.Type) -> mypy.types.Type:
42-
"""
43-
Handles attribute instantiation, e.g. MyAttribute(null=True)
44-
"""
45-
args = dict(zip(ctx.callee_arg_names, ctx.args))
46-
47-
# If initializer is passed null=True, wrap in _NullableAttribute
48-
# to make the underlying type optional
49-
null_arg_exprs = args.get('null')
50-
if null_arg_exprs and len(null_arg_exprs) == 1:
51-
(null_arg_expr,) = null_arg_exprs
52-
if (
53-
not isinstance(null_arg_expr, NameExpr) or
54-
null_arg_expr.fullname not in ('builtins.False', 'builtins.True')
55-
):
56-
ctx.api.fail("'null' argument is not constant False or True, "
57-
"cannot deduce optionality", ctx.context)
58-
return ctx.default_return_type
59-
60-
if null_arg_expr.fullname == 'builtins.True':
61-
return ctx.api.named_generic_type(NULL_ATTR_WRAPPER_FULL_NAME, [
62-
ctx.default_return_type,
63-
underlying_type,
64-
])
65-
66-
return ctx.default_return_type
3+
from .plugin import PynamodbPlugin
674

685

696
def plugin(version: str) -> Type[PynamodbPlugin]:

pynamodb_mypy/plugin.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
from typing import Callable
2+
from typing import Optional
3+
4+
import mypy.types
5+
from mypy.nodes import NameExpr
6+
from mypy.nodes import TypeInfo
7+
from mypy.plugin import FunctionContext
8+
from mypy.plugin import Plugin
9+
10+
ATTR_FULL_NAME = 'pynamodb.attributes.Attribute'
11+
NULL_ATTR_WRAPPER_FULL_NAME = 'pynamodb.attributes._NullableAttributeWrapper'
12+
13+
14+
class PynamodbPlugin(Plugin):
15+
@staticmethod
16+
def _get_attribute_underlying_type(attribute_class: TypeInfo) -> Optional[mypy.types.Type]:
17+
"""
18+
e.g. for `class MyAttribute(Attribute[int])`, this will return `int`.
19+
"""
20+
for base_instance in attribute_class.bases:
21+
if base_instance.type.fullname() == ATTR_FULL_NAME:
22+
return base_instance.args[0]
23+
return None
24+
25+
def get_function_hook(self, fullname: str) -> Optional[Callable[[FunctionContext], mypy.types.Type]]:
26+
symbol_table_node = self.lookup_fully_qualified(fullname)
27+
if not symbol_table_node:
28+
return None
29+
30+
if isinstance(symbol_table_node.node, TypeInfo):
31+
underlying_type = self._get_attribute_underlying_type(symbol_table_node.node)
32+
if underlying_type:
33+
_underlying_type = underlying_type # https://github.com/python/mypy/issues/4297
34+
return lambda ctx: _attribute_instantiation_hook(ctx, _underlying_type)
35+
36+
return None
37+
38+
39+
def _attribute_instantiation_hook(ctx: FunctionContext,
40+
underlying_type: mypy.types.Type) -> mypy.types.Type:
41+
"""
42+
Handles attribute instantiation, e.g. MyAttribute(null=True)
43+
"""
44+
args = dict(zip(ctx.callee_arg_names, ctx.args))
45+
46+
# If initializer is passed null=True, wrap in _NullableAttribute
47+
# to make the underlying type optional
48+
null_arg_exprs = args.get('null')
49+
if null_arg_exprs and len(null_arg_exprs) == 1:
50+
(null_arg_expr,) = null_arg_exprs
51+
if (
52+
not isinstance(null_arg_expr, NameExpr) or
53+
null_arg_expr.fullname not in ('builtins.False', 'builtins.True')
54+
):
55+
ctx.api.fail("'null' argument is not constant False or True, "
56+
"cannot deduce optionality", ctx.context)
57+
return ctx.default_return_type
58+
59+
if null_arg_expr.fullname == 'builtins.True':
60+
return ctx.api.named_generic_type(NULL_ATTR_WRAPPER_FULL_NAME, [
61+
ctx.default_return_type,
62+
underlying_type,
63+
])
64+
65+
return ctx.default_return_type

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@
44
# TODO: Remove this line after https://github.com/pynamodb/PynamoDB/pull/579 is released
55
-e git+git://github.com/ikonst/pynamodb.git@1efc2abd42afc5c759655a0abde8b823aeb18a1c#egg=pynamodb
66
pre-commit
7-
pytest
7+
pytest>=4.1
88
pytest-cov
99
pytest-mock

0 commit comments

Comments
 (0)