Skip to content

Commit

Permalink
Release 3.11.1.2 (#83)
Browse files Browse the repository at this point in the history
  • Loading branch information
avanov committed Oct 27, 2023
1 parent 3db2640 commit bcad756
Show file tree
Hide file tree
Showing 6 changed files with 72 additions and 9 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.rst
Expand Up @@ -2,10 +2,11 @@
CHANGELOG
=========

3.11.1.1
3.11.1.2
========

* Fixed bug in Union of literals serialisation.
* Fixed bug in concrete subclasses of generic classes.


3.11.0.0
Expand Down
2 changes: 1 addition & 1 deletion docs/conf.py
Expand Up @@ -53,7 +53,7 @@
# The short X.Y version.
version = '3.11'
# The full version, including alpha/beta/rc tags.
release = '3.11.1.1'
release = '3.11.1.2'

# 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='3.11.1.1',
version='3.11.1.2',
description='typeit brings typed data into your project',
long_description=README,
classifiers=[
Expand Down
37 changes: 37 additions & 0 deletions tests/test_generics.py
@@ -0,0 +1,37 @@
from dataclasses import dataclass
from typing import Generic, TypeVar, NamedTuple, Sequence

import typeit


A = TypeVar('A')

@dataclass(frozen=True, slots=True)
class X(Generic[A]):
pk: int
entry: A


def test_generic():
class Item(NamedTuple):
value: str

class Concrete(X[Item]):
pass

class Wrapper(NamedTuple):
vals: Sequence[Concrete]

mk_wrapper, serialize_wrapper = typeit.TypeConstructor ^ Wrapper

serialized = {
"vals": [
{
"pk": 1,
"entry": {
"value": "item value"
}
}
]
}
assert serialize_wrapper(mk_wrapper(serialized)) == serialized
24 changes: 20 additions & 4 deletions typeit/parser/__init__.py
Expand Up @@ -480,16 +480,32 @@ def _maybe_node_for_user_type(

if is_generic:
# get the base class that was turned into Generic[T, ...]
hints_source = get_origin_39(typ)
# For generic types without clarification this is get_origin, otherwise if the generic type is already
# clarified (via subclassing like class MyClass(GenericBase[ConcreteType]): ... ) the generic hints source is
# going to be the type itself instead of origins. This second version is actually similar to the else clause
# below: "elif is_named_tuple(typ)"
hints_source = get_origin_39(typ) or typ

# now we need to map generic type variables to the bound class types,
# e.g. we map Generic[T,U,V, ...] to actual types of MyClass[int, float, str, ...]
generic_repr = insp.get_generic_bases(hints_source)
generic_vars_ordered = (insp.get_args(x)[0] for x in generic_repr)
bound_type_args = insp.get_args(typ)
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 get_type_attribute_info(hints_source))]
# Resolve type hints.
# We have to match all generic type parameter placeholders with the actual types passed as implementations
# of the interface. However, we need to keep in mind that not all attributes of the generic type have generic
# placeholders. Hence, in places where we cannot find the generic placeholder name, we just assume that there's
# no placeholder, and therefore ``type_var`` is automatically a concrete type
attribute_hints = [
( field_name
, type_var_to_type.get(type_var_or_concrete_type) or type_var_or_concrete_type
)
for field_name, type_var_or_concrete_type 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 Down
13 changes: 11 additions & 2 deletions typeit/schema/types.py
Expand Up @@ -105,10 +105,19 @@ def deserialize(self, node, cstruct):
r = super().deserialize(node, cstruct)
if r is Null:
return r
return self.typ(**{
d = {
self.deserialize_overrides.get(k, k): v
for k, v in r.items()
})
}
try:
return self.typ(**d)
except TypeError:
raise Invalid(
node,
"Are you trying to use generically defined type Name(Generic[A]) via Name[<MyType>]? "
"Python doesn't support it, you have to use subclassing with a concrete type, like "
"class MySubtype(Name[ConcreteType])"
)

def serialize(self, node, appstruct: iface.IType) -> t.Mapping[str, t.Any]:
if appstruct is Null:
Expand Down

0 comments on commit bcad756

Please sign in to comment.