Add json_sql helper function
This commit is contained in:
@@ -4,10 +4,11 @@ Changelog
|
||||
Here you can see the full list of changes between each SQLAlchemy-Utils release.
|
||||
|
||||
|
||||
0.29.2 (2015-01-xx)
|
||||
0.29.2 (2015-01-08)
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
- Removed deprecated defer_except (SQLAlchemy's own load_only should be used from now on)
|
||||
- Added json_sql PostgreSQL helper function
|
||||
|
||||
|
||||
0.29.1 (2015-01-03)
|
||||
|
@@ -41,6 +41,12 @@ has_unique_index
|
||||
.. autofunction:: has_unique_index
|
||||
|
||||
|
||||
json_sql
|
||||
^^^^^^^^
|
||||
|
||||
.. autofunction:: json_sql
|
||||
|
||||
|
||||
render_expression
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
|
@@ -34,6 +34,7 @@ from .functions import (
|
||||
has_unique_index,
|
||||
identity,
|
||||
is_loaded,
|
||||
json_sql,
|
||||
merge_references,
|
||||
mock_engine,
|
||||
naturally_equivalent,
|
||||
@@ -86,7 +87,7 @@ from .types import (
|
||||
from .models import Timestamp
|
||||
|
||||
|
||||
__version__ = '0.29.1'
|
||||
__version__ = '0.29.2'
|
||||
|
||||
|
||||
__all__ = (
|
||||
|
@@ -14,6 +14,7 @@ from .database import (
|
||||
has_index,
|
||||
has_unique_index,
|
||||
is_auto_assigned_date_column,
|
||||
json_sql
|
||||
)
|
||||
from .foreign_keys import (
|
||||
dependent_objects,
|
||||
@@ -65,6 +66,7 @@ __all__ = (
|
||||
'is_loaded',
|
||||
'is_auto_assigned_date_column',
|
||||
'is_indexed_foreign_key',
|
||||
'json_sql',
|
||||
'make_order_by_deterministic',
|
||||
'mock_engine',
|
||||
'naturally_equivalent',
|
||||
|
@@ -1,3 +1,5 @@
|
||||
import collections
|
||||
import itertools
|
||||
import os
|
||||
from copy import copy
|
||||
|
||||
@@ -105,6 +107,75 @@ def escape_like(string, escape_char='*'):
|
||||
)
|
||||
|
||||
|
||||
def json_sql(value, scalars_to_json=True):
|
||||
"""
|
||||
Convert python data structures to PostgreSQL specific SQLAlchemy JSON
|
||||
constructs. This function is extremly useful if you need to build
|
||||
PostgreSQL JSON on python side.
|
||||
|
||||
.. note::
|
||||
|
||||
This function needs PostgreSQL >= 9.4
|
||||
|
||||
Scalars are converted to to_json SQLAlchemy function objects
|
||||
|
||||
::
|
||||
|
||||
json_sql(1) # Equals SQL: to_json(1)
|
||||
|
||||
json_sql('a') # to_json('a')
|
||||
|
||||
|
||||
Mappings are converted to json_build_object constructs
|
||||
|
||||
::
|
||||
|
||||
json_sql({'a': 'c', '2': 5}) # json_build_object('a', 'c', '2', 5)
|
||||
|
||||
|
||||
Sequences (other than strings) are converted to json_build_array constructs
|
||||
|
||||
::
|
||||
|
||||
json_sql([1, 2, 3]) # json_build_array(1, 2, 3)
|
||||
|
||||
|
||||
You can also nest these data structures
|
||||
|
||||
::
|
||||
|
||||
json_sql({'a': [1, 2, 3]})
|
||||
# json_build_object('a', json_build_array[1, 2, 3])
|
||||
|
||||
|
||||
:param value:
|
||||
value to be converted to SQLAlchemy PostgreSQL function constructs
|
||||
"""
|
||||
scalar_convert = sa.text
|
||||
if scalars_to_json:
|
||||
scalar_convert = lambda a: sa.func.to_json(sa.text(a))
|
||||
|
||||
if isinstance(value, collections.Mapping):
|
||||
return sa.func.json_build_object(
|
||||
*(
|
||||
json_sql(v, scalars_to_json=False)
|
||||
for v in itertools.chain(*value.items())
|
||||
)
|
||||
)
|
||||
elif isinstance(value, str):
|
||||
return scalar_convert("'{0}'".format(value))
|
||||
elif isinstance(value, collections.Sequence):
|
||||
return sa.func.json_build_array(
|
||||
*(
|
||||
json_sql(v, scalars_to_json=False)
|
||||
for v in value
|
||||
)
|
||||
)
|
||||
elif isinstance(value, (int, float)):
|
||||
return scalar_convert(str(value))
|
||||
return value
|
||||
|
||||
|
||||
def has_index(column):
|
||||
"""
|
||||
Return whether or not given column has an index. A column has an index if
|
||||
|
@@ -22,4 +22,3 @@ class TestIsLoaded(object):
|
||||
def test_unloaded_property(self):
|
||||
article = self.Article(id=4)
|
||||
assert not is_loaded(article, 'title')
|
||||
|
||||
|
31
tests/functions/test_json_sql.py
Normal file
31
tests/functions/test_json_sql.py
Normal file
@@ -0,0 +1,31 @@
|
||||
import pytest
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy_utils import json_sql
|
||||
|
||||
from tests import TestCase
|
||||
|
||||
|
||||
class TestJSONSQL(TestCase):
|
||||
dns = 'postgres://postgres@localhost/sqlalchemy_utils_test'
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
('value', 'compiled'),
|
||||
(
|
||||
(1, 'to_json(1)'),
|
||||
(14.14, 'to_json(14.14)'),
|
||||
({'a': 2, 'b': 'c'}, "json_build_object('a', 2, 'b', 'c')"),
|
||||
(
|
||||
{'a': {'b': 'c'}},
|
||||
"json_build_object('a', json_build_object('b', 'c'))"
|
||||
),
|
||||
({}, 'json_build_object()'),
|
||||
([1, 2], 'json_build_array(1, 2)'),
|
||||
([], 'json_build_array()'),
|
||||
(
|
||||
[sa.select([sa.text('1')])],
|
||||
'json_build_array((SELECT 1))'
|
||||
)
|
||||
)
|
||||
)
|
||||
def test_compiled_scalars(self, value, compiled):
|
||||
assert str(json_sql(value).compile(self.connection)) == compiled
|
Reference in New Issue
Block a user