TypedDictSchema has extras_schema and extra_behavior fields, as well as config: CoreConfig with an extra_fields_behavior field, but these are never read in JsonSchemaGenerator.typed_dict_schema. This may make sense if the schema has a cls, but it's valid to create one without, in which case the local config var will be an empty dict, and the extra = config.get('extra') stuff is a no-op.
JsonSchemaGenerator.typed_dict_schema:
|
def typed_dict_schema(self, schema: core_schema.TypedDictSchema) -> JsonSchemaValue: |
|
"""Generates a JSON schema that matches a schema that defines a typed dict. |
|
|
|
Args: |
|
schema: The core schema. |
|
|
|
Returns: |
|
The generated JSON schema. |
|
""" |
|
total = schema.get('total', True) |
|
named_required_fields: list[tuple[str, bool, CoreSchemaField]] = [ |
|
(name, self.field_is_required(field, total), field) |
|
for name, field in schema['fields'].items() |
|
if self.field_is_present(field) |
|
] |
|
if self.mode == 'serialization': |
|
named_required_fields.extend(self._name_required_computed_fields(schema.get('computed_fields', []))) |
|
cls = schema.get('cls') |
|
config = _get_typed_dict_config(cls) |
|
with self._config_wrapper_stack.push(config): |
|
json_schema = self._named_required_fields_schema(named_required_fields) |
|
|
|
if cls is not None: |
|
self._update_class_schema(json_schema, cls, config) |
|
else: |
|
extra = config.get('extra') |
|
if extra == 'forbid': |
|
json_schema['additionalProperties'] = False |
|
elif extra == 'allow': |
|
json_schema['additionalProperties'] = True |
|
|
|
return json_schema |
I ran into this in pydantic/pydantic-ai#2419, where we create a typed_dict_schema like so:
core_config = config_wrapper.core_config(None)
core_config['extra_fields_behavior'] = 'allow' if var_kwargs_schema else 'forbid'
# ...
td_schema = core_schema.typed_dict_schema(
fields,
config=core_config,
extras_schema=gen_schema.generate_schema(var_kwargs_schema) if var_kwargs_schema else None,
)
I worked around this by using our own generator:
class GenerateToolJsonSchema(GenerateJsonSchema):
def typed_dict_schema(self, schema: core_schema.TypedDictSchema) -> JsonSchemaValue:
json_schema = super().typed_dict_schema(schema)
if 'additionalProperties' not in json_schema:
extra = schema.get('extra_behavior') or schema.get('config', {}).get('extra_fields_behavior')
if extra == 'allow':
extras_schema = schema.get('extras_schema', None)
if extras_schema is not None:
json_schema['additionalProperties'] = self.generate_inner(extras_schema) or True
else:
json_schema['additionalProperties'] = True
elif extra == 'forbid':
json_schema['additionalProperties'] = False
return json_schema
TypedDictSchemahasextras_schemaandextra_behaviorfields, as well asconfig: CoreConfigwith anextra_fields_behaviorfield, but these are never read inJsonSchemaGenerator.typed_dict_schema. This may make sense if the schema has acls, but it's valid to create one without, in which case the localconfigvar will be an empty dict, and theextra = config.get('extra')stuff is a no-op.JsonSchemaGenerator.typed_dict_schema:pydantic/pydantic/json_schema.py
Lines 1397 to 1428 in 9b52422
I ran into this in pydantic/pydantic-ai#2419, where we create a
typed_dict_schemalike so:I worked around this by using our own generator: