Skip to content

Commit

Permalink
Merge 4ec46ac into 74b275a
Browse files Browse the repository at this point in the history
  • Loading branch information
Maxim Avanov committed Apr 10, 2019
2 parents 74b275a + 4ec46ac commit 99b0d0d
Show file tree
Hide file tree
Showing 11 changed files with 221 additions and 147 deletions.
File renamed without changes.
49 changes: 49 additions & 0 deletions tests/std_types/test_path.py
@@ -0,0 +1,49 @@
import pathlib as p
from typing import NamedTuple, Union, Dict

from typeit import type_constructor


def test_path():
class X(NamedTuple):
pure: p.PurePath
pure_posix: p.PurePosixPath
pure_win: p.PureWindowsPath
path: p.Path
# WinPath is not possible to instantiate on Linux,
# we are omitting a test here
posix_path: p.PosixPath

mk_x, dict_x = type_constructor(X)

data = {
'pure': '/',
'pure_posix': '/a/b/c',
'pure_win': '\\\\a\\b\\c',
'path': '\\a\\b\\c',
'posix_path': '.',
}
x = mk_x(data)
assert dict_x(x) == data


def test_path_union():
class X(NamedTuple):
x: Union[p.Path, Dict]

mk_x, dict_x = type_constructor(X)
data = {
'x': '/'
}
x = mk_x(data)
assert isinstance(x.x, p.Path)
assert dict_x(x) == data

data = {
'x': {
'x': '/'
}
}
x = mk_x(data)
assert isinstance(x.x, dict)
assert dict_x(x) == data
File renamed without changes.
78 changes: 0 additions & 78 deletions tests/test_parser.py
Expand Up @@ -331,84 +331,6 @@ class X(NamedTuple):
assert x.x == x.y['x']


def test_type_with_unions():
class VariantA(NamedTuple):
variant_a: int

class VariantB(NamedTuple):
variant_b: int
variant_b_attr: int

class X(NamedTuple):
x: Union[None, VariantA, VariantB]
y: Union[str, VariantA]

mk_x, dict_x = p.type_constructor(X)

x = mk_x({'x': {'variant_a': 1}, 'y': 'y'})
assert isinstance(x.x, VariantA)

data = {'x': {'variant_b': 1, 'variant_b_attr': 1}, 'y': 'y'}
x = mk_x(data)
assert isinstance(x.x, VariantB)

assert data == dict_x(x)

assert mk_x({'x': None, 'y': 'y'}) == mk_x({'y': 'y'})
with pytest.raises(colander.Invalid):
# this is not the same as mk_x({}),
# the empty structure is passed as attribute x,
# which should match with only an empty named tuple definition,
# which is not the same as None.
mk_x({'x': {}})


def test_type_with_primitive_union():
class X(NamedTuple):
x: Union[None, str]

mk_x, dict_x = p.type_constructor(X)

x = mk_x({'x': None})
assert x.x is None

data = {'x': 'test'}
x = mk_x(data)
assert x.x == 'test'

assert data == dict_x(x)


def test_union_primitive_match():
class X(NamedTuple):
# here, str() accepts everything that could be passed to int(),
# and int() accepts everything that could be passed to float(),
# and we still want to get int values instead of string values,
# and float values instead of rounded int values.
x: Union[str, int, float, bool]

mk_x, serializer = p.type_constructor(X)

x = mk_x({'x': 1})
assert isinstance(x.x, int)

x = mk_x({'x': 1.0})
assert isinstance(x.x, float)

if not p.PY36:
# Python 3.6 has a bug that drops bool types from
# unions that include int already, so
# x: Union[int, bool] -- will be reduced to just `x: int`
# x: Union[str, bool] -- will be left as is
x = mk_x({'x': True})
assert isinstance(x.x, bool)

data = {'x': '1'}
x = mk_x(data)
assert isinstance(x.x, str)
assert serializer(x) == data


def test_parser_github_pull_request_payload():
data = GITHUB_PR_PAYLOAD_JSON
github_pr_dict = json.loads(data)
Expand Down
29 changes: 0 additions & 29 deletions tests/test_std/test_path.py

This file was deleted.

Empty file added tests/unions/__init__.py
Empty file.
100 changes: 100 additions & 0 deletions tests/unions/test_unions.py
@@ -0,0 +1,100 @@
from typing import NamedTuple, Union, Mapping, Any, Dict

import colander
import pytest

from typeit import type_constructor, parser as p, flags
from typeit.compat import PY36


def test_type_with_unions():
class VariantA(NamedTuple):
variant_a: int

class VariantB(NamedTuple):
variant_b: int
variant_b_attr: int

class X(NamedTuple):
x: Union[None, VariantA, VariantB]
y: Union[str, VariantA]

mk_x, dict_x = p.type_constructor(X)

x = mk_x({'x': {'variant_a': 1}, 'y': 'y'})
assert isinstance(x.x, VariantA)

data = {'x': {'variant_b': 1, 'variant_b_attr': 1}, 'y': 'y'}
x = mk_x(data)
assert isinstance(x.x, VariantB)

assert data == dict_x(x)

assert mk_x({'x': None, 'y': 'y'}) == mk_x({'y': 'y'})
with pytest.raises(colander.Invalid):
# this is not the same as mk_x({}),
# the empty structure is passed as attribute x,
# which should match with only an empty named tuple definition,
# which is not the same as None.
mk_x({'x': {}})


