tri.declarative

tri.declarative contains tools that make it easy to write declarative code. This includes:

Class decorators

With just a few lines of code, turn your API from:

quux = Foo(things=[Bar(name='a', param=1), Bar(name='b', param=2), Bar(name='c', param=2)], baz=3)

into:

class Quux(Foo):
    a = Bar(param=1)
    b = Bar(param=2)
    c = Bar(param=2)

    class Meta:
        baz = 3

And you can still use the first style when it’s more convenient!

More detailed usage examples on @declarative below.

Evaluating

d = dict(
    foo=lambda x: x*2,
    bar=lambda y: y+5,
    baz=[
        foo=lambda x: x*6,
    ],
)

# evaluate only one level
assert evaluate(d, x=2) == dict(
    foo=4,
    bar=lambda y: y+5,  # this function doesn't match the signature so isn't evaluated
    baz=[
        foo=lambda x: x*6,  # one level down so isn't evaluated
    ],
)

# evaluate recursively
assert evaluate_recursive(d, x=2) == dict(
    foo=4,
    bar=lambda y: y+5,  # this function doesn't match the signature so isn't evaluated
    baz=[
        foo=12,
    ],
)

Filtering

d = dict(
    foo=dict(
        show=False,
        x=1,
    ),
    bar=dict(
        show=True,
        x=2,
    ),
)

assert filter_show_recursive(d) == dict(
    bar=dict(
        show=True,
        x=2,
    ),
)

Keyword argument dispatching

@dispatch:

@dispatch(
    bar={},
    baz__foo=2)
def foo(bar, baz):
    do_bar(**bar)
    do_baz(**baz)

Get/set attribute given a path string

class Foo:
    def __init__(a):
        self.a = a

class Bar:
    def __init__(b):
        self.b = b

class Baz:
    def __init__(c):
        self.c = c

x = Foo(Bar(Baz(c=3)))

assert getattr_path(x, 'a__b__c') == 3

assert setattr_path(x, 'a__b__c', 10)
assert getattr_path(x, 'a__b__c') == 10

Running tests

You need tox installed then just make test.

License

BSD

Usage

@declarative

In the example below, the @declarative(str) decorator will ensure that all str members of class Foo will be collected and sent as members constructor keyword argument.

from tri_declarative import declarative

@declarative(str)
class Foo:
    bar = 'barbar'
    baz = 'bazbaz'
    boink = 17

    def __init__(self, members):
        assert members['bar'] == 'barbar'
        assert members['baz'] == 'bazbaz'
        assert 'boink' not in members

f = Foo()

The value of the members argument will also be collected from sub-classes:

from tri_declarative import declarative

@declarative(str)
class Foo:

    def __init__(self, members):
        assert members['bar'] == 'barbar'
        assert members['baz'] == 'bazbaz'

class MyFoo(Foo):
    bar = 'barbar'
    baz = 'bazbaz'

    def __init__(self):
        super(MyFoo, self).__init__()

f = MyFoo()

The members argument can be given another name (things in the example below).

from tri_declarative.declarative import declarative

@declarative(str, 'things')
class Foo:

    bar = 'barbar'

    def __init__(self, **kwargs):
        assert 'things' in kwargs
        assert kwargs['things']['bar'] == 'barbar'

f = Foo()

Note that the collected dict is ordered by class inheritance and by using sorted of the values within each class. (In the ‘str’ example, sorted yields in alphabetical order).

Also note that the collection of class members based on their class does not interfere with instance constructor argument of the same type.

from tri_declarative import declarative

@declarative(str)
class Foo:
    charlie = '3'
    alice = '1'

    def __init__(self, members):
        assert list(members.items()) == [('alice', '1'), ('charlie', '3'),
                                         ('bob', '2'), ('dave', '4'),
                                         ('eric', '5')])
        assert 'animal' not in members


class MyFoo(Foo):
    dave = '4'
    bob = '2'

class MyOtherFoo(MyFoo):
    eric = '5'

    def __init__(self, animal)
        assert animal == 'elephant'

f = MyOtherFoo('elephant')

Real world use-case

Below is a more complete example of using @declarative:

from tri_declarative import declarative, creation_ordered


@creation_ordered
class Field:
    pass


class IntField(Field):
    def render(self, value):
        return '%s' % value


class StringField(Field):
    def render(self, value):
        return "'%s'" % value


@declarative(Field, 'table_fields')
class SimpleSQLModel:

    def __init__(self, **kwargs):
        self.table_fields = kwargs.pop('table_fields')

        for name in kwargs:
            assert name in self.table_fields
            setattr(self, name, kwargs[name])

    def insert_statement(self):
        return 'INSERT INTO %s(%s) VALUES (%s)' % (self.__class__.__name__,
                                                 ', '.join(self.table_fields.keys()),
                                                 ', '.join([field.render(getattr(self, name))
                                                            for name, field in self.table_fields.items()]))


class User(SimpleSQLModel):
    username = StringField()
    password = StringField()
    age = IntField()


my_user = User(username='Bruce_Wayne', password='Batman', age=42)
assert my_user.username == 'Bruce_Wayne'
assert my_user.password == 'Batman'
assert my_user.insert_statement() == "INSERT INTO User(username, password, age) VALUES ('Bruce_Wayne', 'Batman', 42)"

# Fields are ordered by creation time (due to having used the @creation_ordered decorator)
assert list(my_user.get_declared('table_fields').keys()) == ['username', 'password', 'age']

@with_meta

Class decorator to enable a class (and it’s sub-classes) to have a ‘Meta’ class attribute.

The members of the Meta class will be injected as arguments to constructor calls. e.g.:

from tri_declarative import with_meta

@with_meta
class Foo:

    class Meta:
        foo = 'bar'

    def __init__(self, foo, buz):
        assert foo == 'bar'
        assert buz == 'buz'

foo = Foo(buz='buz')

# Members of the 'Meta' class can be accessed thru the get_meta() class method.
assert foo.get_meta() == {'foo': 'bar'}
assert Foo.get_meta() == {'foo': 'bar'}

Foo()  # Crashes, has 'foo' parameter, but no has no 'buz' parameter.

The passing of the merged name space to the constructor is optional. It can be disabled by passing add_init_kwargs=False to the decorator.

from tri_declarative import with_meta

@with_meta(add_init_kwargs=False)
class Foo:
    class Meta:
        foo = 'bar'

Foo()  # No longer crashes
assert Foo().get_meta() == {'foo': 'bar'}

Another example:

from tri_declarative import with_meta

class Foo:

    class Meta:
        foo = 'bar'
        bar = 'bar'

@with_meta
class Bar(Foo):

    class Meta:
        foo = 'foo'
        buz = 'buz'

    def __init__(self, *args, **kwargs):
        assert kwargs['foo'] == 'foo'  # from Bar (overrides Foo)
        assert kwargs['bar'] == 'bar'  # from Foo
        assert kwargs['buz'] == 'buz'  # from Bar

This can be used e.g to enable sub-classes to modify constructor default arguments.

Indices and tables