diff --git a/CHANGES b/CHANGES index dc7aa7a0..fd1291e0 100644 --- a/CHANGES +++ b/CHANGES @@ -18,6 +18,8 @@ Released on February 27, 2017, codename Dubnium the app context and its teardown event. (`#461`_) - Tablename generation logic no longer accesses class properties unless they are ``declared_attr``. (`#467`_) +- Added the ``auto_table_names`` arg to the ``SQLAlchemy`` constructor + which can be used to disable table name generation. (`#476`_) .. _#328: https://github.com/mitsuhiko/flask-sqlalchemy/pull/328 .. _#364: https://github.com/mitsuhiko/flask-sqlalchemy/pull/364 @@ -27,6 +29,7 @@ Released on February 27, 2017, codename Dubnium .. _#460: https://github.com/mitsuhiko/flask-sqlalchemy/pull/460 .. _#461: https://github.com/mitsuhiko/flask-sqlalchemy/pull/461 .. _#467: https://github.com/mitsuhiko/flask-sqlalchemy/pull/467 +.. _#476: https://github.com/mitsuhiko/flask-sqlalchemy/pull/476 Version 2.1 ----------- diff --git a/flask_sqlalchemy/__init__.py b/flask_sqlalchemy/__init__.py index 15da2e3f..992923c9 100644 --- a/flask_sqlalchemy/__init__.py +++ b/flask_sqlalchemy/__init__.py @@ -605,6 +605,13 @@ def __init__(self, name, bases, d): self.__table__.info['bind_key'] = bind_key +class _NoAutoBoundDeclarativeMeta(_BoundDeclarativeMeta): + def __new__(cls, name, bases, d): + d['__no_auto_tablename__'] = True + # skip _BoundDeclarativeMeta.__new__ + return DeclarativeMeta.__new__(cls, name, bases, d) + + def get_state(app): """Gets the state for the application""" assert 'sqlalchemy' in app.extensions, \ @@ -640,6 +647,9 @@ class Model(object): @declared_attr def __tablename__(cls): + if hasattr(cls, '__no_auto_tablename__'): + del cls.__no_auto_tablename__ + return if ( '_cached_tablename' not in cls.__dict__ and _should_set_tablename(cls) @@ -721,15 +731,20 @@ class User(db.Model): The `metadata` parameter was added. This allows for setting custom naming conventions among other, non-trivial things. - .. versionadded:: 3.0 + .. versionadded:: 2.2 The `query_class` parameter was added, to allow customisation of the query class, in place of the default of :class:`BaseQuery`. The `model_class` parameter was added, which allows a custom model class to be used in place of :class:`Model`. - .. versionchanged:: 3.0 + .. versionchanged:: 2.2 Utilise the same query class across `session`, `Model.query` and `Query`. + + ..versionadded:: 2.2 + The `auto_table_names` parameter was added. This allows disabling the + table name generation logic and causes models with no name to fail as + it is the case in vanilla SQLAlchemy. """ #: Default query class used by :attr:`Model.query` and other queries. @@ -738,12 +753,14 @@ class to be used in place of :class:`Model`. Query = None def __init__(self, app=None, use_native_unicode=True, session_options=None, - metadata=None, query_class=BaseQuery, model_class=Model): + metadata=None, query_class=BaseQuery, model_class=Model, + auto_table_names=True): self.use_native_unicode = use_native_unicode self.Query = query_class self.session = self.create_scoped_session(session_options) - self.Model = self.make_declarative_base(model_class, metadata) + self.Model = self.make_declarative_base(model_class, metadata, + auto_table_names) self._engine_lock = Lock() self.app = app _include_sqlalchemy(self, query_class) @@ -796,11 +813,13 @@ class or a :class:`~sqlalchemy.orm.session.sessionmaker`. return orm.sessionmaker(class_=SignallingSession, db=self, **options) - def make_declarative_base(self, model, metadata=None): + def make_declarative_base(self, model, metadata=None, auto_table_names=True): """Creates the declarative base.""" + metaclass = (_BoundDeclarativeMeta if auto_table_names + else _NoAutoBoundDeclarativeMeta) base = declarative_base(cls=model, name='Model', metadata=metadata, - metaclass=_BoundDeclarativeMeta) + metaclass=metaclass) if not getattr(base, 'query_class', None): base.query_class = self.Query diff --git a/test_sqlalchemy.py b/test_sqlalchemy.py index ffcd079a..fdafac0b 100755 --- a/test_sqlalchemy.py +++ b/test_sqlalchemy.py @@ -6,6 +6,7 @@ import flask import sqlalchemy as sa +from sqlalchemy.exc import InvalidRequestError from sqlalchemy.ext.declarative import declared_attr from sqlalchemy.orm import sessionmaker @@ -369,6 +370,16 @@ def floats(self): self.assertTrue(ns.accessed) + def test_no_auto_generation(self): + app = flask.Flask(__name__) + db = fsa.SQLAlchemy(app, auto_table_names=False) + + def _fn(): + class Unnamed(db.Model): + id = db.Column(db.Integer, primary_key=True) + + self.assertRaises(InvalidRequestError, _fn) + class PaginationTestCase(unittest.TestCase): def test_basic_pagination(self):