Skip to content

Commit f4158af

Browse files
authored
Merge pull request #3 from devinaconley/type_0.0.3
Support for nested field types
2 parents 2b5fa49 + 38d8ded commit f4158af

File tree

5 files changed

+142
-8
lines changed

5 files changed

+142
-8
lines changed

objectfactory/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@
66
from .factory import Factory
77
from .field import Nested, List
88

9-
__version__ = '0.0.2'
9+
__version__ = '0.0.3'

objectfactory/field.py

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,20 @@ def deserialize_field( self, instance, value ):
4141
"""
4242
if value is None:
4343
return
44-
obj = Factory.create_object( value )
44+
45+
if '_type' in value:
46+
obj = Factory.create_object( value )
47+
if self._field_type and not isinstance( obj, self._field_type ):
48+
raise ValueError(
49+
'{} is not an instance of type: {}'.format(
50+
type( obj ).__name__, self._field_type.__name__ )
51+
)
52+
elif self._field_type:
53+
obj = self._field_type()
54+
obj.deserialize( value )
55+
else:
56+
raise ValueError( 'Cannot infer type information' )
57+
4558
setattr( instance, self._key, obj )
4659

4760

@@ -50,10 +63,10 @@ class List( Field ):
5063
field type for list of serializable objects
5164
"""
5265

53-
def __init__( self, default=None ):
66+
def __init__( self, default=None, name=None, field_type=None ):
5467
if default is None:
5568
default = []
56-
super().__init__( default )
69+
super().__init__( default=default, name=name, field_type=field_type )
5770

5871
def serialize_field( self, instance, deserializable=True ):
5972
"""
@@ -82,5 +95,17 @@ def deserialize_field( self, instance, value ):
8295
"""
8396
lst = []
8497
for body in value:
85-
lst.append( Factory.create_object( body ) )
98+
if '_type' in body:
99+
obj = Factory.create_object( body )
100+
if self._field_type and not isinstance( obj, self._field_type ):
101+
raise ValueError(
102+
'{} is not an instance of type: {}'.format(
103+
type( obj ).__name__, self._field_type.__name__ )
104+
)
105+
elif self._field_type:
106+
obj = self._field_type()
107+
obj.deserialize( body )
108+
else:
109+
raise ValueError( 'Cannot infer type information' )
110+
lst.append( obj )
86111
setattr( instance, self._key, lst )

objectfactory/serializable.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,11 @@ class Field( object ):
1616
serializable objects
1717
"""
1818

19-
def __init__( self, default=None, name=None ):
19+
def __init__( self, default=None, name=None, field_type=None ):
2020
self._name = name
2121
self._key = None # note: this will be set from parent metaclass __new__
2222
self._default = default
23+
self._field_type = field_type
2324

2425
def __get__( self, instance, owner ):
2526
return getattr( instance, self._key, self._default )

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
setuptools.setup(
77
name='objectfactory',
8-
version='0.0.2',
8+
version='0.0.3',
99
author='Devin A. Conley',
1010
author_email='devinaconley@gmail.com',
1111
description='A python package for the serializable model / factory pattern',

test/test_fields.py

Lines changed: 109 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
module for testing functionality of serializable fields
33
"""
44

5+
# lib
6+
import pytest
7+
58
# src
69
from objectfactory import Factory, Serializable, Field, Nested, List
710
from .test_serializable import MyBasicClass
@@ -21,7 +24,16 @@ class MyComplexClass( Serializable ):
2124
"""
2225
complex class to test hierarchical serialization
2326
"""
24-
nested = Nested( MyBasicClass )
27+
nested = Nested()
28+
prop = Field()
29+
30+
31+
@Factory.register_class
32+
class MyTypedComplexClass( Serializable ):
33+
"""
34+
complex class to test hierarchical serialization
35+
"""
36+
nested = Nested( field_type=MyBasicClass )
2537
prop = Field()
2638

2739