def test_type_with_primitive_union():
class X(NamedTuple):
x: Union[None, str]

mk_x, dict_x = type_constructor(X)

x = mk_x({'x': None})
assert x.x is None

data = {'x': 'test'}
x = mk_x(data)
assert x.x == 'test'

assert data == dict_x(x)


def test_union_primitive_match():
class X(NamedTuple):
# here, str() accepts everything that could be passed to int(),
# and int() accepts everything that could be passed to float(),
# and we still want to get int values instead of string values,
# and float values instead of rounded int values.
x: Union[str, int, float, bool]

mk_x, serializer = type_constructor(X)

x = mk_x({'x': 1})
assert isinstance(x.x, int)

x = mk_x({'x': 1.0})
assert isinstance(x.x, float)

if not PY36:
# Python 3.6 has a bug that drops bool types from
# unions that include int already, so
# x: Union[int, bool] -- will be reduced to just `x: int`
# x: Union[str, bool] -- will be left as is
x = mk_x({'x': True})
assert isinstance(x.x, bool)

data = {'x': '1'}
x = mk_x(data)
assert isinstance(x.x, str)
assert serializer(x) == data


def test_test_union_primitive_and_compound_types():
class X(NamedTuple):
x: Union[str, Dict[str, Any]]

mk_x, dict_x = type_constructor(X)
mk_x_nonstrict, dict_x_nonstrict = type_constructor(X, overrides={flags.NON_STRICT_PRIMITIVES: 1})

data = {'x': {'key': 'value'}}
x = mk_x(data)
assert dict_x(x) == data

x = mk_x_nonstrict(data)
assert dict_x_nonstrict(x) == data
2 changes: 1 addition & 1 deletion typeit/codegen.py
Expand Up @@ -6,7 +6,7 @@
from .definitions import OverridesT
from .definitions import FieldDefinition
from . import interface as iface
from .parser import PY37
from .compat import PY37


_type_name_getter = lambda typ: typ.__name__
Expand Down
5 changes: 5 additions & 0 deletions typeit/compat.py
@@ -0,0 +1,5 @@
import sys

PY_VERSION = sys.version_info[:2]
PY37 = PY_VERSION == (3, 7)
PY36 = PY_VERSION == (3, 6)
29 changes: 12 additions & 17 deletions typeit/parser.py
@@ -1,4 +1,3 @@
import sys
from typing import (
Type, Tuple, Optional, Any, Union, List, Set,
Dict, Callable,
Expand All @@ -14,15 +13,10 @@
from . import schema
from . import interface as iface


PY37 = sys.version_info[:2] == (3, 7)
PY36 = sys.version_info[:2] == (3, 6)


T = TypeVar('T')


def _maybe_node_for_builtin(
def _maybe_node_for_primitive(
typ: Union[Type[iface.IType], Any],
overrides: OverridesT
) -> Optional[schema.SchemaNode]:
Expand All @@ -35,11 +29,11 @@ def _maybe_node_for_builtin(
registry = schema.BUILTIN_TO_SCHEMA_TYPE

try:
typ = registry[typ]
schema_type = registry[typ]
except KeyError:
return None

return schema.SchemaNode(typ)
return schema.SchemaNode(schema_type)


def _maybe_node_for_type_var(
Expand All @@ -52,7 +46,7 @@ def _maybe_node_for_type_var(
we can treat it as typing.Any.
"""
if isinstance(typ, TypeVar):
return _maybe_node_for_builtin(Any, overrides)
return _maybe_node_for_primitive(Any, overrides)
return None


Expand Down Expand Up @@ -96,23 +90,24 @@ def _maybe_node_for_union(
)

allow_empty = NoneClass in variants
node_variants = []
# represents a 2-tuple of (type_from_signature, associated_schema_node)
variant_nodes: List[Tuple[Type, schema.SchemaNode]] = []
for variant in variants:
if variant is NoneClass:
continue
node = decide_node_type(variant, overrides)
if allow_empty:
node.missing = None
node_variants.append(node)
variant_nodes.append((variant, node))

if flags.NON_STRICT_PRIMITIVES in overrides:
primitives_registry = schema.NON_STRICT_BUILTIN_TO_SCHEMA_TYPE
primitive_types = schema.NON_STRICT_BUILTIN_TO_SCHEMA_TYPE
else:
primitives_registry = schema.BUILTIN_TO_SCHEMA_TYPE
primitive_types = schema.BUILTIN_TO_SCHEMA_TYPE

union_node = schema.SchemaNode(
schema.UnionNode(variants=node_variants,
primitives_registry=primitives_registry)
schema.UnionNode(variant_nodes=variant_nodes,
primitive_types=primitive_types)
)
if allow_empty:
union_node.missing = None
Expand Down Expand Up @@ -282,7 +277,7 @@ def _maybe_node_for_overridden(

PARSING_ORDER = [
_maybe_node_for_overridden,
_maybe_node_for_builtin,
_maybe_node_for_primitive,
_maybe_node_for_type_var,
_maybe_node_for_union,
_maybe_node_for_list,
Expand Down

0 comments on commit 99b0d0d

Please sign in to comment.