Skip to content
GitLab
Explore
Sign in
Commits on Source (3)
Update git url
· 22c854dc
Rolf van Kleef
authored
Nov 12, 2018
22c854dc
Added support for tuples
· 42e275a9
Rolf van Kleef
authored
Jan 31, 2019
42e275a9
Bump version to 0.0.5
· ea978c6a
Rolf van Kleef
authored
Jan 31, 2019
ea978c6a
Show whitespace changes
Inline
Side-by-side
README.md
View file @
ea978c6a
...
@@ -119,6 +119,7 @@ support some of its types. This is a list of verified composite types:
...
@@ -119,6 +119,7 @@ support some of its types. This is a list of verified composite types:
*
`Union`
(Including
`Optional`
)
*
`Union`
(Including
`Optional`
)
*
`List`
*
`List`
*
`Tuple`
*
`Any`
*
`Any`
*
`dict_deserializer.deserializer.Deserializable`
*
`dict_deserializer.deserializer.Deserializable`
*
`dict`
*
`dict`
...
@@ -134,8 +135,6 @@ It supports these types as terminal types:
...
@@ -134,8 +135,6 @@ It supports these types as terminal types:
## Planned features
## Planned features
*
Tuples
*
Lists will probably deserialize into tuples
*
NamedTuples
*
NamedTuples
*
The anonymous namedtuple and the class-namedtuples with (optionally) type annotations.
*
The anonymous namedtuple and the class-namedtuples with (optionally) type annotations.
*
Dataclasses
*
Dataclasses
...
...
dict_deserializer/__init__.py
View file @
ea978c6a
from
collections
import
namedtuple
from
collections
import
namedtuple
name
=
'
Dictionary deserializer
'
name
=
'
Dictionary deserializer
'
version
=
'
0.0.
4
'
version
=
'
0.0.
5
'
description
=
"
Dictionary deserializer is a package that aides in the
"
\
description
=
"
Dictionary deserializer is a package that aides in the
"
\
"
deserializing of JSON (or other structures) that are
"
\
"
deserializing of JSON (or other structures) that are
"
\
"
converted to dicts, into composite classes.
"
"
converted to dicts, into composite classes.
"
...
...
dict_deserializer/deserializer.py
View file @
ea978c6a
...
@@ -26,7 +26,9 @@ class Rule:
...
@@ -26,7 +26,9 @@ class Rule:
self
.
default
=
default
self
.
default
=
default
def
__repr__
(
self
:
'
Rule
'
):
def
__repr__
(
self
:
'
Rule
'
):
if
self
.
default
:
return
"
Rule(type={}, default={})
"
.
format
(
self
.
type
,
self
.
default
)
return
"
Rule(type={}, default={})
"
.
format
(
self
.
type
,
self
.
default
)
return
"
Rule(type={})
"
.
format
(
self
.
type
)
def
validate
(
self
:
'
Rule
'
,
key
:
str
,
value
):
def
validate
(
self
:
'
Rule
'
,
key
:
str
,
value
):
"""
"""
...
@@ -46,8 +48,10 @@ class DeserializableMeta(type):
...
@@ -46,8 +48,10 @@ class DeserializableMeta(type):
"""
"""
Metaclass for all Deserializable
Metaclass for all Deserializable
"""
"""
def
__new__
(
def
__new__
(
mcs
:
'
DeserializableMeta
'
,
name
:
str
,
bases
:
Tuple
[
type
],
namespace
:
dict
)
\
mcs
:
'
DeserializableMeta
'
,
name
:
str
,
bases
:
Tuple
[
type
],
namespace
:
dict
)
\
->
type
:
->
type
:
def
auto_ctor
(
self
,
**
kwargs
):
def
auto_ctor
(
self
,
**
kwargs
):
for
k
,
v
in
type
(
self
).
get_attrs
().
items
():
for
k
,
v
in
type
(
self
).
get_attrs
().
items
():
...
@@ -104,6 +108,7 @@ class Deserializable(metaclass=DeserializableMeta):
...
@@ -104,6 +108,7 @@ class Deserializable(metaclass=DeserializableMeta):
"""
"""
Base class for all automagically deserializing classes.
Base class for all automagically deserializing classes.
"""
"""
@classmethod
@classmethod
def
get_attrs
(
cls
)
->
Dict
[
str
,
Rule
]:
def
get_attrs
(
cls
)
->
Dict
[
str
,
Rule
]:
"""
"""
...
@@ -179,12 +184,13 @@ def deserialize(rule: Rule, data, try_all: bool=True, key: str='$'):
...
@@ -179,12 +184,13 @@ def deserialize(rule: Rule, data, try_all: bool=True, key: str='$'):
path to the current value.
path to the current value.
:return: An instance matching Rule.
:return: An instance matching Rule.
"""
"""
#
In case of primitive types, attempt to assign.
#
Deserialize primitives
try
:
try
:
return
rule
.
validate
(
key
,
data
)
return
rule
.
validate
(
key
,
data
)
except
TypeError
:
except
TypeError
:
pass
pass
# Deserialize type unions
if
type
(
rule
.
type
)
is
type
(
Union
):
if
type
(
rule
.
type
)
is
type
(
Union
):
for
arg
in
rule
.
type
.
__args__
:
for
arg
in
rule
.
type
.
__args__
:
try
:
try
:
...
@@ -197,13 +203,15 @@ def deserialize(rule: Rule, data, try_all: bool=True, key: str='$'):
...
@@ -197,13 +203,15 @@ def deserialize(rule: Rule, data, try_all: bool=True, key: str='$'):
raise
TypeError
(
'
{} did not match any of {} for key {}.
'
raise
TypeError
(
'
{} did not match any of {} for key {}.
'
.
format
(
type
(
data
),
rule
.
type
.
__args__
,
key
))
.
format
(
type
(
data
),
rule
.
type
.
__args__
,
key
))
# Deserialize lists
if
type
(
rule
.
type
)
is
type
(
List
):
if
type
(
rule
.
type
)
is
type
(
List
):
if
len
(
rule
.
type
.
__args__
)
!=
1
:
if
len
(
rule
.
type
.
__args__
)
!=
1
:
raise
TypeError
(
raise
TypeError
(
'
Cannot handle list with 0 or more than 1 type arguments.
'
)
'
Cannot handle list with 0 or more than 1 type arguments at {}.
'
.
format
(
key
))
if
type
(
data
)
!=
list
:
if
type
(
data
)
!=
list
:
raise
TypeError
(
raise
TypeError
(
'
Cannot deserialize non-list into list
.
'
)
'
Cannot deserialize non-list into list
at {}.
'
.
format
(
key
)
)
t
=
rule
.
type
.
__args__
[
0
]
t
=
rule
.
type
.
__args__
[
0
]
result
=
[]
result
=
[]
for
i
,
v
in
enumerate
(
data
):
for
i
,
v
in
enumerate
(
data
):
...
@@ -215,6 +223,17 @@ def deserialize(rule: Rule, data, try_all: bool=True, key: str='$'):
...
@@ -215,6 +223,17 @@ def deserialize(rule: Rule, data, try_all: bool=True, key: str='$'):
))
))
return
result
return
result
# Deserialize tuples
if
type
(
rule
.
type
)
is
type
(
Tuple
):
if
len
(
rule
.
type
.
__args__
)
!=
len
(
data
):
raise
TypeError
(
'
Expected a list of {} elements, but got {} elements at {}.
'
.
format
(
len
(
rule
.
type
.
__args__
),
len
(
data
),
key
))
return
tuple
(
deserialize
(
Rule
(
v
[
0
]),
v
[
1
],
key
=
"
{}.{}
"
.
format
(
key
,
k
))
for
k
,
v
in
enumerate
(
zip
(
rule
.
type
.
__args__
,
data
)))
# Deserialize classes
if
issubclass
(
rule
.
type
,
Deserializable
):
if
issubclass
(
rule
.
type
,
Deserializable
):
if
not
isinstance
(
data
,
dict
):
if
not
isinstance
(
data
,
dict
):
raise
TypeError
(
'
Cannot deserialize non-dict into class.
'
)
raise
TypeError
(
'
Cannot deserialize non-dict into class.
'
)
...
@@ -225,6 +244,12 @@ def deserialize(rule: Rule, data, try_all: bool=True, key: str='$'):
...
@@ -225,6 +244,12 @@ def deserialize(rule: Rule, data, try_all: bool=True, key: str='$'):
for
cls
in
classes
:
for
cls
in
classes
:
try
:
try
:
# Instantiate cls with parameters generated by
# the list comprehension.
# It loops through all defined attributes, and
# defines it by recursively calling deserialize on
# each of those attributes with the values found in
# either data, or by using a default.
return
cls
(
**
{
k
:
deserialize
(
return
cls
(
**
{
k
:
deserialize
(
r
,
r
,
data
[
k
]
if
k
in
data
else
r
.
default
,
data
[
k
]
if
k
in
data
else
r
.
default
,
...
@@ -238,7 +263,8 @@ def deserialize(rule: Rule, data, try_all: bool=True, key: str='$'):
...
@@ -238,7 +263,8 @@ def deserialize(rule: Rule, data, try_all: bool=True, key: str='$'):
cause
=
e
cause
=
e
raise
TypeError
(
'
Unable to find matching non-abstract (sub)type of
'
raise
TypeError
(
'
Unable to find matching non-abstract (sub)type of
'
'
{} with key {}. Reason: {}.
'
.
format
(
rule
.
type
,
key
,
cause
))
'
{} with key {}. Reason: {}.
'
.
format
(
rule
.
type
,
key
,
cause
))
raise
TypeError
(
'
Unable to find a deserialization candidate for
'
raise
TypeError
(
'
Unable to find a deserialization candidate for
'
'
{} with key {}.
'
.
format
(
rule
,
key
))
'
{} with key {}.
'
.
format
(
rule
,
key
))
setup.py
View file @
ea978c6a
...
@@ -18,7 +18,7 @@ setuptools.setup(
...
@@ -18,7 +18,7 @@ setuptools.setup(
description
=
dict_deserializer
.
description
,
description
=
dict_deserializer
.
description
,
long_description
=
long_description
,
long_description
=
long_description
,
long_description_content_type
=
"
text/markdown
"
,
long_description_content_type
=
"
text/markdown
"
,
url
=
"
https://git
.iapc.utwente.nl/rkleef/
serializer
_utils
"
,
url
=
"
https://git
hub.com/rhbvkleef/dict_de
serializer
"
,
packages
=
setuptools
.
find_packages
(),
packages
=
setuptools
.
find_packages
(),
install_requires
=
requirements
,
install_requires
=
requirements
,
classifiers
=
[
classifiers
=
[
...
...
test_DirectoryExample.py
View file @
ea978c6a
import
unittest
import
unittest
from
typing
import
List
,
Optional
from
typing
import
List
,
Optional
,
Tuple
from
dict_deserializer.annotations
import
abstract
from
dict_deserializer.annotations
import
abstract
from
dict_deserializer.deserializer
import
Deserializable
,
deserialize
,
Rule
from
dict_deserializer.deserializer
import
Deserializable
,
deserialize
,
Rule
...
@@ -47,8 +47,19 @@ class Group(Object):
...
@@ -47,8 +47,19 @@ class Group(Object):
and
other
.
members
==
self
.
members
and
other
.
members
==
self
.
members
class
NestedTypingStuff
(
Deserializable
):
test
:
List
[
Tuple
[
int
,
int
,
int
]]
def
__str__
(
self
):
return
"
NestedTypingStuff(test={})
"
.
format
(
self
.
test
)
def
__eq__
(
self
,
other
):
return
isinstance
(
other
,
NestedTypingStuff
)
and
self
.
test
==
other
.
test
class
TestLists
(
unittest
.
TestCase
):
class
TestLists
(
unittest
.
TestCase
):
def
test_CorrectDeserializationForNestedWithTypeUnionsAndLists
(
self
):
def
test_CorrectDeserializationForNestedWithTypeUnionsAndLists
(
self
):
# noinspection PyArgumentList
self
.
assertEqual
(
self
.
assertEqual
(
Group
(
Group
(
name
=
'
IAPC
'
,
name
=
'
IAPC
'
,
...
@@ -89,3 +100,35 @@ class TestLists(unittest.TestCase):
...
@@ -89,3 +100,35 @@ class TestLists(unittest.TestCase):
deserialize
(
Rule
(
Object
),
{
deserialize
(
Rule
(
Object
),
{
'
name
'
:
'
Test
'
'
name
'
:
'
Test
'
})
})
def
test_DeserializeTupleCorrect
(
self
):
# noinspection PyArgumentList
self
.
assertEqual
(
NestedTypingStuff
(
test
=
[(
1
,
2
,
3
)]),
deserialize
(
Rule
(
NestedTypingStuff
),
{
"
test
"
:
[
[
1
,
2
,
3
]
]
})
)
def
test_DeserializeTupleFail
(
self
):
with
self
.
assertRaises
(
TypeError
)
as
ctx
:
deserialize
(
Rule
(
NestedTypingStuff
),
{
"
test
"
:
[
[
1
,
2
]
]
})
with
self
.
assertRaises
(
TypeError
)
as
ctx
:
deserialize
(
Rule
(
NestedTypingStuff
),
{
"
test
"
:
[
[
1
,
2
,
"
boo
"
]
]
})