Skip to content

Commit

Permalink
Release 3.11.0.0
Browse files Browse the repository at this point in the history
  • Loading branch information
Maxim Avanov committed Mar 8, 2023
1 parent 23045aa commit 438aa35
Show file tree
Hide file tree
Showing 9 changed files with 95 additions and 39 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@
CHANGELOG
=========

3.11.0.0
========

* Support for Python 3.11


3.9.1.9
=======

Expand Down
8 changes: 4 additions & 4 deletions requirements/minimal.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
inflection>=0.4.0,<0.6.0
colander>=1.7.0,<1.9.0
pyrsistent>=0.18.1,<0.19
typing-inspect>=0.7.1,<0.8.0
typing-extensions>=4.2,<4.3
colander>=1.7.0,<2.1
pyrsistent>=0.19.3
typing-inspect>=0.7.1,<0.9
typing-extensions>=4.2,<4.6
6 changes: 3 additions & 3 deletions requirements/test.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
-r ./minimal.txt
pytest>=7.1,<7.2
coverage>=6.4,<6.5
pytest>=7.1,<7.3
coverage>=6.4,<7.3
coveralls>=3.3,<3.4
pytest-cov>=3.0,<3.1
pytest-cov>=3.0,<4.1
mypy==0.961
py-money==0.5.0
requests>=2.28
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def requirements(at_path: Path):
# ----------------------------

