Skip to content

Commit

Permalink
Do not serialize class properties (#5)
Browse files Browse the repository at this point in the history
  • Loading branch information
BenjaminPelletier authored Jan 11, 2023
1 parent b8ac04e commit e58c91e
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 1 deletion.
1 change: 1 addition & 0 deletions src/implicitdict/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@ def _get_fields(subtype: Type) -> Tuple[Set[str], Set[str]]:
and key not in _DICT_FIELDS
and key[0:2] != '__'
and not callable(getattr(subtype, key))
and not isinstance(getattr(subtype, key), property)
):
all_fields.add(key)
attributes.add(key)
Expand Down
1 change: 0 additions & 1 deletion tests/test_mutability.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import json
from typing import Optional, List

from implicitdict import ImplicitDict
Expand Down
14 changes: 14 additions & 0 deletions tests/test_optional.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,20 @@ def test_fully_defined():
assert "foo5" in s


def test_over_defined():
data = MyData(
required_field="foo1",
optional_field1="foo2",
field_with_default="foo3",
optional_field2_with_none_default="foo4",
optional_field3_with_default="foo5",
unknown_field="foo6",
)
d = json.loads(json.dumps(data))
d["another_unknown_field"] = {"third_unknown_field": "foo7"}
_ = ImplicitDict.parse(d, MyData)


def test_minimally_defined():
# An unspecified optional field will not be present in the object at all
data = MyData(required_field="foo1")
Expand Down
95 changes: 95 additions & 0 deletions tests/test_properties.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import json

from implicitdict import ImplicitDict


class MyData(ImplicitDict):
foo: str

@property
def bar(self) -> str:
return self.foo + 'bar'

def get_baz(self) -> str:
return self.foo + 'baz'

def set_baz(self, value: str) -> None:
self.foo = value

baz = property(get_baz, set_baz)

@property
def booz(self) -> str:
return self.foo + 'booz'

@booz.setter
def booz(self, value: str) -> None:
self.foo = value


def test_property_exclusion():
"""Ensure implicitdict doesn't serialize dynamic properties.
Properties of a class instance are computed dynamically -- they are
methods with syntactic sugar which makes them look like fields. Because we
shouldn't serialize the result of a class method, we also shouldn't
serialize the result of a class property, even if that property includes a
setter.
"""
# Create class instance and ensure it works as expected
data = MyData(foo='foo')
assert data.bar == 'foobar'
assert data.baz == 'foobaz'
assert data.booz == 'foobooz'

# Serialize class instance and ensure the properties weren't serialized
obj = json.loads(json.dumps(data))
assert 'bar' not in obj
assert 'baz' not in obj
assert 'booz' not in obj

# Ensure serialization can be deserialized to class instance
data: MyData = ImplicitDict.parse(obj, MyData)

# Ensure deserialized instance works as expected
assert data.bar == 'foobar'
assert data.baz == 'foobaz'
assert data.booz == 'foobooz'


class MyDict(dict):
@property
def foo(self) -> str:
return 'foo'

def get_bar(self) -> str:
return self.foo + 'bar'

def set_bar(self, value: str) -> None:
self['bar'] = value

bar = property(get_bar, set_bar)

@property
def baz(self) -> str:
return self.foo + 'baz'

@baz.setter
def baz(self, value: str) -> None:
self['baz'] = value


def test_dict_inheritance():
"""Demonstrate that classes inheriting dict do not have their properties serialized."""
data = MyDict()
assert data.foo == 'foo'
assert data.bar == 'foobar'
assert data.baz == 'foobaz'

data['booz'] = 'biz'
deserialized = json.loads(json.dumps(data))

assert 'foo' not in deserialized
assert 'bar' not in deserialized
assert 'baz' not in deserialized
assert deserialized['booz'] == 'biz'

0 comments on commit e58c91e

Please sign in to comment.