Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions docs/models.rst
Original file line number Diff line number Diff line change
Expand Up @@ -143,3 +143,71 @@ Here we configured `Page.tags` to be a list of tags once loaded because we
don't expect too many tags per page. The list of pages per tag
(`Tag.pages`) however is a dynamic backref. As mentioned above this means
that you will get a query object back you can use to fire a select yourself.


External Declarative Bases
--------------------------

Flask-SQLAlchemy allows you to provide your own declarative base if you
feel the need to do so. Doing so can allow you to cut down circular
imports, allow you to use the app factory pattern, or even share your
SQLAlchemy model across different python application using SQLAlchemy,
without the requirement of running a different set of models for use with
Flask-SQLAlchemy.

We declare our model just as you would above, however, using SQLAlchemy
constructs, rather than accessing classes via ``db.*``. Once defined, call
call :meth:`SQLAlchemy.register_base` to register your delcarative base with
Flask-SQLAlchemy.

A minimal example::

from sqlalchemy import Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class User(Base):
__tablename__ = 'user'

id = Column(Integer, primary_key=True)
username = Column(String(80), unique=True)
email = Column(String(255), unique=True)

def __repr__(self):
return '<User %r>' % self.username

from flask import Flask
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///'
db = SQLAlchemy(app)
db.register_base(Base)

db.create_all()

@app.before_first_request
def insert_user():
# We can create new objects the normal way
user = User(id=1, username='foo', email='foo@bar.com')
db.session.add(user)
db.session.commit()

@app.route('/<int:user_id>')
def index(user_id):
# We can query the model two ways:
user = db.session.query(User).get_or_404(user_id)

# Or we can using the model's query property
user = User.query.get_or_404(user_id)

return "Hello, {}".format(user.username)

if __name__ == '__main__':
app.run(debug=True)

If you're using binds, you'll need to use your own session that knows how
to handle them, or use the same ``Model.__bind__`` system and register the
extra engines with ``SQLALCHEMY_BINDS``

56 changes: 45 additions & 11 deletions flask_sqlalchemy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -734,13 +734,17 @@ def __init__(self, app=None, use_native_unicode=True, session_options=None,
self._engine_lock = Lock()
self.app = app
_include_sqlalchemy(self, query_class)
self.external_bases = []

if app is not None:
self.init_app(app)

@property
def metadata(self):
"""The metadata associated with ``db.Model``."""
"""The metadata associated with ``db.Model``.
Access to raw SQLA models added using register_base should
be referenced directly using it's own declarative base.
"""

return self.Model.metadata

Expand Down Expand Up @@ -943,9 +947,11 @@ def get_app(self, reference_app=None):
def get_tables_for_bind(self, bind=None):
"""Returns a list of all tables relevant for a bind."""
result = []
for table in itervalues(self.Model.metadata.tables):
if table.info.get('bind_key') == bind:
result.append(table)
for Base in self.bases:
for table in itervalues(Base.metadata.tables):
if table.info.get('bind_key') == bind:
result.append(table)

return result

def get_binds(self, app=None):
Expand All @@ -972,13 +978,14 @@ def _execute_for_all_tables(self, app, bind, operation, skip_tables=False):
else:
binds = bind

for bind in binds:
extra = {}
if not skip_tables:
tables = self.get_tables_for_bind(bind)
extra['tables'] = tables
op = getattr(self.Model.metadata, operation)
op(bind=self.get_engine(app, bind), **extra)
for Base in self.bases:
for bind in binds:
extra = {}
if not skip_tables:
tables = self.get_tables_for_bind(bind)
extra['tables'] = tables
op = getattr(Base.metadata, operation)
op(bind=self.get_engine(app, bind), **extra)

def create_all(self, bind='__all__', app=None):
"""Creates all tables.
Expand Down Expand Up @@ -1017,6 +1024,33 @@ def __repr__(self):
app and app.config['SQLALCHEMY_DATABASE_URI'] or None
)

@property
def bases(self):
return [self.Model] + self.external_bases

def register_base(self, Base):
"""Register an external raw SQLAlchemy declarative base.
Allows usage of the base with our session management and
adds convenience query property using self.Query by default."""

self.external_bases.append(Base)
for c in Base._decl_class_registry.values():
if isinstance(c, type):
if not hasattr(c, 'query') and not hasattr(c, 'query_class'):
c.query_class = self.Query
if not hasattr(c, 'query'):
c.query = _QueryProperty(self)

# for name in dir(c):
# attr = getattr(c, name)
# if type(attr) == orm.attributes.InstrumentedAttribute:
# if hasattr(attr.prop, 'query_class'):
# attr.prop.query_class = self.Query

# if hasattr(c , 'rel_dynamic'):
# c.rel_dynamic.prop.query_class = self.Query



class FSADeprecationWarning(DeprecationWarning):
pass
Expand Down
Loading