summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2019-08-08 23:34:20 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2019-08-08 23:52:51 -0400
commit104e6907284e602a8485f32fc67fd6af0c00e4d0 (patch)
treefd628fbc353341fa31953b5f55be98b9748e6bea /lib/sqlalchemy
parentd8da7f5ac544f3dd853a221faa5fab4ff788e25b (diff)
downloadsqlalchemy-104e6907284e602a8485f32fc67fd6af0c00e4d0.tar.gz
Correct name for json_serializer / json_deserializer, document and test
The dialects that support json are supposed to take arguments ``json_serializer`` and ``json_deserializer`` at the create_engine() level, however the SQLite dialect calls them ``_json_serilizer`` and ``_json_deserilalizer``. The names have been corrected, the old names are accepted with a change warning, and these parameters are now documented as :paramref:`.create_engine.json_serializer` and :paramref:`.create_engine.json_deserializer`. Fixes: #4798 Change-Id: I1dbfe439b421fe9bb7ff3594ef455af8156f8851
Diffstat (limited to 'lib/sqlalchemy')
-rw-r--r--lib/sqlalchemy/dialects/sqlite/base.py25
-rw-r--r--lib/sqlalchemy/engine/create.py16
-rw-r--r--lib/sqlalchemy/sql/sqltypes.py20
-rw-r--r--lib/sqlalchemy/testing/suite/test_types.py26
4 files changed, 85 insertions, 2 deletions
diff --git a/lib/sqlalchemy/dialects/sqlite/base.py b/lib/sqlalchemy/dialects/sqlite/base.py
index ef8507d05..78ce18ac6 100644
--- a/lib/sqlalchemy/dialects/sqlite/base.py
+++ b/lib/sqlalchemy/dialects/sqlite/base.py
@@ -1426,18 +1426,39 @@ class SQLiteDialect(default.DefaultDialect):
_broken_fk_pragma_quotes = False
_broken_dotted_colnames = False
+ @util.deprecated_params(
+ _json_serializer=(
+ "1.3.7",
+ "The _json_serializer argument to the SQLite dialect has "
+ "been renamed to the correct name of json_serializer. The old "
+ "argument name will be removed in a future release.",
+ ),
+ _json_deserializer=(
+ "1.3.7",
+ "The _json_deserializer argument to the SQLite dialect has "
+ "been renamed to the correct name of json_deserializer. The old "
+ "argument name will be removed in a future release.",
+ ),
+ )
def __init__(
self,
isolation_level=None,
native_datetime=False,
+ json_serializer=None,
+ json_deserializer=None,
_json_serializer=None,
_json_deserializer=None,
**kwargs
):
default.DefaultDialect.__init__(self, **kwargs)
self.isolation_level = isolation_level
- self._json_serializer = _json_serializer
- self._json_deserializer = _json_deserializer
+
+ if _json_serializer:
+ json_serializer = _json_serializer
+ if _json_deserializer:
+ json_deserializer = _json_deserializer
+ self._json_serializer = json_serializer
+ self._json_deserializer = json_deserializer
# this flag used by pysqlite dialect, and perhaps others in the
# future, to indicate the driver is handling date/timestamp
diff --git a/lib/sqlalchemy/engine/create.py b/lib/sqlalchemy/engine/create.py
index 035953e99..cc8304131 100644
--- a/lib/sqlalchemy/engine/create.py
+++ b/lib/sqlalchemy/engine/create.py
@@ -234,6 +234,22 @@ def create_engine(url, **kwargs):
:ref:`session_transaction_isolation` - for the ORM
+ :param json_deserializer: for dialects that support the :class:`.JSON`
+ datatype, this is a Python callable that will convert a JSON string
+ to a Python object. By default, the Python ``json.loads`` function is
+ used.
+
+ .. versionchanged:: 1.3.7 The SQLite dialect renamed this from
+ ``_json_deserializer``.
+
+ :param json_serializer: for dialects that support the :class:`.JSON`
+ datatype, this is a Python callable that will render a given object
+ as JSON. By default, the Python ``json.dumps`` function is used.
+
+ .. versionchanged:: 1.3.7 The SQLite dialect renamed this from
+ ``_json_serializer``.
+
+
:param label_length=None: optional integer value which limits
the size of dynamically generated column labels to that many
characters. If less than 6, labels are generated as
diff --git a/lib/sqlalchemy/sql/sqltypes.py b/lib/sqlalchemy/sql/sqltypes.py
index 38731fcbe..631352ceb 100644
--- a/lib/sqlalchemy/sql/sqltypes.py
+++ b/lib/sqlalchemy/sql/sqltypes.py
@@ -2042,6 +2042,26 @@ class JSON(Indexable, TypeEngine):
values, but care must be taken as to the value of the
:paramref:`.JSON.none_as_null` in these cases.
+ The JSON serializer and deserializer used by :class:`.JSON` defaults to
+ Python's ``json.dumps`` and ``json.loads`` functions; in the case of the
+ psycopg2 dialect, psycopg2 may be using its own custom loader function.
+
+ In order to affect the serializer / deserializer, they are currently
+ configurable at the :func:`.create_engine` level via the
+ :paramref:`.create_engine.json_serializer` and
+ :paramref:`.create_engine.json_deserializer` parameters. For example,
+ to turn off ``ensure_ascii``::
+
+ engine = create_engine(
+ "sqlite://",
+ json_serializer=lambda obj: json.dumps(obj, ensure_ascii=False))
+
+ .. versionchanged:: 1.3.7
+
+ SQLite dialect's ``json_serializer`` and ``json_deserializer``
+ parameters renamed from ``_json_serializer`` and
+ ``_json_deserializer``.
+
.. seealso::
:class:`.postgresql.JSON`
diff --git a/lib/sqlalchemy/testing/suite/test_types.py b/lib/sqlalchemy/testing/suite/test_types.py
index 1e02c0e74..3320dd93c 100644
--- a/lib/sqlalchemy/testing/suite/test_types.py
+++ b/lib/sqlalchemy/testing/suite/test_types.py
@@ -2,8 +2,12 @@
import datetime
import decimal
+import json
+
+import mock
from .. import config
+from .. import engines
from .. import fixtures
from ..assertions import eq_
from ..config import requirements
@@ -727,6 +731,28 @@ class JSONTest(_LiteralRoundTripFixture, fixtures.TablesTest):
eq_(row, (data_element,))
+ def test_round_trip_custom_json(self):
+ data_table = self.tables.data_table
+ data_element = self.data1
+
+ js = mock.Mock(side_effect=json.dumps)
+ jd = mock.Mock(side_effect=json.loads)
+ engine = engines.testing_engine(
+ options=dict(json_serializer=js, json_deserializer=jd)
+ )
+
+ # support sqlite :memory: database...
+ data_table.create(engine, checkfirst=True)
+ engine.execute(
+ data_table.insert(), {"name": "row1", "data": data_element}
+ )
+
+ row = engine.execute(select([data_table.c.data])).first()
+
+ eq_(row, (data_element,))
+ eq_(js.mock_calls, [mock.call(data_element)])
+ eq_(jd.mock_calls, [mock.call(json.dumps(data_element))])
+
def test_round_trip_none_as_sql_null(self):
col = self.tables.data_table.c["nulldata"]