Skip to content

Allow callable defaults for any sealer #6

Open

Description

@dimitern

First let me say - great library! :) I've been evaluating it as well as attrs for a while now.

I'd like to suggest a possible feature extension: allow callable defaults, which will let you do things like this:

from datetime import datetime
import fields
class Record(fields.Tuple.Username[str].Created[datetime.now].Revision[long].Active[bool]):
    pass

Using types as defaults IMO makes the code more readable. Also assuming the callable takes no arguments, it allows simple initialization at run-time (like with datetime.now).

It's easy to do that with a simple sealer wrapper, like the one below:

from functools import wraps
from fields import factory, tuple_sealer

def with_callable_defaults(sealer):
    """
    Wraps the given `sealer` to support `callable` for any specified default.
    """
    @wraps(sealer)
    def wrapped_sealer(fields, defaults):
        for field, default in defaults.iteritems():
            if callable(default):
                try:
                    defaults[field] = default()
                except TypeError:
                    pass
        return sealer(fields, defaults)
    return wrapped_sealer

Tuple = factory(with_callable_defaults(tuple_sealer))

What do you think?

Activity

  1. ionelmc commented on May 15, 2017

    @ionelmc
    Owner

    Another way, that can also keep backwards compat a bit is to (ab)use slices for defaults as callables, eg:

    class Record(fields.Tuple.Username[""].Created[:datetime.now].Revision[None].Active[False):
        pass

    or even with strings for more possibilities (the init function is made from a string, alike namedtuple, so this is possible):

    class Record(fields.Tuple.Username[:"getpass.getuser()"].Created[:"datetime.now()"].Revision[None].Active["Revision!=None"):
        pass
  2. ionelmc commented on May 15, 2017

    @ionelmc
    Owner

    Actually both forms could be supported.

    Anyway, doesn't attrs already support default values from callables?

  3. dimitern commented on May 16, 2017

    @dimitern
    Author

    I was thinking of abusing the slice syntax at first :) Could be useful for things like validators, converters, or even docstrings, esp. if combined with your suggestion about supporting strings:

    class Record(fields.Tuple
                 .WithoutDefault
                 .WithConstDefault["foo"]
                 .WithCallableDefault[:"datetime.now().isoformat()"]
                 .WithDocstring[None, """The doc"""]
                 .AllInOne["bar", """Bar doc"""]): 
        pass
    
    r = Record(1)
    assert r.WithDocString.__doc__ == """The Doc"""
    assert r.WithCallableDefault == "2000-01-02T03:04:05"

    Yeah, attrs supports callable defaults and lots more, but I like the concise declarative style of fields better I think :)

    I'll look into it some more, cheers!

  4. ionelmc commented on May 16, 2017

    @ionelmc
    Owner

    Well yes, fields is not meant to replace attrs, or even compete with it. As a matter of fact it precedes attrs (it's practically a jab at characteristic). You may have guessed it, attrs probably wouldn't exist if I hadn't done fields xD

    Intended use is very small classes (5 fields or less). If you can't fit it on one line then probably you shouldn't use fields at all - the whole idea of extreme brevity become a moot point if you split the definition on multiple lines in ugly jquery chain style.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions