Skip to content

Commit

Permalink
Release 3.11.1.3
Browse files Browse the repository at this point in the history
  • Loading branch information
avanov committed Oct 27, 2023
1 parent bcad756 commit 9d31bbc
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 33 deletions.
4 changes: 2 additions & 2 deletions CHANGELOG.rst
Expand Up @@ -2,11 +2,11 @@
CHANGELOG
=========

3.11.1.2
3.11.1.3
========

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


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.2'
release = '3.11.1.3'

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

from pvectorc import pvector

import typeit


A = TypeVar('A')

@dataclass(frozen=True, slots=True)
class X(Generic[A]):
class Entity(Generic[A]):
""" A generic representation of a database-stored entity.
"""
pk: int
entry: A


def test_generic():
class Item(NamedTuple):
value: str
class InnerEntry(NamedTuple):
entry_id: int
entry_name: str


class Item(NamedTuple):
item_id: int
inner: InnerEntry


class Concrete(X[Item]):
pass
class PersistedItem(Entity[Item]):
pass

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

mk_wrapper, serialize_wrapper = typeit.TypeConstructor ^ Wrapper
class DatabaseResponse(NamedTuple):
name: str
items: Sequence[PersistedItem] = pvector()


def test_generic():

mk_response, serialize_response = typeit.TypeConstructor ^ DatabaseResponse

serialized = {
"vals": [
"name": "response",
"items": [
{
"pk": 1,
"entry": {
"value": "item value"
"item_id": 1,
"inner": {
"entry_id": 2,
"entry_name": "entry_name",
}
}
}
]
}
assert serialize_wrapper(mk_wrapper(serialized)) == serialized
x = serialize_response(mk_response(serialized))
json.dumps(x)
assert x == serialized
51 changes: 34 additions & 17 deletions typeit/parser/__init__.py
Expand Up @@ -107,6 +107,10 @@ def _maybe_node_for_primitive(
return schema.nodes.SchemaNode(schema_type), memo, forward_refs


def is_type_var_placeholder(t) -> bool:
return isinstance(t, TypeVar)


def _maybe_node_for_type_var(
typ: Type,
overrides: OverridesT,
Expand All @@ -118,7 +122,7 @@ def _maybe_node_for_type_var(
as TypeVar. Since it's an indicator of a generic collection,
we can treat it as typing.Any.
"""
if isinstance(typ, TypeVar):
if is_type_var_placeholder(typ):
return _maybe_node_for_primitive(Any, overrides, memo, forward_refs)
return None, memo, forward_refs

Expand Down Expand Up @@ -487,25 +491,38 @@ def _maybe_node_for_user_type(
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, ...]
# e.g. we map Entity[T,U,V, ...] to actual types of Entity[int, float, str, ...]
generic_repr = insp.get_generic_bases(hints_source)
generic_vars_ordered = (insp.get_args(x)[0] for x in generic_repr)
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.
# 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)
)
]
if type_var_to_type:
# 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)
)
]
else:
# Here we have a situation with a concrete type after the generic type is clarified
# into a concrete type, i.e. when we have Entity[T] and later:
# class MyType(Entity[MyOtherType]): ...
#
# In this situation type_var_to_type will be empty, and we need to infer attributes
# of MyType via concrete types
clarified = iter(generic_vars_ordered)
attribute_hints = [
(attr_name, next(clarified) if is_type_var_placeholder(raw_type) else raw_type)
for attr_name, _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

0 comments on commit 9d31bbc

Please sign in to comment.