-
-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Do not compute JSON Schema default when plain serializers are used with when_used set to 'json-unless-none' and the default value is None
#10121
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
Conversation
Deploying pydantic-docs with
|
| 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 |
CodSpeed Performance ReportMerging #10121 will not alter performanceComparing Summary
|
| assert Model.model_json_schema(mode='serialization') == { | ||
| 'properties': {'custom_decimal': {'default': None, 'title': 'Custom Decimal', 'type': 'number'}}, | ||
| 'title': 'Model', | ||
| 'type': 'object', | ||
| } |
There was a problem hiding this comment.
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:
pydantic/pydantic/json_schema.py
Lines 1022 to 1031 in 0c97049
| 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:
pydantic/pydantic/json_schema.py
Lines 492 to 494 in 0c97049
| 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') |
There was a problem hiding this comment.
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`
acef83b to
4396bb3
Compare
Coverage reportClick to see where and how coverage changed
This report was generated by python-coverage-comment-action |
||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this 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!
pydantic/json_schema.py
Outdated
| # 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 |
There was a problem hiding this comment.
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?
pydantic/pydantic/json_schema.py
Lines 2017 to 2044 in 0c97049
| 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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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!
There was a problem hiding this comment.
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.
0eed384 to
eb1570b
Compare
There was a problem hiding this 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', |
There was a problem hiding this comment.
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.
Fixes #10118
Change Summary
Related issue number
Checklist