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

Dynamic default value or factory for function argument #57

Open
xbanke opened this issue Apr 18, 2020 · 2 comments
Open

Dynamic default value or factory for function argument #57

xbanke opened this issue Apr 18, 2020 · 2 comments
Labels
enhancement New feature or request

Comments

@xbanke
Copy link

xbanke commented Apr 18, 2020

  • typical version: 2.0.1
  • Python version: 3.7.4
  • Operating System:

Description

Hey, @seandstewart . I have another feature request.
For data model, there is default_factory which can be used to set dynamic default value. For function argument, if set the default value as a instance of typic.Field, it won't be validated as a default value. datetime.now as a default_factory is quite common. Every time I have to handle it like this:

from datetime import datetime
import typic

@typic.al
def foo(dt: datetime = None):
    dt = datetime.now() if dt is None else dt
    ...

Especilly when we have many functions with dynamic default value, it would be a problem. Currently, beacuse the default value would not be validated, I defined a Helper class to solve this.

from typing import Callable, Optional, Any
from functools import wraps
import inspect
from dataclasses import MISSING
import typic

class DefaultArgument:
    def __init__(self, default: Any = MISSING, *, factory: Optional[Callable] = None):
        assert default is not MISSING or callable(factory)
        self.default = default
        self.factory = factory

    def __call__(self, *args, **kwargs):
        if self.default is not MISSING:
            return self.default
        return self.factory(*args, **kwargs)

    def __repr__(self):
        return f"{self.__class__.__name__}(default={self.default}, factory={self.factory!r})"


def validate(func: Optional[Callable] = None, *, coerce: bool = True, strict: bool = False) -> Callable:
    def _validate(_func: Callable):
        @wraps(func)
        def wrapped(*args, **kwargs):
            bind = inspect.signature(_func).bind(*args, **kwargs)
            bind.apply_defaults()  # apply_defaults is not available in typic.bind
            for k, v in bind.arguments.items():
                if isinstance(v, DefaultArgument):
                    bind.arguments[k] = v()
            return typic.bind(_func, *bind.args, partial=False, coerce=coerce, strict=strict, **bind.kwargs).eval()

        return wrapped

    if callable(func):
        return _validate(func)
    else:
        return _validate

Now we can redefine foo like this:

from datetime import datetime

@validate
def foo(dt: datetime = DefaultArgument(factory=datetime.now)):
    ...

By the way, what if the user want to use the key words such as dict, schema, coerce, strict and so on, as the function argument or data attribute?

@seandstewart
Copy link
Owner

Hey again, @xbanke -

This is a great idea, and again, thank you for the detailed example. I haven’t focused much on this issue in the past because the original focus of the decorator was simply to ensure external inputs are the type you expect, but I do see the value here.

I’m beginning to compile a list of features for the next minor release - I definitely think this is achievable and worth adding to it.

@seandstewart seandstewart added the enhancement New feature or request label Apr 24, 2020
@xbanke
Copy link
Author

xbanke commented Apr 25, 2020

I'm Looking forward to that. When will you release the next version?

@seandstewart seandstewart added this to To do in v2.1 Apr 30, 2020
@seandstewart seandstewart added this to the Typic 2.1 milestone Apr 30, 2020
@seandstewart seandstewart removed this from To do in v2.1 Jan 5, 2021
@seandstewart seandstewart removed this from the Typic 2.1 milestone Jan 5, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants