-
Notifications
You must be signed in to change notification settings - Fork 0
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
Add FunctionModel for handling functions #1
Conversation
eac2e78
to
32ff1ce
Compare
pydantic/function_model.py
Outdated
@@ -0,0 +1,442 @@ | |||
"""Provides FunctionModel, a model for validating function arguments.""" | |||
|
|||
from collections import OrderedDict |
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.
IIRC all Python dicts are guaranteed to be ordered since Python 3.6 and pydantic
2 supports only Python 3.7+.
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.
Replaced it, good point
pydantic/function_model.py
Outdated
_keyword_total: int | ||
_positional_total: int | ||
|
||
T = TypeVar('T') |
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.
Probably best to have this TypeVar
outside of the class.
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.
Done.
pydantic/function_model.py
Outdated
|
||
params[name] = ( | ||
get_annotation_type(param.annotation), | ||
param.default if param.default != Parameter.empty else ..., |
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 think this won't work if the default value is ...
.
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 got the impression somewhere (possibly here, I'm not sure) that ...
was the default value of field defaults. From looking further at that class, it seems they use PydanticUndefined
, which I have changed it to and works just as well with the current tests. I can't find any documentation as to what exactly I should use here, but hopefully if there is an issue then it will get pointed out once we have an upstream PR - I'll make sure to ask explicitly when the time comes so it's not missed.
pydantic/function_model.py
Outdated
config = ( | ||
ConfigDict(extra='allow', validate_assignment=True, validate_default=True) | ||
if has_kwargs | ||
else ConfigDict(extra='forbid', validate_assignment=True, validate_default=True) | ||
) |
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.
config = ( | |
ConfigDict(extra='allow', validate_assignment=True, validate_default=True) | |
if has_kwargs | |
else ConfigDict(extra='forbid', validate_assignment=True, validate_default=True) | |
) | |
config = ConfigDict(extra='allow' if has_kwargs else 'forbid', validate_assignment=True, validate_default=True) |
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.
Changed
pydantic/function_model.py
Outdated
if key == 'args': | ||
# Values within *args are unrestricted | ||
return value | ||
elif key == 'kwargs': | ||
# Values within **kwargs are unrestricted | ||
return 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.
if key == 'args': | |
# Values within *args are unrestricted | |
return value | |
elif key == 'kwargs': | |
# Values within **kwargs are unrestricted | |
return value | |
if key in {'args', 'kwargs'}: | |
# Values within *args and **kwargs are unrestricted | |
return 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.
Fixed, I believe at some point I had it where the two keys were doing something different and I just forgot to reduce it down.
pydantic/function_model.py
Outdated
field = self._model.model_fields.get(key) | ||
assert field is not None # This should always be the case, but Python complains otherwise | ||
return TypeAdapter(get_annotation_type(field.annotation)).validate_python(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.
field = self._model.model_fields.get(key) | |
assert field is not None # This should always be the case, but Python complains otherwise | |
return TypeAdapter(get_annotation_type(field.annotation)).validate_python(value) | |
return TypeAdapter(get_annotation_type(self._model.model_fields[key].annotation)).validate_python(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.
Fixed
pydantic/function_model.py
Outdated
if pos >= len(param_keys): | ||
if has_args: | ||
in_args = True | ||
else: | ||
error = unexp_error # Unknown positional parameter and *args is not present | ||
elif not self._parameters[param_keys[pos]]['positional']: | ||
if has_args: | ||
in_args = True | ||
else: | ||
error = unexp_error # Keyword-only parameter, so can't set via position |
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.
if pos >= len(param_keys): | |
if has_args: | |
in_args = True | |
else: | |
error = unexp_error # Unknown positional parameter and *args is not present | |
elif not self._parameters[param_keys[pos]]['positional']: | |
if has_args: | |
in_args = True | |
else: | |
error = unexp_error # Keyword-only parameter, so can't set via position | |
if pos >= len(param_keys) or not self._parameters[param_keys[pos]]['positional']: | |
if has_args: | |
in_args = True | |
else: | |
error = unexp_error |
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.
Fixed
pydantic/function_model.py
Outdated
param = params[key] | ||
param['set_keyword'] = True if param['keyword'] else False | ||
|
||
if len(errors) > 0: |
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.
if len(errors) > 0: | |
if errors: |
tests/test_function_model.py
Outdated
pytest.skip('These tests use positional-only parameters in functions, which are not supported prior to Python 3.8') | ||
|
||
|
||
"""def test_only_pos(): # noqa: C901 |
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.
What is the purpose of this string?
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.
These were just old tests that I had commented out while I was working on replacing them. Forgot to remove them, but have removed them now.
96615f7
to
a10f210
Compare
This model type uses a Pydantic model to validate function arguments and return values, exposing functions to pass in and retrieve arguments, and to call functions using the stored arguments. This is a feature that has been asked for previously in pydantic#1391 but which was possible through v1 methods which are not present in v2 and so that issue is already closed.
OrderedDict is unnecessary here, as dicts are ordered by default in all Python versions that Pydantic supports (>=3.7). Co-authored-by: Alexander Khabarov <alexander.khabarov@arm.com>
Some old tests were left in the module by mistake within a comment string.
This may be a more appropriate value than the previous, which was Python's ellipsis.
Co-authored-by: Alexander Khabarov <alexander.khabarov@arm.com>
Co-authored-by: Alexander Khabarov <alexander.khabarov@arm.com>
The new format functions the same, but is a bit shorter and more readable. Co-authored-by: Alexander Khabarov <alexander.khabarov@arm.com>
Co-authored-by: Alexander Khabarov <alexander.khabarov@arm.com>
Co-authored-by: Alexander Khabarov <alexander.khabarov@arm.com>
Co-authored-by: Alexander Khabarov <alexander.khabarov@arm.com>
Co-authored-by: Alexander Khabarov <alexander.khabarov@arm.com>
2ea4e0d
to
bfb8b7f
Compare
`get_signature` allows developers to directly access the function signature of the model, which may be useful in situations where their code has access to the model itself but not the function. Also, `FunctionModel` could not previously be imported via `pydantic.FunctionModel`, which has been fixed.
Function call validation is implemented upstream here. We can close this PR. |
Change Summary
Adds a wrapper model that uses a Pydantic model to validate function arguments and return values, exposing functions to pass in and retrieve arguments, and to call functions using the stored arguments.
Related issue number
This is a feature that has been asked for previously in pydantic#1391 but which was possible through v1 methods which are not present in v2 and so that issue is already closed.
Checklist