-
-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Add support for ConstrainedStr as dict keys #332
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
Codecov Report
@@ Coverage Diff @@
## master #332 +/- ##
=====================================
Coverage 100% 100%
=====================================
Files 13 13
Lines 1779 1783 +4
Branches 346 347 +1
=====================================
+ Hits 1779 1783 +4 |
|
@samuelcolvin should I rebase to solve HISTORY conflicts? |
|
Yes, or merge with master. |
c39c241 to
a267c57
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.
Otherwise LGTM.
pydantic/schema.py
Outdated
| else: | ||
| # The dict values are Any, no need to declare it | ||
| return {'type': 'object'}, definitions | ||
| if key_pattern: |
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.
no need for key_pattern just if regex here.
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.
Agreed. Done.
pydantic/schema.py
Outdated
| if key_pattern: | ||
| # Dict keys have a regex pattern | ||
| # f_schema might be a schema or empty dict, add it either way | ||
| dict_schema.update({'patternProperties': {key_pattern: f_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.
since we're only setting on item use setitem dict_schema['patternProperties'] here and below.
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.
Agreed. Done.
| # Dict keys have a regex pattern | ||
| # f_schema might be a schema or empty dict, add it either way | ||
| dict_schema['patternProperties'] = {regex.pattern: f_schema} | ||
| elif f_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.
Sorry, one more question.
Are these two cases really mutually exclusive? Eg. requiring elif rather than if.
maybe you might want both patternProperties and additionalProperties?
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.
Yep. I thought about the same while implementing it.
The schema that would go in additionalProperties ends up as the value of the dict at patternProperties. That dict has as key the regex pattern, and as value the schema for the dict value.
Also from the spec:
Validation with "additionalProperties" applies only to the child values of instance names that do not match any names in "properties", and do not match any regular expression in "patternProperties".
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 can think of a case that would allow for patternProperties and additionalProperties, but it is rather complex.
>>> from typing import Union, Dict, Any
>>> from pydantic import BaseModel, constr
>>>
>>> Identifier = constr(regex=r'^([a-zA-Z_][a-zA-Z0-9_]*)$')
>>> Slug = constr(regex=r'^([a-zA-Z_-][a-zA-Z0-9_-]*)$')
>>> Number = constr(regex=r'^\d+$')
>>>
>>> class IdentifierModel(BaseModel):
... alias: str = ...
...
>>> class SlugModel(BaseModel):
... alias: str = ...
... value: str = ...
...
>>> class NumberModel(BaseModel):
... alias: str = ...
... value: int = ...
...
>>> class Model(BaseModel):
... items: Union[Dict[Identifier, IdentifierModel], Dict[Slug, SlugModel], Dict[Number, NumberModel], Dict[str, Any]] = {}
...
>>> print(Model.schema_json(indent=2))
{
"title": "Model",
"type": "object",
"properties": {
"items": {
"title": "Items",
"default": {},
"anyOf": [
{
"type": "object",
"patternProperties": {
"^([a-zA-Z_][a-zA-Z0-9_]*)$": {
"$ref": "#/definitions/IdentifierModel"
}
}
},
{
"type": "object",
"patternProperties": {
"^([a-zA-Z_-][a-zA-Z0-9_-]*)$": {
"$ref": "#/definitions/SlugModel"
}
}
},
{
"type": "object",
"patternProperties": {
"^\\d+$": {
"$ref": "#/definitions/NumberModel"
}
}
},
{
"type": "object"
}
]
}
},
"definitions": {
"IdentifierModel": {
"title": "IdentifierModel",
"type": "object",
"properties": {
"alias": {
"title": "Alias",
"type": "string"
}
},
"required": [
"alias"
]
},
"SlugModel": {
"title": "SlugModel",
"type": "object",
"properties": {
"alias": {
"title": "Alias",
"type": "string"
},
"value": {
"title": "Value",
"type": "string"
}
},
"required": [
"alias",
"value"
]
},
"NumberModel": {
"title": "NumberModel",
"type": "object",
"properties": {
"alias": {
"title": "Alias",
"type": "string"
},
"value": {
"title": "Value",
"type": "integer"
}
},
"required": [
"alias",
"value"
]
}
}
}
# expected
{
"title": "Model",
"type": "object",
"properties": {
"items": {
"title": "Items",
"default": {},
"patternProperties": {
"^([a-zA-Z_][a-zA-Z0-9_]*)$": {
"$ref": "#/definitions/IdentifierModel"
},
"^([a-zA-Z_-][a-zA-Z0-9_-]*)$": {
"$ref": "#/definitions/SlugModel"
},
"^\\d+$": {
"$ref": "#/definitions/NumberModel"
}
},
"additionalProperties": {},
}
},
"definitions": {
"IdentifierModel": {
"title": "IdentifierModel",
"type": "object",
"properties": {
"alias": {
"title": "Alias",
"type": "string"
}
},
"required": [
"alias"
]
},
"SlugModel": {
"title": "SlugModel",
"type": "object",
"properties": {
"alias": {
"title": "Alias",
"type": "string"
},
"value": {
"title": "Value",
"type": "string"
}
},
"required": [
"alias",
"value"
]
},
"NumberModel": {
"title": "NumberModel",
"type": "object",
"properties": {
"alias": {
"title": "Alias",
"type": "string"
},
"value": {
"title": "Value",
"type": "integer"
}
},
"required": [
"alias",
"value"
]
}
}
}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.
Good find, however I think this is too niche for now, if you think it's a problem @demosdemon please create a new issue to discuss.
Support for `patternProperties` was introduced in pydantic#332, but that logic unfortunately made `patternProperties` and `additionalProperties` mutually exclusive. JSON Schema supports their combination: https://json-schema.org/understanding-json-schema/reference/object.html#additional-properties This prevented well-typed code generation for dictionaries that use regex-constrained string keys.
Support for `patternProperties` was introduced in pydantic#332, but that logic unfortunately made `patternProperties` and `additionalProperties` mutually exclusive. JSON Schema supports their combination: https://json-schema.org/understanding-json-schema/reference/object.html#additional-properties This prevented well-typed code generation for dictionaries that use regex-constrained string keys.
Support for `patternProperties` was introduced in pydantic#332, but that logic unfortunately made `patternProperties` and `additionalProperties` mutually exclusive. JSON Schema supports their combination: https://json-schema.org/understanding-json-schema/reference/object.html#additional-properties This prevented well-typed code generation for dictionaries that use regex-constrained string keys.
Support for `patternProperties` was introduced in pydantic#332, but that logic unfortunately made `patternProperties` and `additionalProperties` mutually exclusive. JSON Schema supports their combination: https://json-schema.org/understanding-json-schema/reference/object.html#additional-properties This prevented well-typed code generation for dictionaries that use regex-constrained string keys.
Support for `patternProperties` was introduced in pydantic#332, but that logic unfortunately made `patternProperties` and `additionalProperties` mutually exclusive. JSON Schema supports their combination: https://json-schema.org/understanding-json-schema/reference/object.html#additional-properties This prevented well-typed code generation for dictionaries that use regex-constrained string keys.
…4641) Support for `patternProperties` was introduced in #332, but that logic unfortunately made `patternProperties` and `additionalProperties` mutually exclusive. JSON Schema supports their combination: https://json-schema.org/understanding-json-schema/reference/object.html#additional-properties This prevented well-typed code generation for dictionaries that use regex-constrained string keys.
* Integrate CodSpeed benchmarks with github actions * Apply suggestions from code review Co-authored-by: Samuel Colvin <samcolvin@gmail.com> * bump Co-authored-by: Arthur Pastel <arthur.pastel@gmail.com>
Change Summary
Add support for ConstrainedStr as dict keys.
Related issue number
#329
Performance Changes
pydantic cares about performance, if there's any risk performance changed on this PR,
please run
make benchmark-pydanticbefore and after the change:Checklist
HISTORY.rsthas been updated#<number>@whatever