attrs
attrs copied to clipboard
attr.Factory provided with the attribute beeing initialized
Hi there,
I'm looking for a way to read default value for an attribute from os.environ
The natural way i see for this is to use a attr.Factory in combination with attr.converters.default_if_none. This factory would call os.environ with some env-key built with attribute's name or metadata.
But i dont see how this factory could be provided with the attribute's name or meta-data.
The other way i tried to was to build a custom decorator
def env_binding(prefix):
def modify_ib_default(attribute):
env_key = (prefix + '_' + attribute.name).upper()
attribute.default = os.environ.get(env_key, attribute.default)
return attribute
def wrap(cls):
new_cls = attr.s(cls)
new_cls.__attrs_attrs__ = tuple(map(modify_ib_default, new_cls.__attrs_attrs__))
return new_cls
return wrap
but since Attribute instances are read-only (setattr --> raise FrozenInstanceError), this method is not valid...
Note: I know the nice python attr-based module config-environ but I need a different behavior.
I’m not 100% sure I understand your use-case, but do you know about the takes_self option of Factory?
Hi, if i'm correct, the takes_self provies de instance being built, not the attribute beeing managed.
Yes, but you can introspect self.__class__ using attr.fields() and attr.fields_dict()?
To be more specific, i would like in a Factory to know what attribute is beeing managed (in any converter, validator).
I see two usages for this: The first one, specific, beeing able to load env variable using the attribute's name. The second one, mandatory in my optinion, would be to have exceptions that tell you what attribute is invalid.
About introspection, i dont realy understand what you have in mind. Here is some code maybee i dont see something obvious
import attr
def my_factory():
def _inner(self):
print("self:", self)
print("fields:", attr.fields(type(self)))
print("fdict:", attr.fields_dict(type(self)))
return attr.Factory(_inner, True)
@attr.s
class A:
a = attr.ib(my_factory())
b = attr.ib(my_factory())
A()
So, when the factory is called for the attribute a:
self: A(a=NOTHING, b=NOTHING)
fields: (
Attribute(name='a', default=Factory(factory=<function my_factory.<locals>._inner at 0x7f8535e1c510>, takes_self=True), validator=None, repr=True, cmp=True, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False),
Attribute(name='b', default=Factory(factory=<function my_factory.<locals>._inner at 0x7f8535e1c378>, takes_self=True), validator=None, repr=True, cmp=True, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False)
)
fdict: {
'a': Attribute(name='a', default=Factory(factory=<function my_factory.<locals>._inner at 0x7f8535e1c510>, takes_self=True), validator=None, repr=True, cmp=True, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False),
'b': Attribute(name='b', default=Factory(factory=<function my_factory.<locals>._inner at 0x7f8535e1c378>, takes_self=True), validator=None, repr=True, cmp=True, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False)
}
And when factory is called for attribute b:
self: A(a=None, b=NOTHING)
fields: (
Attribute(name='a', default=Factory(factory=<function my_factory.<locals>._inner at 0x7f8535e1c510>, takes_self=True), validator=None, repr=True, cmp=True, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False),
Attribute(name='b', default=Factory(factory=<function my_factory.<locals>._inner at 0x7f8535e1c378>, takes_self=True), validator=None, repr=True, cmp=True, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False)
)
fdict: {
'a': Attribute(name='a', default=Factory(factory=<function my_factory.<locals>._inner at 0x7f8535e1c510>, takes_self=True), validator=None, repr=True, cmp=True, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False),
'b': Attribute(name='b', default=Factory(factory=<function my_factory.<locals>._inner at 0x7f8535e1c378>, takes_self=True), validator=None, repr=True, cmp=True, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False)
}
A(a=None, b=None)
I see no way in the factory to infer that it is applied for a or for b.
What i mean by having better factory, validator, converter messages is:
import attr
@attr.s
class A:
a = attr.ib(converter=int)
b = attr.ib(converter=int)
A()->TypeError: __init__() missing 2 required positional arguments: 'a' and 'b'A(b=1)->TypeError: __init__() missing 1 required positional argument: 'a'A('str', 1)->ValueError: invalid literal for int() with base 10: 'str'
Cases 1 and 2 are ok, but in case 3 how the user (dev) will know that the error applies to attribute a ?
Right hm I guess you're asking for a takes_field=False argument, for generalized factories?