Skip to content

Commit

Permalink
Apply feedback:
Browse files Browse the repository at this point in the history
* Improve explanation
* fix typos
* Alias Parameterisation type
  • Loading branch information
diabolo-dan committed Mar 23, 2021
1 parent 2d690f9 commit 050b132
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 23 deletions.
52 changes: 33 additions & 19 deletions pydantic/generics.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@
GenericModelT = TypeVar('GenericModelT', bound='GenericModel')
TypeVarType = Any # since mypy doesn't allow the use of TypeVar as a type

_assigned_parameters: Dict[Type[Any], Dict[TypeVarType, Any]] = {}
Parameterization = Mapping[TypeVarType, Type[Any]]
_assigned_parameters: Dict[Type[Any], Parameterization] = {}


class GenericModel(BaseModel):
Expand Down Expand Up @@ -145,7 +146,7 @@ def __concrete_name__(cls: Type[Any], params: Tuple[Type[Any], ...]) -> str:
return f'{cls.__name__}[{params_component}]'

@classmethod
def __parameterized_bases__(cls, typevars_map: Mapping[TypeVarType, Type[Any]]) -> Iterator[Type[Any]]:
def __parameterized_bases__(cls, typevars_map: Parameterization) -> Iterator[Type[Any]]:
"""Returns unbound bases of cls parameterised to given type variables
Expand All @@ -156,40 +157,53 @@ def __parameterized_bases__(cls, typevars_map: Mapping[TypeVarType, Type[Any]])
:return: an iterator of generic sub classes, parameterised by `typevars_map`
and other assigned parameters of `cls`
e.g. for
e.g.:
```
class A(GenericModel, Generic[T]):
...
class B(A[V], Generic[V]):
...
```
then: `A[int] in B.__concrete_bases__({V: int})`
assert A[int] in B.__parameterized_bases__({V: int})
```
"""
if cls in _assigned_parameters:
# class is a partially "bound" form of a generic model
# cls is already (partially) "bound" form of a generic model
# e.g. cls = B[str, T]
# We need to determine the mapping for the base_model parameters
mapped_types: Mapping[TypeVarType, Type[Any]] = {
mapped_types: Parameterization = {
key: typevars_map.get(value, value) for key, value in _assigned_parameters[cls].items()
}
else:
mapped_types = typevars_map
for base_model in cls.__bases__:
if issubclass(base_model, GenericModel):
if not hasattr(base_model, '__parameters__') or not base_model.__parameters__:
# type is Generic or
# base model is already concrete, and will be included transitively.
continue
elif cls in _assigned_parameters and base_model in _assigned_parameters:
# Subclass will be inferred via cls
if not issubclass(base_model, GenericModel):
continue
elif not getattr(base_model, '__parameters__', ()):
# base_model is "GenericModel" (and has no __parameters__)
# or
# base_model is already concrete, and will be included transitively via cls.
continue
elif cls in _assigned_parameters and base_model in _assigned_parameters:
# e.g. cls = B[S], base_model = A[S]
# B[S][int] should subclass A[int], (and will be transitively via B[int])
# but it's not viable to consistently subclass types with arbitrary construction
# So don't attempt to include A[S][int]
print(cls, base_model, mapped_types)
continue
else:
# Extract parameters that base_model cares about then
# construct the Parameterised base model.
# e.g. cls=B, base_model=A,
# B[int
print('otherwise:', cls, base_model, mapped_types)
base_parameters = tuple([mapped_types[param] for param in base_model.__parameters__])
parameterized_base = base_model.__class_getitem__(base_parameters)
if parameterized_base is base_model or parameterized_base is cls:
# Avoid duplication in MRO
continue
else:
base_parameters = tuple([mapped_types[param] for param in base_model.__parameters__])
result = base_model.__class_getitem__(base_parameters)
if result != base_model and result != cls:
yield result
yield parameterized_base


def replace_types(type_: Any, type_map: Mapping[Any, Any]) -> Any:
Expand Down
8 changes: 4 additions & 4 deletions tests/test_generics.py
Original file line number Diff line number Diff line change
Expand Up @@ -1100,10 +1100,10 @@ class A(GenericModel, Generic[T]):
class B(A[S], Generic[T, S]):
...

PartialyAppliedB = B[str, T]
assert issubclass(PartialyAppliedB[int], A[int])
assert not issubclass(PartialyAppliedB[int], A[str])
assert not issubclass(PartialyAppliedB[str], A[int])
PartiallyAppliedB = B[str, T]
assert issubclass(PartiallyAppliedB[int], A[int])
assert not issubclass(PartiallyAppliedB[int], A[str])
assert not issubclass(PartiallyAppliedB[str], A[int])


@skip_36
Expand Down

0 comments on commit 050b132

Please sign in to comment.