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
Enum validator improvements #9045
Conversation
Deploying pydantic-docs with Cloudflare Pages
|
CodSpeed Performance ReportMerging #9045 will not alter performanceComparing Summary
|
@@ -90,47 +91,32 @@ def to_enum(input_value: Any, /) -> Enum: | |||
try: | |||
return enum_type(input_value) | |||
except ValueError: | |||
# The type: ignore on the next line is to ignore the requirement of LiteralString | |||
raise PydanticCustomError('enum', f'Input should be {expected}', {'expected': expected}) # type: ignore | |||
raise PydanticCustomError('enum', 'Input should be {expected}', {'expected': expected}) |
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.
output is the same, but it's more correct to let pydantic-core take care of replacing the variables in the message template.
python_schema=strict_python_schema, | ||
) | ||
js_updates['type'] = 'integer' | ||
lax_schema = core_schema.no_info_after_validator_function(to_enum, core_schema.int_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.
I've replaced lots of use of core_schema.chain_schema
with core_schema.no_info_after_validator_function
which should be more efficient, and is more concise.
[enum_schema, core_schema.no_info_plain_validator_function(lambda x: x.value)] | ||
) | ||
if config.get('use_enum_values', False): | ||
enum_schema = core_schema.no_info_after_validator_function(attrgetter('value'), enum_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.
attrgetter
is nearly twice as fast as a lambda:
In [1]: import enum
In [2]: class Foo(enum.Enum):
...: x = 'a'
...: y = 'b'
...:
In [3]: with_lambda = lambda thing: thing.x
In [4]: %timeit with_lambda(Foo)
39.1 ns ± 0.161 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each)
In [5]: from operator import attrgetter
In [6]: using_attrgetter = attrgetter('x')
In [7]: %timeit using_attrgetter(Foo)
24.5 ns ± 0.473 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each)
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.
Should we be using attrgetter
more broadly?
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 great! This is much more simple than the existing behavior!
[enum_schema, core_schema.no_info_plain_validator_function(lambda x: x.value)] | ||
) | ||
if config.get('use_enum_values', False): | ||
enum_schema = core_schema.no_info_after_validator_function(attrgetter('value'), enum_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.
Should we be using attrgetter
more broadly?
Change Summary
While looking through how we do enum validation in preparation for moving some of it to Rust, I saw a few things that could be improved. Hopefully this provides a "cleaner" schema, and should improve performance a bit.
Performance gain is small, but not insignificant:
before
After
Checklist