Issue: Allow Meta `database` to be callable
Author: foxx
State: closed
Created: 2016-01-19 17:26:27

As per other issues, I'm currently implementing a database router and connection manager for Peewee.

This allows me to do stuff like;

``` python
class User(Model):
    class Meta:
        # in this example, current_app() is a global context manager which
        # uses thread locals to determine the current application being used
        # in combination with Bottle. It's kinda like ExecutionContext, but for
        # applications instead
        database = lambda: current_app().databases['default']
```

This approach relies on Peewee allowing callables (or properties) being passed into ModelOptions, something which is not currently supported.

I've been able to workaround this by using the following monkey patch;

``` python
import peewee
class CustomModelOptions(peewee.ModelOptions):
    @property
    def database(self):
        if callable(self._database):
            return self._database()
        return self._database

    @database.setter
    def database(self, value):
        self._database = value

peewee.ModelOptions = CustomModelOptions
```

The advantages of such a patch are;
- Provides an alternative to those that /really/ want `Proxy()`
- Allows database routers and connection managers to be implemented without monkey patching or  subclassing `BaseModel`.

@coleifer Would such functionality be merged into the core, obviously implemented in a cleaner fashion, if it had a good PR?


In the event that callable support is not added to the core, here is the updated version of the monkeypatch which can be injected at any point during the runtime. There is a discussion in #804 about why this approach is required for some patches (due to bugs in Python core).

``` python
def ModelOptions__getattribute__(self, key):
    orig = lambda: super(peewee.ModelOptions, self).__getattribute__(key)
    if key == 'database':
        db = orig()
        return db(self) if callable(db) else db
    return orig()
peewee.ModelOptions.__getattribute__ = ModelOptions__getattribute__
```


Thinking about this a bit, the solution I've come up with is to allow providing a custom base class for the `ModelOptions`. You can see one way of implementing a dynamic database in e773c620de3d7dc686dab3c2325e02d1e86407d6

Does this address the issue?


Cloning this locally now for testing, will update shortly


This worked beautifully, +1 from me.

My usage was slightly different than the one shown in test.

``` python
from peewee import peewee

class CallableDatabaseMixin(object):
    @property
    def database(self):
        if callable(self._database):
            return self._database(self)
        return self._database

    @database.setter
    def database(self, value):
        self._database = value


class CustomModelOptions(CallableDatabaseMixin, peewee.ModelOptions):
    pass


class CustomModel(peewee.Model):
    class Meta:
        model_options_base = CustomModelOptions

db = peewee.SqliteDatabase(':memory:')

class A(CustomModel):
    class Meta:
        database = lambda meta: db


class B(CustomModel):
    class Meta:
        database = db


assert A._meta.database == db
assert B._meta.database == db
```


Awesome


PS) Thanks again for the quick turnaround!


@coleifer Any chance of an updated release with this functionality?


Ahh, yeah I will try and take care of that in the next couple days.