Skip to content

Commit 6d6a8be

Browse files
authored
Fix type errors when deriving from a MapAttribute and another type (#904)
1 parent aab050d commit 6d6a8be

File tree

6 files changed

+40
-17
lines changed

6 files changed

+40
-17
lines changed

docs/release_notes.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
Release Notes
22
=============
33

4+
v5.0.1
5+
----------
6+
7+
:date: 2021-02-10
8+
9+
* Fix type errors when deriving from a MapAttribute and another type (#904)
10+
11+
412
v5.0.0
513
----------
614

mypy.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ warn_unused_configs = True
88
warn_redundant_casts = True
99
warn_incomplete_stub = True
1010
follow_imports = normal
11+
show_error_codes = True
1112

1213
# TODO: burn these down
1314
[mypy-tests.*]

pynamodb/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@
77
"""
88
__author__ = 'Jharrod LaFon'
99
__license__ = 'MIT'
10-
__version__ = '5.0.0'
10+
__version__ = '5.0.1'

pynamodb/attributes.py

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,7 @@ def _set_attributes(self, **attributes: Attribute) -> None:
339339
raise ValueError("Attribute {} specified does not exist".format(attr_name))
340340
setattr(self, attr_name, attr_value)
341341

342-
def serialize(self, null_check=True) -> Dict[str, Dict[str, Any]]:
342+
def _container_serialize(self, null_check: bool = True) -> Dict[str, Dict[str, Any]]:
343343
"""
344344
Serialize attribute values for DynamoDB
345345
"""
@@ -357,7 +357,7 @@ def serialize(self, null_check=True) -> Dict[str, Dict[str, Any]]:
357357
attribute_values[attr.attr_name] = {attr.attr_type: attr_value}
358358
return attribute_values
359359

360-
def deserialize(self, attribute_values: Dict[str, Dict[str, Any]]) -> None:
360+
def _container_deserialize(self, attribute_values: Dict[str, Dict[str, Any]]) -> None:
361361
"""
362362
Sets attributes sent back from DynamoDB on this object
363363
"""
@@ -387,17 +387,9 @@ def _instantiate(cls: Type[_ACT], attribute_values: Dict[str, Dict[str, Any]]) -
387387
raise ValueError("Cannot instantiate a {} from the returned class: {}".format(
388388
cls.__name__, stored_cls.__name__))
389389
instance = (stored_cls or cls)(_user_instantiated=False)
390-
AttributeContainer.deserialize(instance, attribute_values)
390+
AttributeContainer._container_deserialize(instance, attribute_values)
391391
return instance
392392

393-
def __eq__(self, other: Any) -> bool:
394-
# This is required so that MapAttribute can call this method.
395-
return self is other
396-
397-
def __ne__(self, other: Any) -> bool:
398-
# This is required so that MapAttribute can call this method.
399-
return self is not other
400-
401393

402394
class DiscriminatorAttribute(Attribute[type]):
403395
attr_type = STRING
@@ -835,14 +827,14 @@ def _update_attribute_paths(self, path_segment):
835827
if isinstance(local_attr, MapAttribute):
836828
local_attr._update_attribute_paths(path_segment)
837829

838-
def __eq__(self, other):
830+
def __eq__(self, other: Any) -> 'Comparison': # type: ignore[override]
839831
if self._is_attribute_container():
840-
return AttributeContainer.__eq__(self, other)
832+
return self is other # type: ignore
841833
return Attribute.__eq__(self, other)
842834

843-
def __ne__(self, other):
835+
def __ne__(self, other: Any) -> 'Comparison': # type: ignore[override]
844836
if self._is_attribute_container():
845-
return AttributeContainer.__ne__(self, other)
837+
return self is not other # type: ignore
846838
return Attribute.__ne__(self, other)
847839

848840
def __iter__(self):
@@ -940,7 +932,7 @@ def serialize(self, values):
940932
setattr(instance, name, values[name])
941933
values = instance
942934

943-
return AttributeContainer.serialize(values)
935+
return AttributeContainer._container_serialize(values)
944936

945937
# Continue to serialize NULL values in "raw" map attributes for backwards compatibility.
946938
# This special case behavior for "raw" attributes should be removed in the future.

pynamodb/models.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1120,6 +1120,18 @@ def _serialize_keys(cls, hash_key, range_key=None) -> Tuple[_KeyType, _KeyType]:
11201120
range_key = cls._range_key_attribute().serialize(range_key)
11211121
return hash_key, range_key
11221122

1123+
def serialize(self, null_check: bool = True) -> Dict[str, Dict[str, Any]]:
1124+
"""
1125+
Serialize attribute values for DynamoDB
1126+
"""
1127+
return self._container_serialize(null_check=null_check)
1128+
1129+
def deserialize(self, attribute_values: Dict[str, Dict[str, Any]]) -> None:
1130+
"""
1131+
Sets attributes sent back from DynamoDB on this object
1132+
"""
1133+
return self._container_deserialize(attribute_values=attribute_values)
1134+
11231135

11241136
class _ModelFuture(Generic[_T]):
11251137
"""

tests/test_mypy.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ class MyModel(Model):
160160
reveal_type(MyModel.my_list[0] == MyModel()) # N: Revealed type is 'pynamodb.expressions.condition.Comparison'
161161
# the following string indexing is not type checked - not by mypy nor in runtime
162162
reveal_type(MyModel.my_list[0]['my_sub_attr'] == 'foobar') # N: Revealed type is 'pynamodb.expressions.condition.Comparison'
163+
reveal_type(MyModel.my_map == 'foobar') # N: Revealed type is 'pynamodb.expressions.condition.Comparison'
163164
""")
164165

165166

@@ -203,3 +204,12 @@ class MyModel(Model):
203204
model = next(typed_result)
204205
not_model = next(typed_result) # E: Incompatible types in assignment (expression has type "MyModel", variable has type "int") [assignment]
205206
""")
207+
208+
209+
def test_map_attribute_derivation(assert_mypy_output):
210+
assert_mypy_output("""
211+
from pynamodb.attributes import MapAttribute
212+
213+
class MyMap(MapAttribute, object):
214+
pass
215+
""")

0 commit comments

Comments
 (0)