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

Use tuple-schema from pydantic-core branch #7198

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions docs/plugins/conversion_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -682,50 +682,50 @@ def filtered(self, predicate: typing.Callable[[Row], bool]) -> ConversionTable:
tuple,
strict=True,
python_input=True,
core_schemas=[core_schema.TuplePositionalSchema, core_schema.TupleVariableSchema],
core_schemas=[core_schema.TupleSchema],
),
Row(
tuple,
'Array',
strict=True,
json_input=True,
core_schemas=[core_schema.TuplePositionalSchema, core_schema.TupleVariableSchema],
core_schemas=[core_schema.TupleSchema],
),
Row(
tuple,
list,
python_input=True,
core_schemas=[core_schema.TuplePositionalSchema, core_schema.TupleVariableSchema],
core_schemas=[core_schema.TupleSchema],
),
Row(
tuple,
set,
python_input=True,
core_schemas=[core_schema.TuplePositionalSchema, core_schema.TupleVariableSchema],
core_schemas=[core_schema.TupleSchema],
),
Row(
tuple,
frozenset,
python_input=True,
core_schemas=[core_schema.TuplePositionalSchema, core_schema.TupleVariableSchema],
core_schemas=[core_schema.TupleSchema],
),
Row(
tuple,
deque,
python_input=True,
core_schemas=[core_schema.TuplePositionalSchema, core_schema.TupleVariableSchema],
core_schemas=[core_schema.TupleSchema],
),
Row(
tuple,
'dict_keys',
python_input=True,
core_schemas=[core_schema.TuplePositionalSchema, core_schema.TupleVariableSchema],
core_schemas=[core_schema.TupleSchema],
),
Row(
tuple,
'dict_values',
python_input=True,
core_schemas=[core_schema.TuplePositionalSchema, core_schema.TupleVariableSchema],
core_schemas=[core_schema.TupleSchema],
),
Row(
set,
Expand Down
27 changes: 5 additions & 22 deletions pydantic/_internal/_core_utils.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from __future__ import annotations

from collections import defaultdict
from typing import Any, Callable, Hashable, Iterable, TypeVar, Union, cast
from typing import Any, Callable, Hashable, Iterable, TypeVar, Union

from pydantic_core import CoreSchema, core_schema
from typing_extensions import TypeAliasType, TypeGuard, get_args
Expand Down Expand Up @@ -29,7 +29,7 @@

_CORE_SCHEMA_FIELD_TYPES = {'typed-dict-field', 'dataclass-field', 'model-field', 'computed-field'}
_FUNCTION_WITH_INNER_SCHEMA_TYPES = {'function-before', 'function-after', 'function-wrap'}
_LIST_LIKE_SCHEMA_WITH_ITEMS_TYPES = {'list', 'tuple-variable', 'set', 'frozenset'}
_LIST_LIKE_SCHEMA_WITH_ITEMS_TYPES = {'list', 'set', 'frozenset'}


def is_core_schema(
Expand All @@ -52,9 +52,7 @@ def is_function_with_inner_schema(

def is_list_like_schema_with_items_schema(
schema: CoreSchema,
) -> TypeGuard[
core_schema.ListSchema | core_schema.TupleVariableSchema | core_schema.SetSchema | core_schema.FrozenSetSchema
]:
) -> TypeGuard[core_schema.ListSchema | core_schema.SetSchema | core_schema.FrozenSetSchema]:
return schema['type'] in _LIST_LIKE_SCHEMA_WITH_ITEMS_TYPES


Expand Down Expand Up @@ -248,23 +246,8 @@ def handle_generator_schema(self, schema: core_schema.GeneratorSchema, f: Walk)
schema['items_schema'] = self.walk(items_schema, f)
return schema

def handle_tuple_variable_schema(
self, schema: core_schema.TupleVariableSchema | core_schema.TuplePositionalSchema, f: Walk
) -> core_schema.CoreSchema:
schema = cast(core_schema.TupleVariableSchema, schema)
items_schema = schema.get('items_schema')
if items_schema is not None:
schema['items_schema'] = self.walk(items_schema, f)
return schema

def handle_tuple_positional_schema(
self, schema: core_schema.TupleVariableSchema | core_schema.TuplePositionalSchema, f: Walk
) -> core_schema.CoreSchema:
schema = cast(core_schema.TuplePositionalSchema, schema)
schema['items_schema'] = [self.walk(v, f) for v in schema['items_schema']]
extra_schema = schema.get('extra_schema')
if extra_schema is not None:
schema['extra_schema'] = self.walk(extra_schema, f)
def handle_tuple_schema(self, schema: core_schema.TupleSchema, f: Walk) -> core_schema.CoreSchema:
schema['items_schema'] = [self.walk(x, f) for x in schema['items_schema']]
return schema

def handle_dict_schema(self, schema: core_schema.DictSchema, f: Walk) -> core_schema.CoreSchema:
Expand Down
4 changes: 4 additions & 0 deletions pydantic/_internal/_generate_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,10 @@ def apply_each_item_validators(
if inner_schema is None:
inner_schema = core_schema.any_schema()
schema['items_schema'] = apply_validators(inner_schema, each_item_validators, field_name)
elif schema['type'] == 'tuple':
inner_schemas = schema['items_schema']
if len(inner_schemas) == 1 and schema.get('variadic_item_index') == 0:
schema['items_schema'] = [apply_validators(inner_schemas[0], each_item_validators, field_name)]
elif schema['type'] == 'dict':
# push down any `each_item=True` validators onto dict _values_
# this is super arbitrary but it's the V1 behavior
Expand Down
43 changes: 23 additions & 20 deletions pydantic/json_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -781,7 +781,7 @@ def list_schema(self, schema: core_schema.ListSchema) -> JsonSchemaValue:
self.update_with_validations(json_schema, schema, self.ValidationsMapping.array)
return json_schema

def tuple_positional_schema(self, schema: core_schema.TuplePositionalSchema) -> JsonSchemaValue:
def tuple_schema(self, schema: core_schema.TupleSchema) -> JsonSchemaValue:
"""Generates a JSON schema that matches a positional tuple schema e.g. `Tuple[int, str, bool]`.

Args:
Expand All @@ -790,29 +790,32 @@ def tuple_positional_schema(self, schema: core_schema.TuplePositionalSchema) ->
Returns:
The generated JSON schema.
"""
json_schema: JsonSchemaValue = {'type': 'array'}
json_schema['minItems'] = len(schema['items_schema'])
prefixItems = [self.generate_inner(item) for item in schema['items_schema']]
if prefixItems:
json_schema['prefixItems'] = prefixItems
if 'extra_schema' in schema:
json_schema['items'] = self.generate_inner(schema['extra_schema'])
items_schema = schema['items_schema']
variadic_item_index = schema.get('variadic_item_index')

if variadic_item_index is None:
prefix_items = [self.generate_inner(item) for item in items_schema]
suffix_items = []
min_items = len(items_schema)
max_items: int | None = len(items_schema)
else:
json_schema['maxItems'] = len(schema['items_schema'])
self.update_with_validations(json_schema, schema, self.ValidationsMapping.array)
return json_schema
prefix_items = [self.generate_inner(item) for item in items_schema[:variadic_item_index]]
# Note the first suffix item might be repeated, but JSON schema can't express this
suffix_items = [self.generate_inner(item) for item in items_schema[variadic_item_index:]]
min_items = len(items_schema) - 1 or None # don't specify min items for variable-length tuple with no min
max_items = None

def tuple_variable_schema(self, schema: core_schema.TupleVariableSchema) -> JsonSchemaValue:
"""Generates a JSON schema that matches a variable tuple schema e.g. `Tuple[int, ...]`.
json_schema: JsonSchemaValue = {'type': 'array'}
if min_items is not None:
json_schema['minItems'] = min_items
if max_items is not None:
json_schema['maxItems'] = max_items

Args:
schema: The core schema.
if prefix_items:
json_schema['prefixItems'] = prefix_items
if suffix_items:
json_schema['items'] = self.get_flattened_anyof(suffix_items)

Returns:
The generated JSON schema.
"""
items_schema = {} if 'items_schema' not in schema else self.generate_inner(schema['items_schema'])
json_schema = {'type': 'array', 'items': items_schema}
self.update_with_validations(json_schema, schema, self.ValidationsMapping.array)
return json_schema

Expand Down
28 changes: 0 additions & 28 deletions tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -542,34 +542,6 @@ def test_camel2snake(value: str, result: str) -> None:
assert to_snake(value) == result


@pytest.mark.parametrize(
'params,expected_extra_schema',
(
pytest.param({}, {}, id='Positional tuple without extra_schema'),
pytest.param(
{'extra_schema': core_schema.float_schema()},
{'extra_schema': {'type': 'str'}},
id='Positional tuple with extra_schema',
),
),
)
def test_handle_tuple_positional_schema(params, expected_extra_schema):
schema = core_schema.tuple_positional_schema([core_schema.str_schema()], **params)

def walk(s, recurse):
# change extra_schema['type'] to 'str'
if s['type'] == 'float':
s['type'] = 'str'
return s

schema = _WalkCoreSchema().handle_tuple_positional_schema(schema, walk)
assert schema == {
**expected_extra_schema,
'items_schema': [{'type': 'str'}],
'type': 'tuple-positional',
}


@pytest.mark.parametrize(
'params,expected_extra_schema',
(
Expand Down
Loading