setup(name='typeit',
version='3.10.0.0',
version='3.11.0.0',
description='typeit brings typed data into your project',
long_description=README,
classifiers=[
Expand Down
6 changes: 4 additions & 2 deletions tests/test_pyrsistent.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@ class X(NamedTuple):
a: ps.typing.PMap[str, Any]
b: ps.typing.PVector[ps.typing.PMap[str, Any]]
c: Optional[ps.typing.PMap[str, Any]]
d: Optional[ps.typing.PMap]

mk_x, serialize_x = ty.TypeConstructor ^ X
mk_x, serialize_x = ty.type_constructor ^ X

data = {
'a': {'x': 'x', 'y': 'y'},
'b': [{'x': 'x', 'y': 'y'}],
'c': None
'c': None,
'd': {'a': 1}
}
x = mk_x(data)
assert isinstance(x.a, ps.PMap)
Expand Down
16 changes: 15 additions & 1 deletion tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,18 @@ class X(NamedTuple):
try:
serialize_x(x)
except typeit.Error as e:
x = list(e)
x = list(e)


def test_new_init():
class X(NamedTuple):
a: int
b: bool

a = 1
b = True
res = utils.new(X)
assert res.a == a
assert res.b == b

res1 = utils.new(X, res)
46 changes: 20 additions & 26 deletions typeit/parser/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
from types import UnionType
from typing import (
Type, Tuple, Optional, Any, Union, List, Set,
Dict, Sequence, get_type_hints,
MutableSet, TypeVar, FrozenSet, Mapping, NamedTuple, ForwardRef, NewType,
Dict, Sequence, MutableSet, TypeVar, FrozenSet, Mapping, ForwardRef, NewType,
)

import inspect
Expand All @@ -12,6 +11,8 @@
from pyrsistent import pmap, pvector
from pyrsistent import typing as pyt

from .type_info import get_type_attribute_info, AttrInfo, NoneType

from ..compat import Literal
from ..definitions import OverridesT
from ..utils import is_named_tuple, clone_schema_node, get_global_name_overrider
Expand All @@ -25,9 +26,6 @@
T = TypeVar('T')
MemoType = TypeVar('MemoType')

NoneType = type(None)


OverrideT = ( flags._Flag # flag override
| TypeExtension # new type extension
| Union[
Expand Down Expand Up @@ -442,15 +440,23 @@ def _maybe_node_for_dict(
if typ in supported_type or get_origin_39(typ) in supported_origin or are_generic_bases_match(generic_bases, supported_origin):
schema_node_type = schema.nodes.PMapSchema if is_pmap(typ) else schema.nodes.SchemaNode

if generic_bases:
# python 3.9 args
key_type, value_type = typ.__args__
else:
def maybe_non_specified_arguments(mappingType):
try:
key_type, value_type = insp.get_args(typ)
kt, kv = insp.get_args(mappingType)
except ValueError:
# Mapping doesn't provide key/value types
key_type, value_type = Any, Any
kt, kv = Any, Any
return kt, kv

if generic_bases:
# python 3.9 args
try:
key_type, value_type = typ.__args__
except AttributeError:
# PMap without clarifying type arguments will cause this branch
key_type, value_type = maybe_non_specified_arguments(typ)
else:
key_type, value_type = maybe_non_specified_arguments(typ)

key_node, memo, forward_refs = decide_node_type(key_type, overrides, memo, forward_refs)
value_node, memo, forward_refs = decide_node_type(value_type, overrides, memo, forward_refs)
Expand All @@ -459,18 +465,6 @@ def _maybe_node_for_dict(
return rv, memo, forward_refs


class AttrInfo(NamedTuple):
name: str
resolved_type: Type
raw_type: Union[Type, ForwardRef]


def _type_hints_getter(typ: Type) -> Sequence[AttrInfo]:
raw = getattr(typ, '__annotations__', {})
existing_only = lambda x: x[1] is not NoneType
return [AttrInfo(name, t, raw.get(name, t)) for name, t in filter(existing_only, get_type_hints(typ).items())]


def _maybe_node_for_user_type(
typ: Type[iface.IType],
overrides: OverridesT,
Expand All @@ -495,7 +489,7 @@ def _maybe_node_for_user_type(
type_var_to_type = pmap(zip(generic_vars_ordered, bound_type_args))
# resolve type hints
attribute_hints = [(field_name, type_var_to_type[type_var])
for field_name, type_var in ((x, raw_type) for x, _resolved_type, raw_type in _type_hints_getter(hints_source))]
for field_name, type_var in ((x, raw_type) for x, _resolved_type, raw_type in get_type_attribute_info(hints_source))]
# Generic types should not have default values
defaults_source = lambda: ()
# Overrides should be the same as class-based ones, as Generics are not NamedTuple classes,
Expand All @@ -516,7 +510,7 @@ def _maybe_node_for_user_type(

elif is_named_tuple(typ):
hints_source = typ
attribute_hints = [(x, raw_type) for x, y, raw_type in _type_hints_getter(hints_source)]
attribute_hints = [(x, raw_type) for x, y, raw_type in get_type_attribute_info(hints_source)]
get_override_identifier = lambda x: getattr(typ, x)
defaults_source = typ.__new__

Expand All @@ -537,7 +531,7 @@ def _maybe_node_for_user_type(
else:
# use init-based types
hints_source = typ.__init__
attribute_hints = [(x, raw_type) for x, y, raw_type in _type_hints_getter(hints_source)]
attribute_hints = [(x, raw_type) for x, y, raw_type in get_type_attribute_info(hints_source)]
get_override_identifier = lambda x: (typ, x)
defaults_source = typ.__init__

Expand Down
15 changes: 15 additions & 0 deletions typeit/parser/type_info.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from typing import Type, get_type_hints, NamedTuple, Union, ForwardRef, Any, Generator

NoneType = type(None)


class AttrInfo(NamedTuple):
name: str
resolved_type: Type
raw_type: Union[Type, ForwardRef]


def get_type_attribute_info(typ: Type) -> Generator[AttrInfo, Any, None]:
raw = getattr(typ, '__annotations__', {})
existing_only = lambda x: x[1] is not NoneType
return (AttrInfo(name, t, raw.get(name, t)) for name, t in filter(existing_only, get_type_hints(typ).items()))
29 changes: 27 additions & 2 deletions typeit/utils.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
import re
import keyword
import string
from typing import Any, Type, TypeVar, Callable
import inspect as ins
from typing import Any, Type, TypeVar, Callable, Optional

from colander import TupleSchema, SequenceSchema

from typeit import flags
from typeit.definitions import OverridesT
from typeit.parser import get_type_attribute_info
from typeit.schema.nodes import SchemaNode

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


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

A = TypeVar('A')
B = TypeVar('B')

def normalize_name(name: str,
pattern=re.compile('^([_0-9]+).*$')) -> str:
Expand Down Expand Up @@ -53,3 +56,25 @@ def clone_schema_node(node: T) -> T:

def get_global_name_overrider(overrides: OverridesT) -> Callable[[str], str]:
return overrides.get(flags.GlobalNameOverride, flags.Identity)


def new(t: Type[A], scope: Optional[B] = None) -> A:
"""Experimental: Init a type instance from the values of the provided scope, as long as the scope variables
have the same names and their types match the types of the attributes being initialised.
"""
if scope is None:
f = ins.currentframe().f_back.f_locals
else:
f_ = scope.__annotations__
f = {x: getattr(scope, x) for x in f_}

tattrs = get_type_attribute_info(t)
constr = {}
for attr in tattrs:
if attr.name not in f:
raise AttributeError(f"Could not find attribute {attr.name} for type {t.__class__} in the provided context")
ctxval = f[attr.name]
if not isinstance(ctxval, attr.resolved_type):
raise AttributeError(f"Types do not match: '{attr.name}' has to be {attr.resolved_type} but got {type(ctxval)} instead.")
constr[attr.name] = ctxval
return t(**constr)

0 comments on commit 438aa35

Please sign in to comment.