Permalink
Browse files

Allow None values for Optional's in unserializer

  • Loading branch information...
Georgeto committed Feb 11, 2019
1 parent 4a4f732 commit 159f8eb872a904a8c36c5be1fa14548381c59357
Showing with 36 additions and 4 deletions.
  1. +18 −3 sipa/model/pycroft/unserialize.py
  2. +18 −1 tests/test_unserialize.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
import inspect
import sys
from typing import Callable, Optional, Any, List
from typing import Callable, Optional, Any, List, Union


class UnserializationError(Exception):
@@ -26,6 +26,10 @@ def _maybe_setattr(cls, attrname, attr):
setattr(cls, attrname, attr)


def _is_optional(t):
return t.__origin__ is Union and len(t.__args__) == 2 and t.__args__[1] is type(None)


MAXDEPTH = 100


@@ -48,6 +52,15 @@ def constructor(val):
else:
raise UnserializationError("Cannot find constructor for List[A, ...]"
" with more than one argument.")
elif name == Optional._name:
if len(args) >= 1:
item_constructor = constructor_from_annotation(args[0], *a, **kw)

def constructor(val):
return item_constructor(val) if val is not None else None
else:
raise UnserializationError("Cannot find constructor for Optional[...]"
" with less than one argument.")
else:
raise UnserializationError(f"Generic type '{name}' not supported")

@@ -74,8 +87,10 @@ def constructor_from_annotation(type_, module, maxdepth=MAXDEPTH) -> Callable:

# Case 1: known generic
if hasattr(type_, '_name'):
# TODO: make everything else required, except if we get Optional[...] here
constructor = constructor_from_generic(type_._name, getattr(type_, '__args__', ()),
type_name = type_._name
if _is_optional(type_):
type_name = Optional._name
constructor = constructor_from_generic(type_name, getattr(type_, '__args__', ()),
module=module, maxdepth=maxdepth-1)

# cases 2, 3: Is an unserializer or something builtin
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
from __future__ import annotations
from typing import Any, List
from typing import Any, List, Optional
from unittest import TestCase

from sipa.model.pycroft.unserialize import unserializer, MissingKeysError, ConversionError, \
@@ -56,6 +56,23 @@ class Foo:
else:
self.assertEqual(f.items, ["bar", "baz"])

def test_optional_can_convert(self):
@unserializer
class Foo:
opt: Optional[str]

try:
f = Foo({'opt': "bar"})
self.assertEqual(f.opt, "bar")
except ConversionError:
self.fail()

try:
f = Foo({'opt': None})
self.assertEqual(f.opt, None)
except ConversionError:
self.fail()

def test_complex_unserializer(self):
@unserializer
class Foo:

0 comments on commit 159f8eb

Please sign in to comment.