Skip to content

Commit

Permalink
Add examples and json_schema_extra to @computed_field (#8013)
Browse files Browse the repository at this point in the history
  • Loading branch information
alexmojaki committed Nov 6, 2023
1 parent 1853b6b commit 5576936
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 29 deletions.
58 changes: 35 additions & 23 deletions pydantic/_internal/_generate_schema.py
Expand Up @@ -37,7 +37,7 @@
from typing_extensions import Annotated, Final, Literal, TypeAliasType, TypedDict, get_args, get_origin, is_typeddict

from ..annotated_handlers import GetCoreSchemaHandler, GetJsonSchemaHandler
from ..config import ConfigDict, JsonEncoder
from ..config import ConfigDict, JsonDict, JsonEncoder
from ..errors import PydanticSchemaGenerationError, PydanticUndefinedAnnotation, PydanticUserError
from ..json_schema import JsonSchemaValue
from ..version import version_short
Expand Down Expand Up @@ -987,15 +987,9 @@ def set_discriminator(schema: CoreSchema) -> CoreSchema:

json_schema_extra = field_info.json_schema_extra

def json_schema_update_func(schema: CoreSchemaOrField, handler: GetJsonSchemaHandler) -> JsonSchemaValue:
json_schema = {**handler(schema), **json_schema_updates}
if isinstance(json_schema_extra, dict):
json_schema.update(to_jsonable_python(json_schema_extra))
elif callable(json_schema_extra):
json_schema_extra(json_schema)
return json_schema

metadata = build_metadata_dict(js_annotation_functions=[json_schema_update_func])
metadata = build_metadata_dict(
js_annotation_functions=[get_json_schema_update_func(json_schema_updates, json_schema_extra)]
)

# apply alias generator
alias_generator = self._config_wrapper.alias_generator
Expand Down Expand Up @@ -1566,6 +1560,14 @@ def set_computed_field_metadata(schema: CoreSchemaOrField, handler: GetJsonSchem
if description is not None:
json_schema['description'] = description

examples = d.info.examples
if examples is not None:
json_schema['examples'] = to_jsonable_python(examples)

json_schema_extra = d.info.json_schema_extra
if json_schema_extra is not None:
add_json_schema_extra(json_schema, json_schema_extra)

return json_schema

metadata = build_metadata_dict(js_annotation_functions=[set_computed_field_metadata])
Expand Down Expand Up @@ -1715,20 +1717,8 @@ def _apply_single_annotation_json_schema(

json_schema_extra = metadata.json_schema_extra
if json_schema_update or json_schema_extra:

def json_schema_update_func(
core_schema: CoreSchemaOrField, handler: GetJsonSchemaHandler
) -> JsonSchemaValue:
json_schema = handler(core_schema)
json_schema.update(json_schema_update)
if isinstance(json_schema_extra, dict):
json_schema.update(to_jsonable_python(json_schema_extra))
elif callable(json_schema_extra):
json_schema_extra(json_schema)
return json_schema

CoreMetadataHandler(schema).metadata.setdefault('pydantic_js_annotation_functions', []).append(
json_schema_update_func
get_json_schema_update_func(json_schema_update, json_schema_extra)
)
return schema

Expand Down Expand Up @@ -2013,6 +2003,28 @@ def _extract_get_pydantic_json_schema(tp: Any, schema: CoreSchema) -> GetJsonSch
return js_modify_function


def get_json_schema_update_func(
json_schema_update: JsonSchemaValue, json_schema_extra: JsonDict | typing.Callable[[JsonDict], None] | None
) -> GetJsonSchemaFunction:
def json_schema_update_func(
core_schema_or_field: CoreSchemaOrField, handler: GetJsonSchemaHandler
) -> JsonSchemaValue:
json_schema = {**handler(core_schema_or_field), **json_schema_update}
add_json_schema_extra(json_schema, json_schema_extra)
return json_schema

return json_schema_update_func


def add_json_schema_extra(
json_schema: JsonSchemaValue, json_schema_extra: JsonDict | typing.Callable[[JsonDict], None] | None
):
if isinstance(json_schema_extra, dict):
json_schema.update(to_jsonable_python(json_schema_extra))
elif callable(json_schema_extra):
json_schema_extra(json_schema)


class _CommonField(TypedDict):
schema: core_schema.CoreSchema
validation_alias: str | list[str | int] | list[list[str | int]] | None
Expand Down
22 changes: 17 additions & 5 deletions pydantic/fields.py
Expand Up @@ -962,6 +962,8 @@ class ComputedFieldInfo:
alias_priority: priority of the alias. This affects whether an alias generator is used
title: Title of the computed field as in OpenAPI document, should be a short summary.
description: Description of the computed field as in OpenAPI document.
examples: Example values of the computed field as in OpenAPI document.
json_schema_extra: Dictionary of extra JSON schema properties.
repr: A boolean indicating whether or not to include the field in the __repr__ output.
"""

Expand All @@ -972,6 +974,8 @@ class ComputedFieldInfo:
alias_priority: int | None
title: str | None
description: str | None
examples: list[Any] | None
json_schema_extra: JsonDict | typing.Callable[[JsonDict], None] | None
repr: bool


Expand All @@ -983,12 +987,14 @@ class ComputedFieldInfo:
@typing.overload
def computed_field(
*,
return_type: Any = PydanticUndefined,
alias: str | None = None,
alias_priority: int | None = None,
title: str | None = None,
description: str | None = None,
examples: list[Any] | None = None,
json_schema_extra: JsonDict | typing.Callable[[JsonDict], None] | None = None,
repr: bool = True,
return_type: Any = PydanticUndefined,
) -> typing.Callable[[PropertyT], PropertyT]:
...

Expand Down Expand Up @@ -1017,6 +1023,8 @@ def computed_field(
alias_priority: int | None = None,
title: str | None = None,
description: str | None = None,
examples: list[Any] | None = None,
json_schema_extra: JsonDict | typing.Callable[[JsonDict], None] | None = None,
repr: bool | None = None,
return_type: Any = PydanticUndefined,
) -> PropertyT | typing.Callable[[PropertyT], PropertyT]:
Expand Down Expand Up @@ -1140,9 +1148,11 @@ def _private_property(self) -> int:
__f: the function to wrap.
alias: alias to use when serializing this computed field, only used when `by_alias=True`
alias_priority: priority of the alias. This affects whether an alias generator is used
title: Title to used when including this computed field in JSON Schema, currently unused waiting for #4697
description: Description to used when including this computed field in JSON Schema, defaults to the functions
docstring, currently unused waiting for #4697
title: Title to use when including this computed field in JSON Schema
description: Description to use when including this computed field in JSON Schema, defaults to the function's
docstring
examples: Example values to use when including this computed field in JSON Schema
json_schema_extra: Dictionary of extra JSON schema properties.
repr: whether to include this computed field in model repr.
Default is `False` for private properties and `True` for public properties.
return_type: optional return for serialization logic to expect when serializing to JSON, if included
Expand All @@ -1169,7 +1179,9 @@ def dec(f: Any) -> Any:
else:
repr_ = repr

dec_info = ComputedFieldInfo(f, return_type, alias, alias_priority, title, description, repr_)
dec_info = ComputedFieldInfo(
f, return_type, alias, alias_priority, title, description, examples, json_schema_extra, repr_
)
return _decorators.PydanticDescriptorProxy(f, dec_info)

if __f is None:
Expand Down
9 changes: 8 additions & 1 deletion tests/test_computed_fields.py
Expand Up @@ -73,7 +73,12 @@ def area(self) -> int:
"""An awesome area"""
return self.width * self.length

@computed_field(title='Pikarea', description='Another area')
@computed_field(
title='Pikarea',
description='Another area',
examples=[100, 200],
json_schema_extra={'foo': 42},
)
@property
def area2(self) -> int:
return self.width * self.length
Expand Down Expand Up @@ -103,6 +108,8 @@ def double_width(self) -> int:
'area2': {
'title': 'Pikarea',
'description': 'Another area',
'examples': [100, 200],
'foo': 42,
'type': 'integer',
'readOnly': True,
},
Expand Down

0 comments on commit 5576936

Please sign in to comment.