Skip to content
Merged
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
1 change: 1 addition & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ In development

- Table names are automatically generated in more cases, including
subclassing mixins and abstract models.
- Allow using a custom MetaData object.

Version 2.0
-----------
Expand Down
34 changes: 34 additions & 0 deletions docs/config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -107,3 +107,37 @@ Oracle::
SQLite (note the four leading slashes)::

sqlite:////absolute/path/to/foo.db

Using custom MetaData and naming conventions
--------------------------------------------

You can optionally construct the :class:`SQLAlchemy` object with a custom
:class:`~sqlalchemy.schema.MetaData` object.
This allows you to, among other things,
specify a `custom constraint naming convention
<http://docs.sqlalchemy.org/en/latest/core/constraints.html#constraint-naming-conventions>`_.
Doing so is important for dealing with database migrations (for instance using
`alembic <https://alembic.readthedocs.org>`_ as stated
`here <http://alembic.readthedocs.org/en/latest/naming.html>`_. Since SQL
defines no standard naming conventions, there is no guaranteed nor effective
compatibility by default among database implementations. You can define a
custom naming convention like this as suggested by the SQLAlchemy docs::

from sqlalchemy import MetaData
from flask import Flask
from flask.ext.sqlalchemy import SQLAlchemy

convention = {
"ix": 'ix_%(column_0_label)s',
"uq": "uq_%(table_name)s_%(column_0_name)s",
"ck": "ck_%(table_name)s_%(constraint_name)s",
"fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
"pk": "pk_%(table_name)s"
}

metadata = MetaData(naming_convention=convention)
db = SQLAlchemy(app, metadata=metadata)

For more info about :class:`~sqlalchemy.schema.MetaData`,
`check out the official docs on it
<http://docs.sqlalchemy.org/en/latest/core/metadata.html>`_.
12 changes: 9 additions & 3 deletions flask_sqlalchemy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -678,11 +678,16 @@ class User(db.Model):
.. versionadded:: 0.16
`scopefunc` is now accepted on `session_options`. It allows specifying
a custom function which will define the SQLAlchemy session's scoping.

.. versionadded:: 2.1
The `metadata` parameter was added. This allows for setting custom
naming conventions among other, non-trivial things.
"""

def __init__(self, app=None,
use_native_unicode=True,
session_options=None):
session_options=None,
metadata=None):
self.use_native_unicode = use_native_unicode

if session_options is None:
Expand All @@ -693,7 +698,7 @@ def __init__(self, app=None,
)

self.session = self.create_scoped_session(session_options)
self.Model = self.make_declarative_base()
self.Model = self.make_declarative_base(metadata)
self._engine_lock = Lock()

if app is not None:
Expand Down Expand Up @@ -730,9 +735,10 @@ def create_session(self, options):
"""
return SignallingSession(self, **options)

def make_declarative_base(self):
def make_declarative_base(self, metadata=None):
"""Creates the declarative base."""
base = declarative_base(cls=Model, name='Model',
metadata=metadata,
metaclass=_BoundDeclarativeMeta)
base.query = _QueryProperty(self)
return base
Expand Down
59 changes: 59 additions & 0 deletions test_sqlalchemy.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from datetime import datetime
import flask
from flask.ext import sqlalchemy
from sqlalchemy import MetaData
from sqlalchemy.ext.declarative import declared_attr
from sqlalchemy.orm import sessionmaker

Expand Down Expand Up @@ -81,6 +82,63 @@ def test_helper_api(self):
self.assertEqual(self.db.metadata, self.db.Model.metadata)


class CustomMetaDataTestCase(unittest.TestCase):

def setUp(self):
self.app = flask.Flask(__name__)
self.app.config['SQLALCHEMY_ENGINE'] = 'sqlite://'
self.app.config['TESTING'] = True

def test_custom_metadata_positive(self):

convention = {
"ix": 'ix_%(column_0_label)s',
"uq": "uq_%(table_name)s_%(column_0_name)s",
"ck": "ck_%(table_name)s_%(constraint_name)s",
"fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
"pk": "pk_%(table_name)s"
}

metadata = MetaData(naming_convention=convention)
db = sqlalchemy.SQLAlchemy(self.app, metadata=metadata)
self.db = db

class One(db.Model):
id = db.Column(db.Integer, primary_key=True)
myindex = db.Column(db.Integer, index=True)

class Two(db.Model):
id = db.Column(db.Integer, primary_key=True)
one_id = db.Column(db.Integer, db.ForeignKey(One.id))
myunique = db.Column(db.Integer, unique=True)

self.assertEqual(list(One.__table__.constraints)[0].name, 'pk_one')
self.assertEqual(list(One.__table__.indexes)[0].name, 'ix_one_myindex')

self.assertIn('fk_two_one_id_one', [c.name for c in Two.__table__.constraints])
self.assertIn('uq_two_myunique', [c.name for c in Two.__table__.constraints])
self.assertIn('pk_two', [c.name for c in Two.__table__.constraints])

def test_custom_metadata_negative(self):
db = sqlalchemy.SQLAlchemy(self.app, metadata=None)
self.db = db

class One(db.Model):
id = db.Column(db.Integer, primary_key=True)
myindex = db.Column(db.Integer, index=True)

class Two(db.Model):
id = db.Column(db.Integer, primary_key=True)
one_id = db.Column(db.Integer, db.ForeignKey(One.id))
myunique = db.Column(db.Integer, unique=True)

self.assertNotEqual(list(One.__table__.constraints)[0].name, 'pk_one')

self.assertNotIn('fk_two_one_id_one', [c.name for c in Two.__table__.constraints])
self.assertNotIn('uq_two_myunique', [c.name for c in Two.__table__.constraints])
self.assertNotIn('pk_two', [c.name for c in Two.__table__.constraints])


class TestQueryProperty(unittest.TestCase):

def setUp(self):
Expand Down Expand Up @@ -591,6 +649,7 @@ class QazWsx(db.Model):
def suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(BasicAppTestCase))
suite.addTest(unittest.makeSuite(CustomMetaDataTestCase))
suite.addTest(unittest.makeSuite(TestQueryProperty))
suite.addTest(unittest.makeSuite(TablenameTestCase))
suite.addTest(unittest.makeSuite(PaginationTestCase))
Expand Down