Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Do not try to automatically handle subclasses of mappings #5744

Merged
merged 5 commits into from May 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
96 changes: 1 addition & 95 deletions pydantic/_internal/_generate_schema.py
Expand Up @@ -413,15 +413,6 @@ def _generate_schema(self, obj: Any) -> core_schema.CoreSchema: # noqa: C901
return self._pattern_schema(obj)
elif obj is collections.abc.Hashable or obj is typing.Hashable:
return self._hashable_schema()
elif isinstance(obj, type):
if obj is dict:
return self._dict_schema(obj)
if issubclass(obj, dict):
# TODO: We would need to handle generic subclasses of certain typing dict subclasses here
# This includes subclasses of typing.Counter, typing.DefaultDict, and typing.OrderedDict
# Note also that we may do a better job of handling typing.DefaultDict by inspecting its arguments.
return self._dict_subclass_schema(obj)
# probably need to take care of other subclasses here
elif isinstance(obj, typing.TypeVar):
return self._unsubstituted_typevar_schema(obj)
elif is_finalvar(obj):
Expand Down Expand Up @@ -473,26 +464,10 @@ def _generate_schema(self, obj: Any) -> core_schema.CoreSchema: # noqa: C901
elif issubclass(origin, typing.Tuple): # type: ignore[arg-type]
# TODO: To support generic subclasses of typing.Tuple, we need to better-introspect the args to origin
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this comment be removed? Not sure how you feel about generic subclasses of Tuple in the context of this PR

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should do this work for Tuple as well, but I'm not going to do it in this PR, it's already huge 😆

return self._tuple_schema(obj)
elif issubclass(origin, typing.Counter):
# Subclasses of typing.Counter may be handled as subclasses of dict; see note above
return self._counter_schema(obj)
elif origin in (typing.Dict, dict):
return self._dict_schema(obj)
elif is_typeddict(origin):
return self._typed_dict_schema(obj, origin)
elif issubclass(origin, typing.Dict):
# Subclasses of typing.Dict may be handled as subclasses of dict; see note above
return self._dict_subclass_schema(obj)
elif issubclass(origin, typing.Mapping):
# Because typing.Mapping does not have a specified `__init__` signature, we don't validate into subclasses
return self._mapping_schema(obj)
elif issubclass(origin, typing.Type): # type: ignore[arg-type]
return self._subclass_schema(obj)
elif issubclass(origin, typing.OrderedDict):
# Subclasses of typing.OrderedDict may be handled as subclasses of dict; see note above
from ._std_types_schema import ordered_dict_schema

return ordered_dict_schema(self, obj)
elif issubclass(origin, typing.Sequence):
if origin in {typing.Sequence, collections.abc.Sequence}:
return self._sequence_schema(obj)
Expand Down Expand Up @@ -822,76 +797,6 @@ def _tuple_schema(self, tuple_type: Any) -> core_schema.CoreSchema:
else:
return core_schema.tuple_positional_schema([self.generate_schema(p) for p in params])

def _dict_schema(self, dict_type: Any) -> core_schema.DictSchema:
"""
Generate schema for a Dict, e.g. `dict[str, int]`.
"""
try:
arg0, arg1 = get_args(dict_type)
except ValueError:
return core_schema.dict_schema()
else:
return core_schema.dict_schema(
keys_schema=self.generate_schema(arg0),
values_schema=self.generate_schema(arg1),
)

def _dict_subclass_schema(self, dict_subclass: Any) -> core_schema.CoreSchema:
"""
Generate schema for a subclass of dict or Dict
"""
try:
arg0, arg1 = get_args(dict_subclass)
except ValueError:
arg0, arg1 = Any, Any

from ._validators import mapping_validator

# TODO could do `core_schema.chain_schema(core_schema.is_instance_schema(dict_subclass), ...` in strict mode
return core_schema.no_info_wrap_validator_function(
mapping_validator,
core_schema.dict_schema(
keys_schema=self.generate_schema(arg0),
values_schema=self.generate_schema(arg1),
),
)

def _counter_schema(self, counter_type: Any) -> core_schema.CoreSchema:
"""
Generate schema for `typing.Counter`
"""
arg = get_first_arg(counter_type)

from ._validators import construct_counter

# TODO could do `core_schema.chain_schema(core_schema.is_instance_schema(Counter), ...` in strict mode
return core_schema.no_info_after_validator_function(
construct_counter,
core_schema.dict_schema(
keys_schema=self.generate_schema(arg),
values_schema=core_schema.int_schema(),
),
)

def _mapping_schema(self, mapping_type: Any) -> core_schema.CoreSchema:
"""
Generate schema for a Dict, e.g. `dict[str, int]`.
"""
try:
arg0, arg1 = get_args(mapping_type)
except ValueError:
return core_schema.is_instance_schema(typing.Mapping, cls_repr='Mapping')
else:
from ._validators import mapping_validator

return core_schema.no_info_wrap_validator_function(
mapping_validator,
core_schema.dict_schema(
keys_schema=self.generate_schema(arg0),
values_schema=self.generate_schema(arg1),
),
)

def _type_schema(self) -> core_schema.CoreSchema:
return core_schema.custom_error_schema(
core_schema.is_instance_schema(type),
Expand Down Expand Up @@ -1153,6 +1058,7 @@ def _get_prepare_pydantic_annotations_for_known_type(
std_types.datetime_prepare_pydantic_annotations,
std_types.uuid_prepare_pydantic_annotations,
std_types.path_schema_prepare_pydantic_annotations,
std_types.mapping_like_prepare_pydantic_annotations,
):
res = gen(obj, annotations)
if res is not None:
Expand Down