|
1 | | -from typing import Callable |
2 | | -from typing import Optional |
3 | 1 | from typing import Type |
4 | 2 |
|
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 |
67 | 4 |
|
68 | 5 |
|
69 | 6 | def plugin(version: str) -> Type[PynamodbPlugin]: |
|
0 commit comments