Skip to content

Commit

Permalink
Fix type errors when deriving from a MapAttribute and another type (#904
Browse files Browse the repository at this point in the history
)
  • Loading branch information
ikonst authored Feb 11, 2021
1 parent aab050d commit 6d6a8be
Show file tree
Hide file tree
Showing 6 changed files with 40 additions and 17 deletions.
8 changes: 8 additions & 0 deletions docs/release_notes.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
Release Notes
=============

v5.0.1
----------

:date: 2021-02-10

* Fix type errors when deriving from a MapAttribute and another type (#904)


v5.0.0
----------

Expand Down
1 change: 1 addition & 0 deletions mypy.ini
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ warn_unused_configs = True
warn_redundant_casts = True
warn_incomplete_stub = True
follow_imports = normal
show_error_codes = True

# TODO: burn these down
[mypy-tests.*]
Expand Down
2 changes: 1 addition & 1 deletion pynamodb/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@
"""
__author__ = 'Jharrod LaFon'
__license__ = 'MIT'
__version__ = '5.0.0'
__version__ = '5.0.1'
24 changes: 8 additions & 16 deletions pynamodb/attributes.py
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ def _set_attributes(self, **attributes: Attribute) -> None:
raise ValueError("Attribute {} specified does not exist".format(attr_name))
setattr(self, attr_name, attr_value)

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

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

def __eq__(self, other: Any) -> bool:
# This is required so that MapAttribute can call this method.
return self is other

def __ne__(self, other: Any) -> bool:
# This is required so that MapAttribute can call this method.
return self is not other


class DiscriminatorAttribute(Attribute[type]):
attr_type = STRING
Expand Down Expand Up @@ -835,14 +827,14 @@ def _update_attribute_paths(self, path_segment):
if isinstance(local_attr, MapAttribute):
local_attr._update_attribute_paths(path_segment)

def __eq__(self, other):
def __eq__(self, other: Any) -> 'Comparison': # type: ignore[override]
if self._is_attribute_container():
return AttributeContainer.__eq__(self, other)
return self is other # type: ignore
return Attribute.__eq__(self, other)

def __ne__(self, other):
def __ne__(self, other: Any) -> 'Comparison': # type: ignore[override]
if self._is_attribute_container():
return AttributeContainer.__ne__(self, other)
return self is not other # type: ignore
return Attribute.__ne__(self, other)

def __iter__(self):
Expand Down Expand Up @@ -940,7 +932,7 @@ def serialize(self, values):
setattr(instance, name, values[name])
values = instance

return AttributeContainer.serialize(values)
return AttributeContainer._container_serialize(values)

# Continue to serialize NULL values in "raw" map attributes for backwards compatibility.
# This special case behavior for "raw" attributes should be removed in the future.
Expand Down
12 changes: 12 additions & 0 deletions pynamodb/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -1120,6 +1120,18 @@ def _serialize_keys(cls, hash_key, range_key=None) -> Tuple[_KeyType, _KeyType]:
range_key = cls._range_key_attribute().serialize(range_key)
return hash_key, range_key

def serialize(self, null_check: bool = True) -> Dict[str, Dict[str, Any]]:
"""
Serialize attribute values for DynamoDB
"""
return self._container_serialize(null_check=null_check)

def deserialize(self, attribute_values: Dict[str, Dict[str, Any]]) -> None:
"""
Sets attributes sent back from DynamoDB on this object
"""
return self._container_deserialize(attribute_values=attribute_values)


class _ModelFuture(Generic[_T]):
"""
Expand Down
10 changes: 10 additions & 0 deletions tests/test_mypy.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ class MyModel(Model):
reveal_type(MyModel.my_list[0] == MyModel()) # N: Revealed type is 'pynamodb.expressions.condition.Comparison'
# the following string indexing is not type checked - not by mypy nor in runtime
reveal_type(MyModel.my_list[0]['my_sub_attr'] == 'foobar') # N: Revealed type is 'pynamodb.expressions.condition.Comparison'
reveal_type(MyModel.my_map == 'foobar') # N: Revealed type is 'pynamodb.expressions.condition.Comparison'
""")


Expand Down Expand Up @@ -203,3 +204,12 @@ class MyModel(Model):
model = next(typed_result)
not_model = next(typed_result) # E: Incompatible types in assignment (expression has type "MyModel", variable has type "int") [assignment]
""")


def test_map_attribute_derivation(assert_mypy_output):
assert_mypy_output("""
from pynamodb.attributes import MapAttribute
class MyMap(MapAttribute, object):
pass
""")

0 comments on commit 6d6a8be

Please sign in to comment.