Skip to content

Commit

Permalink
Support default values at construction time
Browse files Browse the repository at this point in the history
  • Loading branch information
Maxim Avanov committed Apr 11, 2020
1 parent 0826f4c commit b41a917
Show file tree
Hide file tree
Showing 7 changed files with 72 additions and 6 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.rst
Expand Up @@ -2,6 +2,13 @@
CHANGELOG
=========


0.23.0
===============

* Support for default values at construction time.


0.22.0
===============

Expand Down
4 changes: 2 additions & 2 deletions docs/conf.py
Expand Up @@ -51,9 +51,9 @@
# built documents.
#
# The short X.Y version.
version = '0.22'
version = '0.23'
# The full version, including alpha/beta/rc tags.
release = '0.22.0'
release = '0.23.0'

# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Expand Up @@ -38,7 +38,7 @@ def requirements(at_path: Path):
# ----------------------------

setup(name='typeit',
version='0.22.0',
version='0.23.0',
description='typeit brings typed data into your project',
long_description=README,
classifiers=[
Expand Down
16 changes: 15 additions & 1 deletion tests/parser/test_dataclasses.py
Expand Up @@ -11,7 +11,7 @@ def test_dataclasses():
class InventoryItem:
name: str
unit_price: float
quantity_on_hand: int = 0
quantity_on_hand: int

overrides = {
(InventoryItem, 'quantity_on_hand'): 'quantity'
Expand All @@ -27,3 +27,17 @@ class InventoryItem:
x = mk_inv(serialized)
assert isinstance(x, InventoryItem)
assert serialize_inv(x) == serialized

def test_with_default_values():

@dataclass
class X:
one: int
two: int = 2
three: int = 3

data = {'one': 1}

mk_x, serialize_x = typeit.TypeConstructor ^ X
x = mk_x(data)
assert serialize_x(x) == {'one': 1, 'two': 2, 'three': 3}
30 changes: 30 additions & 0 deletions tests/parser/test_parser.py
Expand Up @@ -435,3 +435,33 @@ class X(NamedTuple):
])
def test_parse_builtins_and_sequences(typ):
mk_x, serialize_x = typeit.TypeConstructor ^ typ


def test_default_namedtuple_values():
class X(NamedTuple):
x: int = 1

data = {}

mk_x, serialize_x = typeit.TypeConstructor ^ X

x = mk_x(data)
assert isinstance(x, X)
assert x.x == 1
assert serialize_x(x) == {'x': 1}


def test_default_init_based_value():
class X:
def __init__(self, x: int, y: int = 1):
self.x = x
self.y = y

data = {'x': 0}

mk_x, serialize_x = typeit.TypeConstructor ^ X

x = mk_x(data)
assert isinstance(x, X)
assert x.x == 0 and x.y == 1
assert serialize_x(x) == {'x': 0, 'y': 1}
8 changes: 8 additions & 0 deletions typeit/parser.py
Expand Up @@ -4,6 +4,7 @@
MutableSet, TypeVar, FrozenSet, Mapping, Callable,
)

import inspect
import collections

import colander as col
Expand Down Expand Up @@ -376,6 +377,7 @@ def _maybe_node_for_user_type(
hints_source = typ
attribute_hints = _type_hints_getter(hints_source)
get_override_identifier = lambda x: getattr(typ, x)
defaults = typ._field_defaults

deserialize_overrides = pmap({
# try to get a specific override for a field, if it doesn't exist, use the global modifier
Expand All @@ -396,6 +398,11 @@ def _maybe_node_for_user_type(
hints_source = typ.__init__
attribute_hints = _type_hints_getter(hints_source)
get_override_identifier = lambda x: (typ, x)
defaults = {
k: v.default
for k, v in inspect.signature(typ.__init__).parameters.items()
if k != 'self' and v.default != inspect.Parameter.empty
}

deserialize_overrides = pmap({
# try to get a specific override for a field, if it doesn't exist, use the global modifier
Expand Down Expand Up @@ -438,6 +445,7 @@ def _maybe_node_for_user_type(
# might be from the cache already
node = clone_schema_node(node)
node.name = serialized_field_name
node.missing = defaults.get(field_name, node.missing)
type_schema.add(node)
return type_schema, memo

Expand Down
11 changes: 9 additions & 2 deletions typeit/utils.py
@@ -1,12 +1,19 @@
import re
import keyword
import string
from typing import Any, Type
from typing import Any, Type, TypeVar

from colander import TupleSchema, SequenceSchema

from typeit.schema.nodes import SchemaNode

NORMALIZATION_PREFIX = 'overridden__'
SUPPORTED_CHARS = string.ascii_letters + string.digits


T = TypeVar('T', SchemaNode, TupleSchema, SequenceSchema)


def normalize_name(name: str,
pattern=re.compile('^([_0-9]+).*$')) -> str:
""" Some field name patterns are not allowed in NamedTuples
Expand All @@ -28,7 +35,7 @@ def is_named_tuple(typ: Type[Any]) -> bool:
return hasattr(typ, '_fields')


def clone_schema_node(node):
def clone_schema_node(node: T) -> T:
""" Clonning the node and reassigning the same children,
because clonning is recursive, but we are only interested
in a new version of the outermost schema node, the children nodes
Expand Down

0 comments on commit b41a917

Please sign in to comment.