@@ -34,6 +46,15 @@ class MyOtherComplexClass( Serializable ):
3446
nested_list_prop = List()
3547

3648

49+
@Factory.register_class
50+
class MyOtherTypedComplexClass( Serializable ):
51+
"""
52+
complex class to test list serialization
53+
"""
54+
str_prop = Field()
55+
nested_list_prop = List( field_type=MyBasicClass )
56+
57+
3758
@Factory.register_class
3859
class MyClassWithFieldOptionals( Serializable ):
3960
"""
@@ -98,6 +119,56 @@ def test_deserialize( self ):
98119
assert obj.nested.str_prop == 'random string'
99120
assert obj.nested.int_prop == 4321
100121

122+
def test_deserialize_typed( self ):
123+
"""
124+
test deserialization without _type field
125+
126+
expect nested json to be deserialized into a MyBasicClass object that is
127+
a member of MyTypedComplexClass, even without nested _type field
128+
129+
:return:
130+
"""
131+
body = {
132+
'_type': 'MyComplexClass',
133+
'prop': 'really cool property',
134+
'nested': {
135+
'str_prop': 'random string',
136+
'int_prop': 4321
137+
}
138+
}
139+
140+
obj = MyTypedComplexClass()
141+
obj.deserialize( body )
142+
143+
assert isinstance( obj, MyTypedComplexClass )
144+
assert obj.prop == 'really cool property'
145+
assert isinstance( obj.nested, MyBasicClass )
146+
assert obj.nested.str_prop == 'random string'
147+
assert obj.nested.int_prop == 4321
148+
149+
def test_deserialize_enforce( self ):
150+
"""
151+
test deserialization enforcing field type
152+
153+
expect an error to be thrown on deserialization because the nested
154+
field is of the incorrect type
155+
156+
:return:
157+
"""
158+
body = {
159+
'_type': 'MyComplexClass',
160+
'prop': 'really cool property',
161+
'nested': {
162+
'_type': 'MyBasicClassWithLists',
163+
'str_prop': 'random string',
164+
'int_prop': 4321
165+
}
166+
}
167+
168+
obj = MyTypedComplexClass()
169+
with pytest.raises( ValueError ):
170+
obj.deserialize( body )
171+
101172

102173
class TestPrimitiveList( object ):
103174
"""
@@ -267,6 +338,43 @@ def test_deserialize( self ):
267338
assert nested_obj.str_prop == nested_strings[i]
268339
assert nested_obj.int_prop == nested_ints[i]
269340

341+
def test_deserialize_typed( self ):
342+
"""
343+
test deserialization without _type field
344+
345+
expect list of nested json objects to be deserialized into a list
346+
of MyBasicClass objects that is a member of MyComplexClass, even without
347+
_type field specified
348+
349+
:return:
350+
"""
351+
body = {
352+
'_type': 'MyOtherTypedComplexClass',
353+
'str_prop': 'really great string property',
354+
'nested_list_prop': []
355+
}
356+
nested_strings = ['some string', 'another string', 'one more string']
357+
nested_ints = [101, 102, 103]
358+
359+
for s, n in zip( nested_strings, nested_ints ):
360+
body['nested_list_prop'].append(
361+
{
362+
'str_prop': s,
363+
'int_prop': n
364+
}
365+
)
366+
367+
obj = MyOtherTypedComplexClass()
368+
obj.deserialize( body )
369+
370+
assert isinstance( obj, MyOtherTypedComplexClass )
371+
assert obj.str_prop == 'really great string property'
372+
assert len( obj.nested_list_prop ) == 3
373+
for i, nested_obj in enumerate( obj.nested_list_prop ):
374+
assert isinstance( nested_obj, MyBasicClass )
375+
assert nested_obj.str_prop == nested_strings[i]
376+
assert nested_obj.int_prop == nested_ints[i]
377+
270378

271379
class TestFieldOptionals( object ):
272380
"""

0 commit comments

Comments
 (0)