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

[bug] Lazy validation fails with TypeError: __call__() takes 2 positional arguments but 3 were given #662

Closed
yahman72 opened this issue Oct 4, 2021 · 7 comments
Assignees

Comments

@yahman72
Copy link

yahman72 commented Oct 4, 2021

Describe the bug
Tried to introduce Lazy Validators as described in https://www.dynaconf.com/validation/#computed-values i.e.

from dynaconf.utils.parse_conf import empty, Lazy

Validator("FOO", default=Lazy(empty, formatter=my_function))

First bug (documentation): The above fails with
ImportError: cannot import name 'empty' from 'dynaconf.utils.parse_conf'
--> "empty" seems to be now in dynaconf.utils.functional.empty

Second bug:
Lazy Validator fails with
TypeError: __call__() takes 2 positional arguments but 3 were given

To Reproduce
Steps to reproduce the behavior:

from dynaconf.utils.parse_conf import Lazy
from dynaconf.utils.functional import empty

def lazy_foobar(s, v):
    return "foobar"

from dynaconf import Dynaconf
s = Dynaconf(
    validators=[Validator("FOOBAR", default=Lazy(empty, formatter=lazy_foobar)),],
)
print(s.FOOBAR)

stacktrace:

    print(s.FOOBAR)
  File "c:\ta\virtualenv\dconf\lib\site-packages\dynaconf\base.py", line 113, in __getattr__
    self._setup()
  File "c:\ta\virtualenv\dconf\lib\site-packages\dynaconf\base.py", line 164, in _setup
    settings_module=settings_module, **self._kwargs
  File "c:\ta\virtualenv\dconf\lib\site-packages\dynaconf\base.py", line 236, in __init__
    only=self._validate_only, exclude=self._validate_exclude
  File "c:\ta\virtualenv\dconf\lib\site-packages\dynaconf\validator.py", line 417, in validate
    validator.validate(self.settings, only=only, exclude=exclude)
  File "c:\ta\virtualenv\dconf\lib\site-packages\dynaconf\validator.py", line 198, in validate
    settings, settings.current_env, only=only, exclude=exclude
  File "c:\ta\virtualenv\dconf\lib\site-packages\dynaconf\validator.py", line 227, in _validate_items
    if callable(self.default)
TypeError: __call__() takes 2 positional arguments but 3 were given

Expected behavior
Work as documented in https://www.dynaconf.com/validation/#computed-values

Environment (please complete the following information):

  • OS: Windows 10 Pro
  • Dynaconf Version: 3.1.7 (also 3.1.4)
  • Frameworks in use: N/A

Additional context
Add any other context about the problem here.

@GibranHL0
Copy link
Contributor

Hi 👋

I'd like to be assigned to this issue and give it a try.

@GibranHL0
Copy link
Contributor

Hey, I found the issue. 👋

The problem behind this is within the __call__ method as it states to receive the parameter settings to return a formatted tuple with the self.value and a dictionary with the os.environ and the settings.

But within the _validate_items method, it invokes the __call__ one, giving the values of settings and self (self is the Validator instance defined in the default argument while the Settings object is created).

Then, the issue comes when the method is called and the Validator instance is not received.

But when checking the regular implementation, I found that the elements retrieved when the Validator is called are settings and the Validator.

So, I was hesitating about which is going to be the best approach.

  • Do I change the Lazy __call__ method to retrieve the values as the regular implementation (the settings and Validator objects)?
  • Do I receive the Validator object and ignore it?
  • Do I add a new if statement that checks whether is a regular implementation or a Lazy one to decide which elements should it send?

@rochacbruno
Copy link
Member

Hi @GibranHL0

My suggestions

on utils/parse_conf.py

# make the default value for Lazy to be an empty value
11  from dynaconf.utils.functional import empty
169  def __init__(self, value=empty, formatter=Formatters.python_formatter):

# accept a `validator_object` as named parameter and bind it to the context
178  def __call__(self, settings, validator_object=None):
         """LazyValue triggers format lazily."""
        self.settings = settings
        self.context["_validator_object"] = validator_object
        return self.formatter(self.value, **self.context)

Then, update the documentation on https://www.dynaconf.com/validation/#computed-values (docs/validation.md)

the correct way of using the computed lazy value must be

from dynaconf import Dynaconf, Validator
from dynaconf.utils.parse_conf import Lazy


def my_lazy_function(value, **context):
    """
    value: default value passed to the validator, defaults to `empty`
    context: a dictionary containing
             env: All the environment variables
             this: The settings instance
    """
    # Hypothetical example
    if env.get("HOSTNAME") == "localhost":
        result = f"{this.server.name}/local"
    else:
        result = f"{this.server.name}/{value}"
    return result


settings = Dynaconf(
    validators=[
        Validator("FOOBAR", default=Lazy("default_optional_value", formatter=my_lazy_function))
    ]
)

# When the value is first accessed, then the my_lazy_function will be called
print(settings.FOOBAR)

@justinmayer
Copy link

Can someone confirm whether this was indeed addressed by #675?

@rochacbruno
Copy link
Member

@justinmayer yes, on latest master it is working as described on the docs part of the issue #675

@andressadotpy
Copy link
Collaborator

@GibranHL0 Do you have any updates here? Do you need any help?

@GibranHL0
Copy link
Contributor

Hi @andressadotpy

The branch with the solution has already been merged #675.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants