mirror of
https://github.com/psycopg/psycopg2.git
synced 2024-11-11 03:26:37 +03:00
Added Json adapter
This commit is contained in:
parent
64e3e1199e
commit
b8e7f02256
|
@ -128,6 +128,33 @@ Additional data types
|
|||
---------------------
|
||||
|
||||
|
||||
.. _adapt-json:
|
||||
|
||||
.. index::
|
||||
pair: JSON; Data types
|
||||
pair: JSON; Adaptation
|
||||
|
||||
JSON adaptation
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
.. versionadded:: 2.4.6
|
||||
|
||||
Psycopg can use an underlying JSON_ module implementation to adapt Python
|
||||
objects to and from the PostgreSQL |pgjson|_ data type. The library used
|
||||
depends on the Python version: with Python 2.6 and following the
|
||||
:py:mod:`json` module from the standard library is used; with previous
|
||||
versions the `simplejson`_ module is be used if available. Note that the last
|
||||
`!simplejson` version supporting Python 2.4 is the 2.0.9.
|
||||
|
||||
.. _JSON: http://www.json.org/
|
||||
.. |pgjson| replace:: :sql:`json`
|
||||
.. _pgjson: http://www.postgresql.org/docs/current/static/datatype-json.html
|
||||
.. _simplejson: http://pypi.python.org/pypi/simplejson/
|
||||
|
||||
.. autoclass:: Json
|
||||
|
||||
|
||||
|
||||
.. _adapt-hstore:
|
||||
|
||||
.. index::
|
||||
|
|
|
@ -967,4 +967,59 @@ def register_composite(name, conn_or_curs, globally=False):
|
|||
return caster
|
||||
|
||||
|
||||
# import the best json implementation available
|
||||
if sys.version_info[:2] >= (2,6):
|
||||
import json
|
||||
else:
|
||||
try:
|
||||
import simplejson as json
|
||||
except ImportError:
|
||||
json = None
|
||||
|
||||
|
||||
class Json(object):
|
||||
"""A wrapper to adapt a Python object to :sql:`json` data type.
|
||||
|
||||
`!Json` can be used to wrap any object supported by the underlying
|
||||
`!json` module. Any keyword argument will be passed to the
|
||||
underlying :py:func:`json.dumps()` function, allowing extension and
|
||||
customization. ::
|
||||
|
||||
curs.execute("insert into mytable (jsondata) values (%s)",
|
||||
(Json({'a': 100}),))
|
||||
|
||||
.. note::
|
||||
|
||||
You can use `~psycopg2.extensions.register_adapter()` to adapt Python
|
||||
dictionaries to JSON::
|
||||
|
||||
psycopg2.extensions.register_adapter(dict,
|
||||
psycopg2.extras.Json)
|
||||
|
||||
This setting is global though, so it is not compatible with the use of
|
||||
`register_hstore()`. Any other object supported by the `!json` library
|
||||
used by Psycopg can be registered the same way, but this will clobber
|
||||
the default adaptation rule, so be careful to unwanted side effects.
|
||||
|
||||
"""
|
||||
def __init__(self, adapted, **kwargs):
|
||||
self.adapted = adapted
|
||||
self.kwargs = kwargs
|
||||
|
||||
def __conform__(self, proto):
|
||||
if proto is _ext.ISQLQuote:
|
||||
return self
|
||||
|
||||
def getquoted(self):
|
||||
s = json.dumps(self.adapted, **self.kwargs)
|
||||
return _ext.QuotedString(s).getquoted()
|
||||
|
||||
|
||||
# clobber the above class if json is not available
|
||||
if json is None:
|
||||
class Json(Json):
|
||||
def __init__(self, adapted):
|
||||
raise ImportError("no json module available")
|
||||
|
||||
|
||||
__all__ = filter(lambda k: not k.startswith('_'), locals().keys())
|
||||
|
|
|
@ -14,12 +14,9 @@
|
|||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||
# License for more details.
|
||||
|
||||
try:
|
||||
import decimal
|
||||
except:
|
||||
pass
|
||||
import re
|
||||
import sys
|
||||
from decimal import Decimal
|
||||
from datetime import date
|
||||
|
||||
from testutils import unittest, skip_if_no_uuid, skip_before_postgres
|
||||
|
@ -784,6 +781,84 @@ class AdaptTypeTestCase(unittest.TestCase):
|
|||
return oid
|
||||
|
||||
|
||||
def skip_if_no_json_module(f):
|
||||
"""Skip a test if no Python json module is available"""
|
||||
def skip_if_no_json_module_(self):
|
||||
if psycopg2.extras.json is None:
|
||||
return self.skipTest("json module not available")
|
||||
|
||||
return f(self)
|
||||
|
||||
return skip_if_no_json_module_
|
||||
|
||||
def skip_if_no_json_type(f):
|
||||
"""Skip a test if PostgreSQL json type is not available"""
|
||||
def skip_if_no_json_type_(self):
|
||||
curs = self.conn.cursor()
|
||||
curs.execute("select oid from pg_type where typname = 'json'")
|
||||
if not curs.fetchone():
|
||||
return self.skipTest("json not available in test database")
|
||||
|
||||
return f(self)
|
||||
|
||||
return skip_if_no_json_type_
|
||||
|
||||
class JsonTestCase(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.conn = psycopg2.connect(dsn)
|
||||
|
||||
def tearDown(self):
|
||||
self.conn.close()
|
||||
|
||||
def test_module_not_available(self):
|
||||
from psycopg2.extras import json, Json
|
||||
if json is not None:
|
||||
return self.skipTest("json module is available")
|
||||
|
||||
self.assertRaises(ImportError, Json, None)
|
||||
|
||||
@skip_if_no_json_module
|
||||
def test_adapt(self):
|
||||
from psycopg2.extras import json, Json
|
||||
|
||||
objs = [None, "te'xt", 123, 123.45,
|
||||
u'\xe0\u20ac', ['a', 100], {'a': 100} ]
|
||||
|
||||
curs = self.conn.cursor()
|
||||
for obj in enumerate(objs):
|
||||
self.assertEqual(curs.mogrify("%s", (Json(obj),)),
|
||||
psycopg2.extensions.QuotedString(json.dumps(obj)).getquoted())
|
||||
|
||||
@skip_if_no_json_module
|
||||
def test_adapt_extended(self):
|
||||
"""Json passes through kw arguments to dumps"""
|
||||
from psycopg2.extras import json, Json
|
||||
|
||||
class DecimalEncoder(json.JSONEncoder):
|
||||
def default(self, obj):
|
||||
if isinstance(obj, Decimal):
|
||||
return float(obj)
|
||||
return json.JSONEncoder.default(self, obj)
|
||||
|
||||
curs = self.conn.cursor()
|
||||
obj = Decimal('123.45')
|
||||
self.assertEqual(curs.mogrify("%s", (Json(obj, cls=DecimalEncoder),)),
|
||||
b("'123.45'"))
|
||||
|
||||
@skip_if_no_json_module
|
||||
def test_register_on_dict(self):
|
||||
from psycopg2.extras import Json
|
||||
psycopg2.extensions.register_adapter(dict, Json)
|
||||
|
||||
try:
|
||||
curs = self.conn.cursor()
|
||||
obj = {'a': 123}
|
||||
self.assertEqual(curs.mogrify("%s", (obj,)),
|
||||
b("""'{"a": 123}'"""))
|
||||
finally:
|
||||
del psycopg2.extensions.adapters[dict, psycopg2.extensions.ISQLQuote]
|
||||
|
||||
|
||||
def test_suite():
|
||||
return unittest.TestLoader().loadTestsFromName(__name__)
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user