Skip to content

Conversation

tiangolo
Copy link
Contributor

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-pydantic before and after the change:

  • before: ?
  • after: ?

Checklist

  • Unit tests for the changes exist
  • Tests pass on CI and coverage remains at 100%
  • Documentation reflects the changes
  • No performance deterioration (if applicable)
  • HISTORY.rst has been updated
    • if this is the first change since a release, please add a new section
    • include the issue number or this pull request number #<number>
    • if you're not a regular contributer please include your github username @whatever

@codecov
Copy link

codecov bot commented Dec 27, 2018

Codecov Report

Merging #332 into master will not change coverage.
The diff coverage is 100%.

@@          Coverage Diff          @@
##           master   #332   +/-   ##
=====================================
  Coverage     100%   100%           
=====================================
  Files          13     13           
  Lines        1779   1783    +4     
  Branches      346    347    +1     
=====================================
+ Hits         1779   1783    +4

@tiangolo
Copy link
Contributor Author

@samuelcolvin should I rebase to solve HISTORY conflicts?

@samuelcolvin
Copy link
Member

Yes, or merge with master.

Copy link
Member

@samuelcolvin samuelcolvin left a comment

Choose a reason for hiding this comment

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

Otherwise LGTM.

else:
# The dict values are Any, no need to declare it
return {'type': 'object'}, definitions
if key_pattern:
Copy link
Member

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agreed. Done.

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}})
Copy link
Member

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.

Copy link
Contributor Author

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:
Copy link
Member

@samuelcolvin samuelcolvin Dec 27, 2018

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?

Copy link
Contributor Author

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".

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"
      ]
    }
  }
}

Copy link
Member

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.

@samuelcolvin samuelcolvin merged commit c6d1a69 into pydantic:master Dec 27, 2018
@tiangolo tiangolo deleted the dict-constr-key branch December 28, 2018 04:00
jparise added a commit to jparise/pydantic that referenced this pull request Oct 22, 2022
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.
jparise added a commit to jparise/pydantic that referenced this pull request Oct 22, 2022
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.
jparise added a commit to jparise/pydantic that referenced this pull request Oct 22, 2022
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.
jparise added a commit to jparise/pydantic that referenced this pull request Oct 22, 2022
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.
jparise added a commit to jparise/pydantic that referenced this pull request Oct 22, 2022
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.
samuelcolvin pushed a commit that referenced this pull request Oct 27, 2022
…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.
alexdrydew pushed a commit to alexdrydew/pydantic that referenced this pull request Dec 23, 2023
* 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants