Skip to content
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

Find out how pydantic validates function calls without calling them #875

Open
sobolevn opened this issue Mar 25, 2021 · 5 comments
Open
Labels
bug Something isn't working

Comments

@sobolevn
Copy link
Member

We need to chec how pydantic validates function arguments without calling the function itself: https://pydantic-docs.helpmanual.io/usage/validation_decorator/#validate-without-calling-the-function

We need this because our @curry implementation relies on this logic. And right now it is very slow.

@sobolevn sobolevn added the bug Something isn't working label Mar 25, 2021
@thepabloaguilar
Copy link
Member

I have a little explanation while I was walking through the code!

The class responsible for is ValidatedFunction! In its __init__ it process all parameters and types.

When we call validate(...) under the hood we're calling init_model_instance ant it calls two functions:

  1. build_values: That is responsible to inspect all the passed values to validate(...)
  2. model: This is not a function but a dynamically generated model

The model is created in create_model method, at the end we have a normal BaseModel but instead of class specifications we have function specifications! The validations occur in the validate_model function.

@sobolevn
Copy link
Member Author

@thepabloaguilar can we adopt this / similar approach to speed up our @curry?

@thepabloaguilar
Copy link
Member

Well, pydantic approach seems more complicated, I don't know if it'll be faster than our actual implementation.

I have something in mind for a long time, maybe we can use partial to build curry. Did you try it??
When we have a partial from a partial, both are merged:

>>> from functools import partial
>>> def f(a, b, c):
...     ...
...

>>> p1 = partial(f, 1)
>>> str(p1)
'functools.partial(<function f at 0x105bae160>, 1)'

>>> p2 = partial(p1, 2)
>>> str(p2)
'functools.partial(<function f at 0x105bae160>, 1, 2)'

# We have access to `args`, `kwargs` and `func`
>>> (p2.args, p2.keywords, p.func)
((1, 2), {}, <function a at 0x105b95ca0>)

I don't know if it's possible to build something using that approach, it's just thinking!

@sobolevn
Copy link
Member Author

We need some logical flag to decide when to actually call a function: https://github.com/dry-python/returns/blob/master/returns/curry.py#L138-L139

@thepabloaguilar
Copy link
Member

Hummmm. I want to make some tests around partial and pydantic approach!

Using partial we can use the inspect module to determine if we need to call:

>>> import inspect
# Consider `p1` and `p2` from my last example
>>> inspect.signature(p2)
<Signature (c)>
>>> inspect.signature(p1)
<Signature (b, c)>

>>> inspect.getfullargspec(p1)
FullArgSpec(args=['b', 'c'], varargs=None, varkw=None, defaults=None, kwonlyargs=[], kwonlydefaults=None, annotations={})
>>> inspect.getfullargspec(p2)
FullArgSpec(args=['c'], varargs=None, varkw=None, defaults=None, kwonlyargs=[], kwonlydefaults=None, annotations={})

Pydantic:
This for loop is responsible to get all of the function fields. To follow this approach we'll need to transform curry in a callable class instead of a function!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Development

No branches or pull requests

2 participants