Skip to content

Commit be457d5

Browse files
committed
work with sqlalchemy 1.4 immutable URL object
1 parent e5f6b7a commit be457d5

File tree

3 files changed

+61
-6
lines changed

3 files changed

+61
-6
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: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,26 @@
4040
before_models_committed = _signals.signal('before-models-committed')
4141

4242

43+
def _sa_url_set(url, **kwargs):
44+
try:
45+
url = url.set(**kwargs)
46+
except AttributeError:
47+
# SQLAlchemy <= 1.3
48+
for key, value in kwargs.items():
49+
setattr(url, key, value)
50+
51+
return url
52+
53+
54+
def _sa_url_query_setdefault(url, **kwargs):
55+
query = dict(url.query)
56+
57+
for key, value in kwargs.items():
58+
query.setdefault(key, value)
59+
60+
return _sa_url_set(url, query=query)
61+
62+
4363
def _make_table(db):
4464
def _make_table(*args, **kwargs):
4565
if len(args) > 1 and isinstance(args[1], db.Column):
@@ -552,7 +572,7 @@ def get_engine(self):
552572
return self._engine
553573

554574
sa_url = make_url(uri)
555-
options = self.get_options(sa_url, echo)
575+
sa_url, options = self.get_options(sa_url, echo)
556576
self._engine = rv = self._sa.create_engine(sa_url, options)
557577

558578
if _record_queries(self._app):
@@ -566,8 +586,9 @@ def get_engine(self):
566586
def get_options(self, sa_url, echo):
567587
options = {}
568588

569-
self._sa.apply_pool_defaults(self._app, options)
570-
self._sa.apply_driver_hacks(self._app, sa_url, options)
589+
options = self._sa.apply_pool_defaults(self._app, options)
590+
sa_url, options = self._sa.apply_driver_hacks(self._app, sa_url, options)
591+
571592
if echo:
572593
options['echo'] = echo
573594

@@ -578,7 +599,7 @@ def get_options(self, sa_url, echo):
578599
# Give options set in SQLAlchemy.__init__() ultimate priority
579600
options.update(self._sa._engine_options)
580601

581-
return options
602+
return sa_url, options
582603

583604

584605
def get_state(app):
@@ -861,6 +882,11 @@ def shutdown_session(response_or_exc):
861882
return response_or_exc
862883

863884
def apply_pool_defaults(self, app, options):
885+
"""
886+
.. versionchanged:: 2.5
887+
Returns the ``options`` dict, for consistency with
888+
:meth:`apply_driver_hacks`.
889+
"""
864890
def _setdefault(optionkey, configkey):
865891
value = app.config[configkey]
866892
if value is not None:
@@ -869,6 +895,7 @@ def _setdefault(optionkey, configkey):
869895
_setdefault('pool_timeout', 'SQLALCHEMY_POOL_TIMEOUT')
870896
_setdefault('pool_recycle', 'SQLALCHEMY_POOL_RECYCLE')
871897
_setdefault('max_overflow', 'SQLALCHEMY_MAX_OVERFLOW')
898+
return options
872899

873900
def apply_driver_hacks(self, app, sa_url, options):
874901
"""This method is called before engine creation and used to inject
@@ -879,9 +906,15 @@ def apply_driver_hacks(self, app, sa_url, options):
879906
The default implementation provides some saner defaults for things
880907
like pool sizes for MySQL and sqlite. Also it injects the setting of
881908
`SQLALCHEMY_NATIVE_UNICODE`.
909+
910+
.. versionchanged:: 2.5
911+
Returns ``(sa_url, options)``. SQLAlchemy 1.4 made the URL
912+
immutable, so any changes to it must now be passed back up
913+
to the original caller.
882914
"""
883915
if sa_url.drivername.startswith('mysql'):
884-
sa_url.query.setdefault('charset', 'utf8')
916+
sa_url = _sa_url_query_setdefault(sa_url, charset="utf8")
917+
885918
if sa_url.drivername != 'mysql+gaerdbms':
886919
options.setdefault('pool_size', 10)
887920
options.setdefault('pool_recycle', 7200)
@@ -911,7 +944,9 @@ def apply_driver_hacks(self, app, sa_url, options):
911944

912945
# if it's not an in memory database we make the path absolute.
913946
if not detected_in_memory:
914-
sa_url.database = os.path.join(app.root_path, sa_url.database)
947+
sa_url = _sa_url_set(
948+
sa_url, database=os.path.join(app.root_path, sa_url.database)
949+
)
915950

916951
unu = app.config['SQLALCHEMY_NATIVE_UNICODE']
917952
if unu is None:
@@ -932,6 +967,8 @@ def apply_driver_hacks(self, app, sa_url, options):
932967
DeprecationWarning
933968
)
934969

970+
return sa_url, options
971+
935972
@property
936973
def engine(self):
937974
"""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)