Skip to content

Conversation

Viicos
Copy link
Member

@Viicos Viicos commented Aug 13, 2024

Fixes #10118

Change Summary

Related issue number

Checklist

  • The pull request title is a good summary of the changes - it will be used in the changelog
  • Unit tests for the changes exist
  • Tests pass on CI
  • Documentation reflects the changes where applicable
  • My PR is ready to review, please add a comment including the phrase "please review" to assign reviewers

@github-actions github-actions bot added the relnotes-fix Used for bugfixes. label Aug 13, 2024
Copy link

cloudflare-workers-and-pages bot commented Aug 13, 2024

Deploying pydantic-docs with  Cloudflare Pages  Cloudflare Pages

Latest commit: eb1570b
Status: ✅  Deploy successful!
Preview URL: https://41c39d23.pydantic-docs.pages.dev
Branch Preview URL: https://10118-plain-json-schema.pydantic-docs.pages.dev

View logs

Copy link

codspeed-hq bot commented Aug 13, 2024

CodSpeed Performance Report

Merging #10121 will not alter performance

Comparing 10118-plain-json-schema (eb1570b) with main (f0d8f65)

Summary

✅ 17 untouched benchmarks

Comment on lines +6269 to +6273
assert Model.model_json_schema(mode='serialization') == {
'properties': {'custom_decimal': {'default': None, 'title': 'Custom Decimal', 'type': 'number'}},
'title': 'Model',
'type': 'object',
}
Copy link
Member Author

Choose a reason for hiding this comment

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

This revealed another issue with the JSON Schema: type should be 'anyOf': [{'type': 'number'}, {'type': 'null'}]}.

When the JSON Schema for our field is created, schema is of type default, and schema['schema'] is of type nullable:

def default_schema(self, schema: core_schema.WithDefaultSchema) -> JsonSchemaValue:
"""Generates a JSON schema that matches a schema with a default value.
Args:
schema: The core schema.
Returns:
The generated JSON schema.
"""
json_schema = self.generate_inner(schema['schema'])

However, self.generate_inner(schema['schema']) will not go through nullable_schema as we would expect. As there's a serialization key, ser_schema is being used:

if self.mode == 'serialization' and 'serialization' in schema_or_field:
ser_schema = schema_or_field['serialization'] # type: ignore
json_schema = self.ser_schema(ser_schema)

This makes sense, however, the issue is ser_schema kind of assumes when_used is always. I'll see if I can fix this in a follow up PR.

and (ser_schema := schema['schema'].get('serialization'))
and (ser_func := ser_schema.get('function'))
and ser_schema.get('type') == 'function-plain'
and not ser_schema.get('info_arg')
Copy link
Member Author

@Viicos Viicos Aug 13, 2024

Choose a reason for hiding this comment

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

Switching from and ser_schema.get('info_arg') is False releaved an issue in the tests, that I fixed in 0eed384.

…th `when_used` set to `'json-unless-none'` and the default value is `None`
@Viicos Viicos force-pushed the 10118-plain-json-schema branch from acef83b to 4396bb3 Compare August 13, 2024 12:01
Copy link
Contributor

github-actions bot commented Aug 13, 2024

Coverage report

Click to see where and how coverage changed

FileStatementsMissingCoverageCoverage
(new stmts)
Lines missing
  pydantic
  config.py
  json_schema.py
  pydantic/_internal
  _config.py
  _model_construction.py
Project Total  

This report was generated by python-coverage-comment-action

Copy link
Contributor

@sydney-runkle sydney-runkle left a comment

Choose a reason for hiding this comment

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

This patch looks alright to me - I've left one comment that I think might simplify things!

Comment on lines 1059 to 1069
# It might be that the provided default needs to be validated first
# (assuming `validate_default` is enabled). However, we can't perform
# such validation during JSON Schema generation so we don't support
# this pattern for now.
# (One example is when using `foo: ByteSize = '1MB'`, which validates and
# serializes as an int. In this case, `ser_func` is `int` and `int('1MB')` fails).
self.emit_warning(
'non-serializable-default',
f'Unable to serialize value {default!r} with the plain serializer; excluding default from JSON schema',
)
return json_schema
Copy link
Contributor

Choose a reason for hiding this comment

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

Could we do something like this?

def encode_default(self, dft: Any) -> Any:
"""Encode a default value to a JSON-serializable value.
This is used to encode default values for fields in the generated JSON schema.
Args:
dft: The default value to encode.
Returns:
The encoded default value.
"""
from .type_adapter import TypeAdapter, _type_has_config
config = self._config
try:
default = (
dft
if _type_has_config(type(dft))
else TypeAdapter(type(dft), config=config.config_dict).dump_python(dft, mode='json')
)
except PydanticSchemaGenerationError:
raise pydantic_core.PydanticSerializationError(f'Unable to encode default value {dft}')
return pydantic_core.to_jsonable_python(
default,
timedelta_mode=config.ser_json_timedelta,
bytes_mode=config.ser_json_bytes,
)

I feel like we shouldn't be skipping that logic here, we could maybe call that function?

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 don't think it makes sense to do so, or we may end up with a wrong default value in the JSON Schema (in the related test fixed, '1MB' was set as the default value in the JSON Schema, but this isn't true as the default is 1000000). In my opinion, it's better to have no value than a wrong one.

However, we emit a warning here, so I'm ok to call encode_default.

Copy link
Contributor

Choose a reason for hiding this comment

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

That's fair - I wonder if we could use a similar pattern though + attempt to validate the default + include that in the JSON schema. Feels messy to do validation here, I admit.

I'm not opposed to the warning you have in place, I think that's good!

Copy link
Contributor

Choose a reason for hiding this comment

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

That's fair - I wonder if we could use a similar pattern though + attempt to validate the default + include that in the JSON schema. Feels messy to do validation here, I admit.

I think we can do this in a separate PR, if someone asks for it. I'm not keen to go ahead and do this now.

Change looks good to me, thanks for including the helpful warning.

@Viicos Viicos force-pushed the 10118-plain-json-schema branch from 0eed384 to eb1570b Compare August 13, 2024 18:02
Copy link
Contributor

@sydney-runkle sydney-runkle left a comment

Choose a reason for hiding this comment

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

Looks good @Viicos, nice work

# serializes as an int. In this case, `ser_func` is `int` and `int('1MB')` fails).
self.emit_warning(
'non-serializable-default',
f'Unable to serialize value {default!r} with the plain serializer; excluding default from JSON schema',
Copy link
Contributor

Choose a reason for hiding this comment

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

One nit, this isn't always a user defined plain serializer, which might be confusing. We could change the wording of this a bit, but I'm not super worried about it, so will go ahead and merge this.

@sydney-runkle sydney-runkle merged commit a42fb67 into main Aug 14, 2024
61 checks passed
@sydney-runkle sydney-runkle deleted the 10118-plain-json-schema branch August 14, 2024 15:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
awaiting author revision awaiting changes from the PR author relnotes-fix Used for bugfixes.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

PlainSerializer not respecting when_used field during generation of json schema
2 participants