Skip to content

Commit b0b71ff

Browse files
authored
Merge pull request #933 from pallets/sqlalchemy-1.4
Support SQLAlchemy 1.4
2 parents e5f6b7a + 978d8d1 commit b0b71ff

File tree

3 files changed

+70
-8
lines changed

3 files changed

+70
-8
lines changed

CHANGES.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
Version 2.5
2+
-----------
3+
4+
Unreleased
5+
6+
- Update to support SQLAlchemy 1.4.
7+
- SQLAlchemy ``URL`` objects are immutable. Some internal methods have
8+
changed to return a new URL instead of ``None``. :issue:`885`
9+
10+
111
Version 2.4.4
212
-------------
313

flask_sqlalchemy/__init__.py

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,22 @@
1515
from flask.signals import Namespace
1616
from sqlalchemy import event, inspect, orm
1717
from sqlalchemy.engine.url import make_url
18-
from sqlalchemy.ext.declarative import DeclarativeMeta, declarative_base
1918
from sqlalchemy.orm.exc import UnmappedClassError
2019
from sqlalchemy.orm.session import Session as SessionBase
2120

22-
from flask_sqlalchemy.model import Model
2321
from ._compat import itervalues, string_types, xrange
2422
from .model import DefaultMeta
23+
from .model import Model
2524
from . import utils
2625

26+
try:
27+
from sqlalchemy.orm import declarative_base
28+
from sqlalchemy.orm import DeclarativeMeta
29+
except ImportError:
30+
# SQLAlchemy <= 1.3
31+
from sqlalchemy.ext.declarative import declarative_base
32+
from sqlalchemy.ext.declarative import DeclarativeMeta
33+
2734
__version__ = "2.4.4"
2835

2936
# the best timer function for the platform
@@ -40,6 +47,26 @@
4047
before_models_committed = _signals.signal('before-models-committed')
4148

4249

50+
def _sa_url_set(url, **kwargs):
51+
try:
52+
url = url.set(**kwargs)
53+
except AttributeError:
54+
# SQLAlchemy <= 1.3
55+
for key, value in kwargs.items():
56+
setattr(url, key, value)
57+
58+
return url
59+
60+
61+
def _sa_url_query_setdefault(url, **kwargs):
62+
query = dict(url.query)
63+
64+
for key, value in kwargs.items():
65+
query.setdefault(key, value)
66+
67+
return _sa_url_set(url, query=query)
68+
69+
4370
def _make_table(db):
4471
def _make_table(*args, **kwargs):
4572
if len(args) > 1 and isinstance(args[1], db.Column):
@@ -552,7 +579,7 @@ def get_engine(self):
552579
return self._engine
553580

554581
sa_url = make_url(uri)
555-
options = self.get_options(sa_url, echo)
582+
sa_url, options = self.get_options(sa_url, echo)
556583
self._engine = rv = self._sa.create_engine(sa_url, options)
557584

558585
if _record_queries(self._app):
@@ -566,8 +593,9 @@ def get_engine(self):
566593
def get_options(self, sa_url, echo):
567594
options = {}
568595

569-
self._sa.apply_pool_defaults(self._app, options)
570-
self._sa.apply_driver_hacks(self._app, sa_url, options)
596+
options = self._sa.apply_pool_defaults(self._app, options)
597+
sa_url, options = self._sa.apply_driver_hacks(self._app, sa_url, options)
598+
571599
if echo:
572600
options['echo'] = echo
573601

@@ -578,7 +606,7 @@ def get_options(self, sa_url, echo):
578606
# Give options set in SQLAlchemy.__init__() ultimate priority
579607
options.update(self._sa._engine_options)
580608

581-
return options
609+
return sa_url, options
582610

583611

584612
def get_state(app):
@@ -861,6 +889,11 @@ def shutdown_session(response_or_exc):
861889
return response_or_exc
862890

863891
def apply_pool_defaults(self, app, options):
892+
"""
893+
.. versionchanged:: 2.5
894+
Returns the ``options`` dict, for consistency with
895+
:meth:`apply_driver_hacks`.
896+
"""
864897
def _setdefault(optionkey, configkey):
865898
value = app.config[configkey]
866899
if value is not None:
@@ -869,6 +902,7 @@ def _setdefault(optionkey, configkey):
869902
_setdefault('pool_timeout', 'SQLALCHEMY_POOL_TIMEOUT')
870903
_setdefault('pool_recycle', 'SQLALCHEMY_POOL_RECYCLE')
871904
_setdefault('max_overflow', 'SQLALCHEMY_MAX_OVERFLOW')
905+
return options
872906

873907
def apply_driver_hacks(self, app, sa_url, options):
874908
"""This method is called before engine creation and used to inject
@@ -879,9 +913,15 @@ def apply_driver_hacks(self, app, sa_url, options):
879913
The default implementation provides some saner defaults for things
880914
like pool sizes for MySQL and sqlite. Also it injects the setting of
881915
`SQLALCHEMY_NATIVE_UNICODE`.
916+
917+
.. versionchanged:: 2.5
918+
Returns ``(sa_url, options)``. SQLAlchemy 1.4 made the URL
919+
immutable, so any changes to it must now be passed back up
920+
to the original caller.
882921
"""
883922
if sa_url.drivername.startswith('mysql'):
884-
sa_url.query.setdefault('charset', 'utf8')
923+
sa_url = _sa_url_query_setdefault(sa_url, charset="utf8")
924+
885925
if sa_url.drivername != 'mysql+gaerdbms':
886926
options.setdefault('pool_size', 10)
887927
options.setdefault('pool_recycle', 7200)
@@ -911,7 +951,9 @@ def apply_driver_hacks(self, app, sa_url, options):
911951

912952
# if it's not an in memory database we make the path absolute.
913953
if not detected_in_memory:
914-
sa_url.database = os.path.join(app.root_path, sa_url.database)
954+
sa_url = _sa_url_set(
955+
sa_url, database=os.path.join(app.root_path, sa_url.database)
956+
)
915957

916958
unu = app.config['SQLALCHEMY_NATIVE_UNICODE']
917959
if unu is None:
@@ -932,6 +974,8 @@ def apply_driver_hacks(self, app, sa_url, options):
932974
DeprecationWarning
933975
)
934976

977+
return sa_url, options
978+
935979
@property
936980
def engine(self):
937981
"""Gives access to the engine. If the database configuration is bound

tests/test_config.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import os
2+
13
import mock
24
import pytest
35
from sqlalchemy.pool import NullPool
@@ -216,3 +218,9 @@ def test_pool_class_nullpool(self, m_create_engine, app_nr):
216218
args, options = m_create_engine.call_args
217219
assert options['poolclass'].__name__ == 'NullPool'
218220
assert 'pool_size' not in options
221+
222+
223+
def test_sqlite_relative_to_app_root(app):
224+
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///test.db"
225+
db = fsa.SQLAlchemy(app)
226+
assert db.engine.url.database == os.path.join(app.root_path, "test.db")

0 commit comments

Comments
 (0)