Skip to content

Commit

Permalink
Parser memoization #38
Browse files Browse the repository at this point in the history
  • Loading branch information
Maxim Avanov committed Jul 7, 2019
1 parent 9f52943 commit c119ac1
Show file tree
Hide file tree
Showing 13 changed files with 201 additions and 155 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.rst
Expand Up @@ -7,6 +7,7 @@ CHANGELOG

* SumType from/to mapping #43
* Support explicit tags for SumType Variants #32
* Parser memoization #38


0.14.1
Expand Down
5 changes: 3 additions & 2 deletions tests/parser/test_codegen.py
Expand Up @@ -3,7 +3,8 @@

import pytest

from typeit import codegen as cg, parser as p
import typeit
from typeit import codegen as cg
from typeit.codegen import TypeitSchema


Expand Down Expand Up @@ -55,7 +56,7 @@ def test_parser_github_pull_request_payload():
assert PullRequestType.links in overrides
assert overrides[PullRequestType.links] == '_links'

constructor, serializer = p.type_constructor(
constructor, serializer = typeit.type_constructor(
typ,
overrides=overrides
)
Expand Down
4 changes: 2 additions & 2 deletions tests/parser/test_dataclasses.py
@@ -1,8 +1,8 @@
import typeit
from typeit.compat import PY_VERSION

if PY_VERSION >= (3, 7):
from dataclasses import dataclass
from typeit import parser as p


def test_dataclasses():
Expand All @@ -13,7 +13,7 @@ class InventoryItem:
unit_price: float
quantity_on_hand: int = 0

mk_inv, serialize_inv = p.type_constructor(InventoryItem)
mk_inv, serialize_inv = typeit.type_constructor(InventoryItem)

serialized = {
'name': 'test',
Expand Down
8 changes: 4 additions & 4 deletions tests/parser/test_extending.py
Expand Up @@ -4,7 +4,7 @@
from money.money import Money

import typeit
from typeit import schema, parser as p
from typeit import schema


def test_extending():
Expand All @@ -19,12 +19,12 @@ def deserialize(self, node, cstruct):
try:
currency = Currency(r[0])
except ValueError:
raise typeit.Invalid(node, f'Invalid currency token in {r}', cstruct)
raise schema.Invalid(node, f'Invalid currency token in {r}', cstruct)

try:
rv = Money(r[1], currency)
except:
raise typeit.Invalid(node, f'Invalid amount in {r}', cstruct)
raise schema.Invalid(node, f'Invalid amount in {r}', cstruct)

return rv

Expand All @@ -37,7 +37,7 @@ def serialize(self, node, appstruct: Optional[Money]):
return super().serialize(node, r)

mk_x, serialize_x = (
p.type_constructor
typeit.type_constructor
& MoneySchema[Money] << schema.types.Enum(Currency) << schema.primitives.NonStrictStr()
^ X
)
Expand Down
43 changes: 21 additions & 22 deletions tests/parser/test_parser.py
Expand Up @@ -4,7 +4,6 @@
import pytest

import typeit
from typeit import parser as p
from typeit import flags
from typeit import schema
from typeit.sums import Either
Expand All @@ -15,7 +14,7 @@ class X(NamedTuple):
x: Sequence
y: List

mk_main, serialize_main = p.type_constructor ^ X
mk_main, serialize_main = typeit.type_constructor ^ X
x = mk_main({'x': [], 'y': []})
x = mk_main({'x': [1], 'y': ['1']})
assert x.x[0] == int(x.y[0])
Expand All @@ -30,8 +29,8 @@ class X(NamedTuple):
c: float
d: bool

mk_x, serialize_x = p.type_constructor ^ X
mk_x_nonstrict, serialize_x_nonstrict = p.type_constructor & flags.NON_STRICT_PRIMITIVES ^ X
mk_x, serialize_x = typeit.type_constructor ^ X
mk_x_nonstrict, serialize_x_nonstrict = typeit.type_constructor & flags.NON_STRICT_PRIMITIVES ^ X

data = {
'a': '1',
Expand Down Expand Up @@ -71,7 +70,7 @@ def test_serialize_list():
class X(NamedTuple):
x: Union[None, Sequence[str]]

mk_x, serialize_x = p.type_constructor ^ X
mk_x, serialize_x = typeit.type_constructor ^ X
data = {
'x': ['str'],
}
Expand All @@ -92,7 +91,7 @@ def test_serialize_union_lists():
class X(NamedTuple):
x: Union[Sequence[str], Sequence[float], Sequence[int]]

mk_x, serialize_x = p.type_constructor ^ X
mk_x, serialize_x = typeit.type_constructor ^ X
data = {
'x': [1],
}
Expand All @@ -106,7 +105,7 @@ class X(NamedTuple):
y: Sequence[Any]
z: Sequence[str]

mk_main, serializer = p.type_constructor(X)
mk_main, serializer = typeit.type_constructor(X)

x = mk_main({'x': 1, 'y': [], 'z': ['Hello']})
assert x.y == []
Expand All @@ -123,7 +122,7 @@ class X(NamedTuple):
b: Tuple # the following are equivalent
c: tuple

mk_x, serializer = p.type_constructor(X)
mk_x, serializer = typeit.type_constructor(X)

x = mk_x({
'a': ['value', 5],
Expand Down Expand Up @@ -167,7 +166,7 @@ class X(NamedTuple):
a: Tuple[Tuple[Dict, Y], int]
b: Optional[Any]

mk_x, serializer = p.type_constructor(X)
mk_x, serializer = typeit.type_constructor(X)

x = mk_x({
'a': [
Expand All @@ -194,7 +193,7 @@ class X(NamedTuple):
a: Tuple[int, ...]

with pytest.raises(TypeError):
mk_x, serialize_x = p.type_constructor(X)
mk_x, serialize_x = typeit.type_constructor(X)


def test_enum_like_types():
Expand All @@ -205,7 +204,7 @@ class Enums(Enum):
class X(NamedTuple):
e: Enums

mk_x, serialize_x = p.type_constructor(X)
mk_x, serialize_x = typeit.type_constructor(X)

data = {'e': 'a'}
x = mk_x(data)
Expand All @@ -232,7 +231,7 @@ class Right:
class X(NamedTuple):
x: MyEither

mk_x, serialize_x = p.type_constructor ^ X
mk_x, serialize_x = typeit.type_constructor ^ X
x_data = {
'x': ('left', {'err': 'Error'})
}
Expand Down Expand Up @@ -294,7 +293,7 @@ class MyType(NamedTuple):
val: Union[E0, E1]


__, serializer = p.type_constructor(MyType)
__, serializer = typeit.type_constructor(MyType)

assert serializer(MyType(val=E1.Z)) == {'val': 'z'}

Expand All @@ -308,7 +307,7 @@ class X(NamedTuple):
x: int
y: Types

mk_x, serializer = p.type_constructor(X)
mk_x, serializer = typeit.type_constructor(X)

for variant in Types:
x = mk_x({'x': 1, 'y': variant.value})
Expand All @@ -329,7 +328,7 @@ class X(NamedTuple):
g: Set[Any]
h: Set[int]

mk_x, serializer = p.type_constructor(X)
mk_x, serializer = typeit.type_constructor(X)

x = mk_x({
'a': [],
Expand All @@ -356,7 +355,7 @@ class X(NamedTuple):
XS = Sequence[X]

data = [{'x': 1, 'y': {}}]
mk_xs, serialize_xs = p.type_constructor(XS)
mk_xs, serialize_xs = typeit.type_constructor(XS)
z = mk_xs(data)
assert z[0].x == 1
assert serialize_xs(z) == data
Expand All @@ -365,7 +364,7 @@ class X(NamedTuple):
XS = Sequence[int]

data = [1, 2, 3]
mk_xs, serialize_xs = p.type_constructor(XS)
mk_xs, serialize_xs = typeit.type_constructor(XS)

z = mk_xs(data)
assert z[0] == 1
Expand All @@ -379,7 +378,7 @@ class X(NamedTuple):
(Dict[str, Any], {'x': 1, 'y': True, 'z': '1'})
))
def test_parse_builtins(typ, data):
mk_x, serialize_x = p.type_constructor(typ)
mk_x, serialize_x = typeit.type_constructor(typ)

z = mk_x(data)
assert z == data
Expand All @@ -399,7 +398,7 @@ class X(NamedTuple):
x: int
y: Dict[str, Any]

mk_x, serializer = p.type_constructor(X)
mk_x, serializer = typeit.type_constructor(X)

with pytest.raises(typeit.Error):
mk_x({})
Expand All @@ -418,10 +417,10 @@ class X(NamedTuple):
data = {'my-x': 1}

with pytest.raises(typeit.Error):
mk_x, serialize_x = p.type_constructor ^ X
mk_x, serialize_x = typeit.type_constructor ^ X
mk_x(data)

mk_x, serialize_x = p.type_constructor & {X.x: 'my-x'} ^ X
mk_x, serialize_x = typeit.type_constructor & {X.x: 'my-x'} ^ X
x = mk_x(data)
assert serialize_x(x) == data

Expand All @@ -435,4 +434,4 @@ class X(NamedTuple):
Sequence,
])
def test_parse_builtins_and_sequences(typ):
mk_x, serialize_x = p.type_constructor ^ typ
mk_x, serialize_x = typeit.type_constructor ^ typ
5 changes: 2 additions & 3 deletions tests/parser/test_regular_classes.py
@@ -1,12 +1,11 @@
from money.money import Money

from typeit import parser as p
from typeit import flags
import typeit


def test_regular_classes():

mk_x, serialize_x = p.type_constructor & flags.NON_STRICT_PRIMITIVES ^ Money
mk_x, serialize_x = typeit.type_constructor & typeit.flags.NON_STRICT_PRIMITIVES ^ Money

serialized = {
'amount': '10',
Expand Down
2 changes: 1 addition & 1 deletion tests/test_sums.py
Expand Up @@ -139,7 +139,7 @@ class VariantB:
b: bool

mk_x, serialize_x = (
typeit.type_constructor & typeit.flags.SUM_TYPES_AS_DICT << '_type' ^ X
typeit.type_constructor & typeit.flags.SUM_TYPES_AS_DICT << '_type' ^ X
)

data = {
Expand Down
3 changes: 1 addition & 2 deletions tests/test_utils.py
Expand Up @@ -2,7 +2,6 @@
from typeit.schema.errors import InvalidData
from typeit import utils
from typing import NamedTuple, Sequence
from typeit import type_constructor
from enum import Enum


Expand All @@ -28,7 +27,7 @@ class X(NamedTuple):
items: Sequence[Item]
item: Item

mk_x, serialize_x = type_constructor(X)
mk_x, serialize_x = typeit.type_constructor(X)

data = {
'items': [
Expand Down
2 changes: 1 addition & 1 deletion tests/unions/test_unions.py
Expand Up @@ -19,7 +19,7 @@ class X(NamedTuple):
x: Union[None, VariantA, VariantB]
y: Union[str, VariantA]

mk_x, serialize_x = p.type_constructor(X)
mk_x, serialize_x = typeit.type_constructor(X)

x = mk_x({'x': {'variant_a': 1}, 'y': 'y'})
assert isinstance(x.x, VariantA)
Expand Down
2 changes: 1 addition & 1 deletion typeit/__init__.py
@@ -1,5 +1,5 @@
from . import flags
from .parser import type_constructor
from .combinator import type_constructor
from .schema.errors import Error

__all__ = ('type_constructor', 'flags', 'Error')
61 changes: 61 additions & 0 deletions typeit/combinator.py
@@ -0,0 +1,61 @@
from functools import partial
from typing import Tuple, Callable, Dict, Any, Union, List, Type

from pyrsistent import pmap

from . import flags, schema
from .definitions import OverridesT, NO_OVERRIDES
from .parser import T, decide_node_type, OverrideT
from .schema.errors import errors_aware_constructor

TypeTools = Tuple[ Callable[[Dict[str, Any]], T]
, Callable[[T], Union[List, Dict]] ]


class _TypeConstructor:
def __init__(self, overrides: Union[Dict, OverridesT] = NO_OVERRIDES):
self.overrides = pmap(overrides)
self.memo = pmap()

def __call__(self,
typ: Type[T],
overrides: OverridesT = NO_OVERRIDES
) -> TypeTools:
""" Generate a constructor and a serializer for the given type
:param overrides: a mapping of type_field => serialized_field_name.
"""
try:
schema_node, memo = decide_node_type(typ, overrides, self.memo)
except TypeError as e:
raise TypeError(
f'Cannot create a type constructor for {typ}: {e}'
)
self.memo = memo
return (
partial(errors_aware_constructor, schema_node.deserialize),
partial(errors_aware_constructor, schema_node.serialize)
)

def __and__(self, override: OverrideT) -> '_TypeConstructor':
if isinstance(override, flags._Flag):
overrides = self.overrides.set(override, True)

elif isinstance(override, schema.meta.TypeExtension):
overrides = self.overrides.set(override.typ, override)

elif isinstance(override, tuple):
# override is a flag with extra settings
overrides = self.overrides.set(override[0], override[1])

else:
# override is a field mapping
overrides = self.overrides.update(override)

return self.__class__(overrides=overrides)

def __xor__(self, typ: Type[T]) -> TypeTools:
return self.__call__(typ, self.overrides)


type_constructor = _TypeConstructor()

0 comments on commit c119ac1

Please sign in to comment.