Add json_sql helper function

This commit is contained in:
Konsta Vesterinen
2015-01-08 13:49:14 +02:00
parent 2143490f92
commit c1389d9e07
7 changed files with 114 additions and 3 deletions

View File

@@ -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)

View File

@@ -41,6 +41,12 @@ has_unique_index
.. autofunction:: has_unique_index
json_sql
^^^^^^^^
.. autofunction:: json_sql
render_expression
^^^^^^^^^^^^^^^^^

View File

@@ -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__ = (

View File

@@ -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',

View File

@@ -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

View File

@@ -22,4 +22,3 @@ class TestIsLoaded(object):
def test_unloaded_property(self):
article = self.Article(id=4)
assert not is_loaded(article, 'title')

View 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