Coverage for pydantic/_internal/_schema_gather.py: 95.50%
124 statements
« prev ^ index » next coverage.py v7.6.12, created at 2025-02-13 19:35 +0000
« prev ^ index » next coverage.py v7.6.12, created at 2025-02-13 19:35 +0000
1# pyright: reportTypedDictNotRequiredAccess=false, reportGeneralTypeIssues=false, reportArgumentType=false, reportAttributeAccessIssue=false
2from __future__ import annotations 1zAabcdefghBiCDjklmnopqMGHIJKLEFrstuvwxy
4from dataclasses import dataclass, field 1zAabcdefghBiCDjklmnopqMGHIJKLEFrstuvwxy
5from typing import TypedDict 1zAabcdefghBiCDjklmnopqMGHIJKLEFrstuvwxy
7from pydantic_core.core_schema import ComputedField, CoreSchema, DefinitionReferenceSchema, SerSchema 1zAabcdefghBiCDjklmnopqMGHIJKLEFrstuvwxy
8from typing_extensions import TypeAlias 1zAabcdefghBiCDjklmnopqMGHIJKLEFrstuvwxy
10AllSchemas: TypeAlias = 'CoreSchema | SerSchema | ComputedField' 1zAabcdefghBiCDjklmnopqMGHIJKLEFrstuvwxy
13class GatherResult(TypedDict): 1zAabcdefghBiCDjklmnopqMGHIJKLEFrstuvwxy
14 """Schema traversing result."""
16 collected_references: dict[str, DefinitionReferenceSchema | None] 1zAabcdefghBiCDjklmnopqMGHIJKLEFrstuvwxy
17 """The collected definition references. 1abcdefghijklmnopqGHIJKLrstuvwxy
19 If a definition reference schema can be inlined, it means that there is
20 only one in the whole core schema. As such, it is stored as the value.
21 Otherwise, the value is set to `None`.
22 """
24 deferred_discriminator_schemas: list[CoreSchema] 1zAabcdefghBiCDjklmnopqMGHIJKLEFrstuvwxy
25 """The list of core schemas having the discriminator application deferred.""" 1zAabcdefghBiCDjklmnopqMGHIJKLEFrstuvwxy
28class MissingDefinitionError(LookupError): 1zAabcdefghBiCDjklmnopqMGHIJKLEFrstuvwxy
29 """A reference was pointing to a non-existing core schema."""
31 def __init__(self, schema_reference: str, /) -> None: 1zAabcdefghBiCDjklmnopqMGHIJKLEFrstuvwxy
32 self.schema_reference = schema_reference 1zAabcdefghBiCDjklmnopqEFrstuvwxy
35@dataclass 1zAabcdefghBiCDjklmnopqMGHIJKLEFrstuvwxy
36class GatherContext: 1zAabcdefghBiCDjklmnopqMGHIJKLEFrstuvwxy
37 """The current context used during core schema traversing.
39 Context instances should only be used during schema traversing.
40 """
42 definitions: dict[str, CoreSchema] 1zAabcdefghBiCDjklmnopqMGHIJKLEFrstuvwxy
43 """The available definitions.""" 1abcdefghijklmnopqGHIJKLrstuvwxy
45 deferred_discriminator_schemas: list[CoreSchema] = field(init=False, default_factory=list) 1zAabcdefghBiCDjklmnopqMGHIJKLEFrstuvwxy
46 """The list of core schemas having the discriminator application deferred. 1abcdefghijklmnopqGHIJKLrstuvwxy
48 Internally, these core schemas have a specific key set in the core metadata dict.
49 """
51 collected_references: dict[str, DefinitionReferenceSchema | None] = field(init=False, default_factory=dict) 1zAabcdefghBiCDjklmnopqMGHIJKLEFrstuvwxy
52 """The collected definition references. 1zAabcdefghBiCDjklmnopqMGHIJKLEFrstuvwxy
54 If a definition reference schema can be inlined, it means that there is
55 only one in the whole core schema. As such, it is stored as the value.
56 Otherwise, the value is set to `None`.
58 During schema traversing, definition reference schemas can be added as candidates, or removed
59 (by setting the value to `None`).
60 """
63def traverse_metadata(schema: AllSchemas, ctx: GatherContext) -> None: 1zAabcdefghBiCDjklmnopqMGHIJKLEFrstuvwxy
64 meta = schema.get('metadata') 1zAabcdefghBiCDjklmnopqMGHIJKLEFrstuvwxy
65 if meta is not None and 'pydantic_internal_union_discriminator' in meta: 1zAabcdefghBiCDjklmnopqMGHIJKLEFrstuvwxy
66 ctx.deferred_discriminator_schemas.append(schema) # pyright: ignore[reportArgumentType] 1zAabcdefghBiCDjklmnopqEFrstuvwxy
69def traverse_definition_ref(def_ref_schema: DefinitionReferenceSchema, ctx: GatherContext) -> None: 1zAabcdefghBiCDjklmnopqMGHIJKLEFrstuvwxy
70 schema_ref = def_ref_schema['schema_ref'] 1zAabcdefghBiCDjklmnopqMGHIJKLEFrstuvwxy
72 if schema_ref not in ctx.collected_references: 1zAabcdefghBiCDjklmnopqMGHIJKLEFrstuvwxy
73 definition = ctx.definitions.get(schema_ref) 1zAabcdefghBiCDjklmnopqMGHIJKLEFrstuvwxy
74 if definition is None: 1zAabcdefghBiCDjklmnopqMGHIJKLEFrstuvwxy
75 raise MissingDefinitionError(schema_ref) 1zAabcdefghBiCDjklmnopqEFrstuvwxy
77 # The `'definition-ref'` schema was only encountered once, make it
78 # a candidate to be inlined:
79 ctx.collected_references[schema_ref] = def_ref_schema 1zAabcdefghBiCDjklmnopqMGHIJKLEFrstuvwxy
80 traverse_schema(definition, ctx) 1zAabcdefghBiCDjklmnopqMGHIJKLEFrstuvwxy
81 if 'serialization' in def_ref_schema: 1zAabcdefghBiCDjklmnopqMGHIJKLEFrstuvwxy
82 traverse_schema(def_ref_schema['serialization'], ctx) 1zAabcdefghBiCDjklmnopqEFrstuvwxy
83 traverse_metadata(def_ref_schema, ctx) 1zAabcdefghBiCDjklmnopqMGHIJKLEFrstuvwxy
84 else:
85 # The `'definition-ref'` schema was already encountered, meaning
86 # the previously encountered schema (and this one) can't be inlined:
87 ctx.collected_references[schema_ref] = None 1zAabcdefghBiCDjklmnopqMGHIJKLEFrstuvwxy
90def traverse_schema(schema: AllSchemas, context: GatherContext) -> None: 1zAabcdefghBiCDjklmnopqMGHIJKLEFrstuvwxy
91 # TODO When we drop 3.9, use a match statement to get better type checking and remove
92 # file-level type ignore.
93 # (the `'type'` could also be fetched in every `if/elif` statement, but this alters performance).
94 schema_type = schema['type'] 1zAabcdefghBiCDjklmnopqMGHIJKLEFrstuvwxy
96 if schema_type == 'definition-ref': 1zAabcdefghBiCDjklmnopqMGHIJKLEFrstuvwxy
97 traverse_definition_ref(schema, context) 1zAabcdefghBiCDjklmnopqMGHIJKLEFrstuvwxy
98 # `traverse_definition_ref` handles the possible serialization and metadata schemas:
99 return 1zAabcdefghBiCDjklmnopqMGHIJKLEFrstuvwxy
100 elif schema_type == 'definitions': 100 ↛ 101line 100 didn't jump to line 101 because the condition on line 100 was never true1zAabcdefghBiCDjklmnopqMGHIJKLEFrstuvwxy
101 traverse_schema(schema['schema'], context)
102 for definition in schema['definitions']:
103 traverse_schema(definition, context)
104 elif schema_type in {'list', 'set', 'frozenset', 'generator'}: 1zAabcdefghBiCDjklmnopqMGHIJKLEFrstuvwxy
105 if 'items_schema' in schema: 1zAabcdefghBiCDjklmnopqMGHIJKLEFrstuvwxy
106 traverse_schema(schema['items_schema'], context) 1zAabcdefghBiCDjklmnopqMGHIJKLEFrstuvwxy
107 elif schema_type == 'tuple': 1zAabcdefghBiCDjklmnopqMGHIJKLEFrstuvwxy
108 if 'items_schema' in schema: 108 ↛ 179line 108 didn't jump to line 179 because the condition on line 108 was always true1zAabcdefghBiCDjklmnopqEFrstuvwxy
109 for s in schema['items_schema']: 1zAabcdefghBiCDjklmnopqEFrstuvwxy
110 traverse_schema(s, context) 1zAabcdefghBiCDjklmnopqEFrstuvwxy
111 elif schema_type == 'dict': 1zAabcdefghBiCDjklmnopqMGHIJKLEFrstuvwxy
112 if 'keys_schema' in schema: 112 ↛ 114line 112 didn't jump to line 114 because the condition on line 112 was always true1zAabcdefghBiCDjklmnopqEFrstuvwxy
113 traverse_schema(schema['keys_schema'], context) 1zAabcdefghBiCDjklmnopqEFrstuvwxy
114 if 'values_schema' in schema: 114 ↛ 179line 114 didn't jump to line 179 because the condition on line 114 was always true1zAabcdefghBiCDjklmnopqEFrstuvwxy
115 traverse_schema(schema['values_schema'], context) 1zAabcdefghBiCDjklmnopqEFrstuvwxy
116 elif schema_type == 'union': 1zAabcdefghBiCDjklmnopqMGHIJKLEFrstuvwxy
117 for choice in schema['choices']: 1zAabcdefghBiCDjklmnopqEFrstuvwxy
118 if isinstance(choice, tuple): 1zAabcdefghBiCDjklmnopqEFrstuvwxy
119 traverse_schema(choice[0], context) 1zAabcdefghBiCDjklmnopqEFrstuvwxy
120 else:
121 traverse_schema(choice, context) 1zAabcdefghBiCDjklmnopqEFrstuvwxy
122 elif schema_type == 'tagged-union': 1zAabcdefghBiCDjklmnopqMGHIJKLEFrstuvwxy
123 for v in schema['choices'].values(): 1zAabcdefghBiCDjklmnopqEFrstuvwxy
124 traverse_schema(v, context) 1zAabcdefghBiCDjklmnopqEFrstuvwxy
125 elif schema_type == 'chain': 1zAabcdefghBiCDjklmnopqMGHIJKLEFrstuvwxy
126 for step in schema['steps']: 1zAabcdefghBiCDjklmnopqEFrstuvwxy
127 traverse_schema(step, context) 1zAabcdefghBiCDjklmnopqEFrstuvwxy
128 elif schema_type == 'lax-or-strict': 1zAabcdefghBiCDjklmnopqMGHIJKLEFrstuvwxy
129 traverse_schema(schema['lax_schema'], context) 1zAabcdefghBiCDjklmnopqEFrstuvwxy
130 traverse_schema(schema['strict_schema'], context) 1zAabcdefghBiCDjklmnopqEFrstuvwxy
131 elif schema_type == 'json-or-python': 1zAabcdefghBiCDjklmnopqMGHIJKLEFrstuvwxy
132 traverse_schema(schema['json_schema'], context) 1zAabcdefghBiCDjklmnopqEFrstuvwxy
133 traverse_schema(schema['python_schema'], context) 1zAabcdefghBiCDjklmnopqEFrstuvwxy
134 elif schema_type in {'model-fields', 'typed-dict'}: 1zAabcdefghBiCDjklmnopqMGHIJKLEFrstuvwxy
135 if 'extras_schema' in schema: 1zAabcdefghBiCDjklmnopqMGHIJKLEFrstuvwxy
136 traverse_schema(schema['extras_schema'], context) 1zAabcdefghBiCDjklmnopqEFrstuvwxy
137 if 'computed_fields' in schema: 1zAabcdefghBiCDjklmnopqMGHIJKLEFrstuvwxy
138 for s in schema['computed_fields']: 1zAabcdefghBiCDjklmnopqMGHIJKLEFrstuvwxy
139 traverse_schema(s, context) 1zAabcdefghBiCDjklmnopqEFrstuvwxy
140 for s in schema['fields'].values(): 1zAabcdefghBiCDjklmnopqMGHIJKLEFrstuvwxy
141 traverse_schema(s, context) 1zAabcdefghBiCDjklmnopqMGHIJKLEFrstuvwxy
142 elif schema_type == 'dataclass-args': 1zAabcdefghBiCDjklmnopqMGHIJKLEFrstuvwxy
143 if 'computed_fields' in schema: 143 ↛ 146line 143 didn't jump to line 146 because the condition on line 143 was always true1zAabcdefghBiCDjklmnopqMGHIJKLEFrstuvwxy
144 for s in schema['computed_fields']: 1zAabcdefghBiCDjklmnopqMGHIJKLEFrstuvwxy
145 traverse_schema(s, context) 1zAabcdefghBiCDjklmnopqEFrstuvwxy
146 for s in schema['fields']: 1zAabcdefghBiCDjklmnopqMGHIJKLEFrstuvwxy
147 traverse_schema(s, context) 1zAabcdefghBiCDjklmnopqMGHIJKLEFrstuvwxy
148 elif schema_type == 'arguments': 1zAabcdefghBiCDjklmnopqMGHIJKLEFrstuvwxy
149 for s in schema['arguments_schema']: 1zAabcdefghBiCDjklmnopqEFrstuvwxy
150 traverse_schema(s['schema'], context) 1zAabcdefghBiCDjklmnopqEFrstuvwxy
151 if 'var_args_schema' in schema: 1zAabcdefghBiCDjklmnopqEFrstuvwxy
152 traverse_schema(schema['var_args_schema'], context) 1zAabcdefghBiCDjklmnopqEFrstuvwxy
153 if 'var_kwargs_schema' in schema: 1zAabcdefghBiCDjklmnopqEFrstuvwxy
154 traverse_schema(schema['var_kwargs_schema'], context) 1zAabcdefghBiCDjklmnopqEFrstuvwxy
155 elif schema_type == 'call': 1zAabcdefghBiCDjklmnopqMGHIJKLEFrstuvwxy
156 traverse_schema(schema['arguments_schema'], context) 1zAabcdefghBiCDjklmnopqEFrstuvwxy
157 if 'return_schema' in schema: 1zAabcdefghBiCDjklmnopqEFrstuvwxy
158 traverse_schema(schema['return_schema'], context) 1zAabcdefghBiCDjklmnopqEFrstuvwxy
159 elif schema_type == 'computed-field': 1zAabcdefghBiCDjklmnopqMGHIJKLEFrstuvwxy
160 traverse_schema(schema['return_schema'], context) 1zAabcdefghBiCDjklmnopqEFrstuvwxy
161 elif schema_type == 'function-plain': 1zAabcdefghBiCDjklmnopqMGHIJKLEFrstuvwxy
162 # TODO duplicate schema types for serializers and validators, needs to be deduplicated.
163 if 'return_schema' in schema: 1zAabcdefghBiCDjklmnopqEFrstuvwxy
164 traverse_schema(schema['return_schema'], context) 1zAabcdefghBiCDjklmnopqEFrstuvwxy
165 if 'json_schema_input_schema' in schema: 1zAabcdefghBiCDjklmnopqEFrstuvwxy
166 traverse_schema(schema['json_schema_input_schema'], context) 1zAabcdefghBiCDjklmnopqEFrstuvwxy
167 elif schema_type == 'function-wrap': 1zAabcdefghBiCDjklmnopqMGHIJKLEFrstuvwxy
168 # TODO duplicate schema types for serializers and validators, needs to be deduplicated.
169 if 'return_schema' in schema: 1zAabcdefghBiCDjklmnopqEFrstuvwxy
170 traverse_schema(schema['return_schema'], context) 1zAabcdefghBiCDjklmnopqEFrstuvwxy
171 if 'schema' in schema: 1zAabcdefghBiCDjklmnopqEFrstuvwxy
172 traverse_schema(schema['schema'], context) 1zAabcdefghBiCDjklmnopqEFrstuvwxy
173 if 'json_schema_input_schema' in schema: 1zAabcdefghBiCDjklmnopqEFrstuvwxy
174 traverse_schema(schema['json_schema_input_schema'], context) 1zAabcdefghBiCDjklmnopqEFrstuvwxy
175 else:
176 if 'schema' in schema: 1zAabcdefghBiCDjklmnopqMGHIJKLEFrstuvwxy
177 traverse_schema(schema['schema'], context) 1zAabcdefghBiCDjklmnopqMGHIJKLEFrstuvwxy
179 if 'serialization' in schema: 1zAabcdefghBiCDjklmnopqMGHIJKLEFrstuvwxy
180 traverse_schema(schema['serialization'], context) 1zAabcdefghBiCDjklmnopqEFrstuvwxy
181 traverse_metadata(schema, context) 1zAabcdefghBiCDjklmnopqMGHIJKLEFrstuvwxy
184def gather_schemas_for_cleaning(schema: CoreSchema, definitions: dict[str, CoreSchema]) -> GatherResult: 1zAabcdefghBiCDjklmnopqMGHIJKLEFrstuvwxy
185 """Traverse the core schema and definitions and return the necessary information for schema cleaning.
187 During the core schema traversing, any `'definition-ref'` schema is:
189 - Validated: the reference must point to an existing definition. If this is not the case, a
190 `MissingDefinitionError` exception is raised.
191 - Stored in the context: the actual reference is stored in the context. Depending on whether
192 the `'definition-ref'` schema is encountered more that once, the schema itself is also
193 saved in the context to be inlined (i.e. replaced by the definition it points to).
194 """
195 context = GatherContext(definitions) 1zAabcdefghBiCDjklmnopqMGHIJKLEFrstuvwxy
196 traverse_schema(schema, context) 1zAabcdefghBiCDjklmnopqMGHIJKLEFrstuvwxy
198 return { 1zAabcdefghBiCDjklmnopqMGHIJKLEFrstuvwxy
199 'collected_references': context.collected_references,
200 'deferred_discriminator_schemas': context.deferred_discriminator_schemas,
201 }