Customizing¶
Flask-SQLAlchemy defines sensible defaults. However, sometimes customization is needed. There are various ways to customize how the models are defined and interacted with.
These customizations are applied at the creation of the SQLAlchemy
object and extend to all models derived from its Model
class.
Model Class¶
SQLAlchemy models all inherit from a declarative base class. This is exposed
as db.Model
in Flask-SQLAlchemy, which all models extend. This can be
customized by subclassing the default and passing the custom class to
model_class
.
The following example gives every model an integer primary key, or a foreign key for joined-table inheritance.
Note
Integer primary keys for everything is not necessarily the best database design (that’s up to your project’s requirements), this is only an example.
from flask_sqlalchemy import Model, SQLAlchemy
import sqlalchemy as sa
from sqlalchemy.ext.declarative import declared_attr, has_inherited_table
class IdModel(Model):
@declared_attr
def id(cls):
for base in cls.__mro__[1:-1]:
if getattr(base, '__table__', None) is not None:
type = sa.ForeignKey(base.id)
break
else:
type = sa.Integer
return sa.Column(type, primary_key=True)
db = SQLAlchemy(model_class=IdModel)
class User(db.Model):
name = db.Column(db.String)
class Employee(User):
title = db.Column(db.String)
Model Mixins¶
If behavior is only needed on some models rather than all models, use mixin classes to customize only those models. For example, if some models should track when they are created or updated:
from datetime import datetime
class TimestampMixin(object):
created = db.Column(
db.DateTime, nullable=False, default=datetime.utcnow)
updated = db.Column(db.DateTime, onupdate=datetime.utcnow)
class Author(db.Model):
...
class Post(TimestampMixin, db.Model):
...
Query Class¶
It is also possible to customize what is available for use on the
special query
property of models. For example, providing a
get_or
method:
from flask_sqlalchemy import BaseQuery, SQLAlchemy
class GetOrQuery(BaseQuery):
def get_or(self, ident, default=None):
return self.get(ident) or default
db = SQLAlchemy(query_class=GetOrQuery)
# get a user by id, or return an anonymous user instance
user = User.query.get_or(user_id, anonymous_user)
And now all queries executed from the special query
property
on Flask-SQLAlchemy models can use the get_or
method as part
of their queries. All relationships defined with
db.relationship
(but not sqlalchemy.orm.relationship()
)
will also be provided with this functionality.
It also possible to define a custom query class for individual
relationships as well, by providing the query_class
keyword
in the definition. This works with both db.relationship
and sqlalchemy.relationship
:
class MyModel(db.Model):
cousin = db.relationship('OtherModel', query_class=GetOrQuery)
Note
If a query class is defined on a relationship, it will take precedence over the query class attached to its corresponding model.
It is also possible to define a specific query class for individual models
by overriding the query_class
class attribute on the model:
class MyModel(db.Model):
query_class = GetOrQuery
In this case, the get_or
method will be only availble on queries
orginating from MyModel.query
.
Model Metaclass¶
Warning
Metaclasses are an advanced topic, and you probably don’t need to customize them to achieve what you want. It is mainly documented here to show how to disable table name generation.
The model metaclass is responsible for setting up the SQLAlchemy internals when
defining model subclasses. Flask-SQLAlchemy adds some extra behaviors through
mixins; its default metaclass, DefaultMeta
, inherits them all.
BindMetaMixin
:__bind_key__
is extracted from the class and applied to the table. See Multiple Databases with Binds.NameMetaMixin
: If the model does not specify a__tablename__
but does specify a primary key, a name is automatically generated.
You can add your own behaviors by defining your own metaclass and creating the declarative base yourself. Be sure to still inherit from the mixins you want (or just inherit from the default metaclass).
Passing a declarative base class instead of a simple model base class, as shown
above, to base_class
will cause Flask-SQLAlchemy to use this base instead
of constructing one with the default metaclass.
from flask_sqlalchemy import SQLAlchemy
from flask_sqlalchemy.model import DefaultMeta, Model
class CustomMeta(DefaultMeta):
def __init__(cls, name, bases, d):
# custom class setup could go here
# be sure to call super
super(CustomMeta, cls).__init__(name, bases, d)
# custom class-only methods could go here
db = SQLAlchemy(model_class=declarative_base(
cls=Model, metaclass=CustomMeta, name='Model'))
You can also pass whatever other arguments you want to
declarative_base()
to customize the base
class as needed.
Disabling Table Name Generation¶
Some projects prefer to set each model’s __tablename__
manually rather than
relying on Flask-SQLAlchemy’s detection and generation. The table name
generation can be disabled by defining a custom metaclass.
from flask_sqlalchemy.model import BindMetaMixin, Model
from sqlalchemy.ext.declarative import DeclarativeMeta, declarative_base
class NoNameMeta(BindMetaMixin, DeclarativeMeta):
pass
db = SQLAlchemy(model_class=declarative_base(
cls=Model, metaclass=NoNameMeta, name='Model'))
This creates a base that still supports the __bind_key__
feature but does
not generate table names.