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

Default values and subcast #270

Open
tobiasmboelz opened this issue Sep 1, 2022 · 2 comments
Open

Default values and subcast #270

tobiasmboelz opened this issue Sep 1, 2022 · 2 comments

Comments

@tobiasmboelz
Copy link

tobiasmboelz commented Sep 1, 2022

As far as I can tell the default value can either be of the cast type or a string. So env.bool('FOO', True), env.bool('FOO', 'true'), env.bool('FOO', '1') and env.bool('FOO', 'YES') all are basically the same.

>>> os.environ.get('FOO')
>>> env.bool('FOO', True)
True
>>> env.bool('FOO', 'true')
True
>>> env.bool('FOO', '1')
True
>>> env.bool('FOO', 'YES')
True

The same goes for lists with a subcast to a basic type. env.list('FOO', '1, 2, 42', subcast=float) and env.list('FOO', [1.0, 2.0, 42.0], subcast=float) both do work.

>>> env.list('FOO', '1, 2, 42', subcast=float)
[1.0, 2.0, 42.0]
>>> env.list('FOO', [1.0, 2.0, 42.0], subcast=float)
[1.0, 2.0, 42.0]

But, using the target type as default only works as long as the subcast type/callable can be called with the cast type itself. It breaks when the callable expects a string and returns something else. For example:

>>> env.list('FOO', 'a:b, b:c', subcast=lambda s: tuple(s.split(':')))
[('a', 'b'), (' b', 'c')]
>>> env.list('FOO', ['a:b', 'b:c'], subcast=lambda s: tuple(s.split(':')))
[('a', 'b'), ('b', 'c')]
>>> env.list('FOO', [('a', 'b'), ('b', 'c')], subcast=lambda s: tuple(s.split(':')))
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "[…]\lib\site-packages\environs\__init__.py", line 123, in method
    value = field.deserialize(value)
  File "[…]\lib\site-packages\marshmallow\fields.py", line 368, in deserialize
    output = self._deserialize(value, attr, data, **kwargs)
  File "[…]\lib\site-packages\marshmallow\fields.py", line 784, in _deserialize
    result.append(self.inner.deserialize(each, **kwargs))
  File "[…]\lib\site-packages\marshmallow\fields.py", line 368, in deserialize
    output = self._deserialize(value, attr, data, **kwargs)
  File "[…]\lib\site-packages\environs\__init__.py", line 188, in _deserialize
    return func(value)
  File "<console>", line 1, in <lambda>
AttributeError: 'tuple' object has no attribute 'split'

If this is expected behaviour, I’d be happy to provide a pull request with a supplement to the documentation (well, README.md).

@BroFromSpace
Copy link

Am I correct in understanding that the subcast is applied to the default value? If so, does this make sense, since a default value is supposed to be predictable and shouldn't require any transformations? Wouldn't it be better to simply check if the default value is of the correct type or None?

@tobiasmboelz
Copy link
Author

The subcast is applied to elements of the list after splitting.

Env.list has special handling for the case that a list (or any other iterable except string) is passed in1, but subcast has not. I'd suggest that, for consistency, the subcast should only be applied if the value is a string.

As for the suggestion to check if the default value is of the correct type: That would only work as long as the subcast argument is a type. For (other) callables, that would require either another argument or a mandatory return annotation. IMO, both would make usage of the package unnecessarily complex.

Footnotes

  1. So does Env.dict.

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

No branches or pull requests

2 participants