mirror of
https://github.com/psycopg/psycopg2.git
synced 2025-03-13 12:25:48 +03:00
Dropped keywords passthrough in Json adapter
Pass a dumps function instead. Allow customizing by either arg passing or subclassing. The basic Json class now raises ImportError on getquoted() if json is not available, thus allowing using a customized Json subclass even when the json module is not available.
This commit is contained in:
parent
d963b478e2
commit
7386b8327c
|
@ -152,6 +152,7 @@ versions the `simplejson`_ module is be used if available. Note that the last
|
||||||
.. _simplejson: http://pypi.python.org/pypi/simplejson/
|
.. _simplejson: http://pypi.python.org/pypi/simplejson/
|
||||||
|
|
||||||
.. autoclass:: Json
|
.. autoclass:: Json
|
||||||
|
:members: dumps
|
||||||
|
|
||||||
.. autofunction:: register_json
|
.. autofunction:: register_json
|
||||||
|
|
||||||
|
|
78
lib/_json.py
78
lib/_json.py
|
@ -48,51 +48,83 @@ JSON_OID = 114
|
||||||
JSONARRAY_OID = 199
|
JSONARRAY_OID = 199
|
||||||
|
|
||||||
class Json(object):
|
class Json(object):
|
||||||
"""A wrapper to adapt a Python object to :sql:`json` data type.
|
"""
|
||||||
|
An `~psycopg2.extensions.ISQLQuote` wrapper to adapt a Python object to
|
||||||
|
:sql:`json` data type.
|
||||||
|
|
||||||
`!Json` can be used to wrap any object supported by the underlying
|
`!Json` can be used to wrap any object supported by the underlying
|
||||||
`!json` module. Raise `!ImportError` if no module is available.
|
`!json` module. `~psycopg2.extensions.ISQLQuote.getquoted()` will raise
|
||||||
|
`!ImportError` if no module is available.
|
||||||
|
|
||||||
Any keyword argument will be passed to the underlying
|
The basic usage is to wrap `!Json` around the object to be adapted::
|
||||||
:py:func:`json.dumps()` function, allowing extension and customization. ::
|
|
||||||
|
|
||||||
curs.execute("insert into mytable (jsondata) values (%s)",
|
curs.execute("insert into mytable (jsondata) values (%s)",
|
||||||
(Json({'a': 100}),))
|
[Json({'a': 100})])
|
||||||
|
|
||||||
|
If you want to customize the adaptation from Python to PostgreSQL you can
|
||||||
|
either provide a custom *dumps* function::
|
||||||
|
|
||||||
|
curs.execute("insert into mytable (jsondata) values (%s)",
|
||||||
|
[Json({'a': 100}, dumps=simplejson.dumps)])
|
||||||
|
|
||||||
|
or you can subclass `!Json` overriding the `dumps()` method::
|
||||||
|
|
||||||
|
class MyJson(Json):
|
||||||
|
def dumps(self, obj):
|
||||||
|
return simplejson.dumps(obj)
|
||||||
|
|
||||||
|
curs.execute("insert into mytable (jsondata) values (%s)",
|
||||||
|
[MyJson({'a': 100})])
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
You can use `~psycopg2.extensions.register_adapter()` to adapt Python
|
You can use `~psycopg2.extensions.register_adapter()` to adapt any
|
||||||
dictionaries to JSON::
|
Python dictionary to JSON, either using `!Json` or any subclass or
|
||||||
|
factory creating a compatible adapter::
|
||||||
|
|
||||||
psycopg2.extensions.register_adapter(dict,
|
psycopg2.extensions.register_adapter(dict, psycopg2.extras.Json)
|
||||||
psycopg2.extras.Json)
|
|
||||||
|
|
||||||
This setting is global though, so it is not compatible with the use of
|
This setting is global though, so it is not compatible with similar
|
||||||
`register_hstore()`. Any other object supported by the `!json` library
|
adapters such as the one registered by `register_hstore()`. Any other
|
||||||
used by Psycopg can be registered the same way, but this will clobber
|
object supported by JSON can be registered the same way, but this will
|
||||||
the default adaptation rule, so be careful to unwanted side effects.
|
clobber the default adaptation rule, so be careful to unwanted side
|
||||||
|
effects.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def __init__(self, adapted, **kwargs):
|
def __init__(self, adapted, dumps=None):
|
||||||
self.adapted = adapted
|
self.adapted = adapted
|
||||||
self.kwargs = kwargs
|
|
||||||
|
if dumps is not None:
|
||||||
|
self._dumps = dumps
|
||||||
|
elif json is not None:
|
||||||
|
self._dumps = json.dumps
|
||||||
|
else:
|
||||||
|
self._dumps = None
|
||||||
|
|
||||||
def __conform__(self, proto):
|
def __conform__(self, proto):
|
||||||
if proto is ISQLQuote:
|
if proto is ISQLQuote:
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
def dumps(self, obj):
|
||||||
|
"""Serialize *obj* in JSON format.
|
||||||
|
|
||||||
|
The default is to call `!json.dumps()` or the *dumps* function
|
||||||
|
provided in the constructor. You can override this method to create a
|
||||||
|
customized JSON wrapper.
|
||||||
|
"""
|
||||||
|
dumps = self._dumps
|
||||||
|
if dumps is not None:
|
||||||
|
return dumps(obj)
|
||||||
|
else:
|
||||||
|
raise ImportError(
|
||||||
|
"json module not available: "
|
||||||
|
"you should provide a dumps function")
|
||||||
|
|
||||||
def getquoted(self):
|
def getquoted(self):
|
||||||
s = json.dumps(self.adapted, **self.kwargs)
|
s = self.dumps(self.adapted)
|
||||||
return QuotedString(s).getquoted()
|
return 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")
|
|
||||||
|
|
||||||
|
|
||||||
def register_json(conn_or_curs=None, globally=False, loads=None,
|
def register_json(conn_or_curs=None, globally=False, loads=None,
|
||||||
oid=None, array_oid=None):
|
oid=None, array_oid=None):
|
||||||
"""Create and register typecasters converting :sql:`json` type to Python objects.
|
"""Create and register typecasters converting :sql:`json` type to Python objects.
|
||||||
|
|
|
@ -781,6 +781,16 @@ class AdaptTypeTestCase(unittest.TestCase):
|
||||||
return oid
|
return oid
|
||||||
|
|
||||||
|
|
||||||
|
def skip_if_json_module(f):
|
||||||
|
"""Skip a test if no Python json module is available"""
|
||||||
|
def skip_if_json_module_(self):
|
||||||
|
if psycopg2.extras.json is not None:
|
||||||
|
return self.skipTest("json module is available")
|
||||||
|
|
||||||
|
return f(self)
|
||||||
|
|
||||||
|
return skip_if_json_module_
|
||||||
|
|
||||||
def skip_if_no_json_module(f):
|
def skip_if_no_json_module(f):
|
||||||
"""Skip a test if no Python json module is available"""
|
"""Skip a test if no Python json module is available"""
|
||||||
def skip_if_no_json_module_(self):
|
def skip_if_no_json_module_(self):
|
||||||
|
@ -810,12 +820,20 @@ class JsonTestCase(unittest.TestCase):
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
self.conn.close()
|
self.conn.close()
|
||||||
|
|
||||||
|
@skip_if_json_module
|
||||||
def test_module_not_available(self):
|
def test_module_not_available(self):
|
||||||
from psycopg2.extras import json, Json
|
from psycopg2.extras import Json
|
||||||
if json is not None:
|
self.assertRaises(ImportError, Json(None).getquoted)
|
||||||
return self.skipTest("json module is available")
|
|
||||||
|
|
||||||
self.assertRaises(ImportError, Json, None)
|
@skip_if_json_module
|
||||||
|
def test_customizable_with_module_not_available(self):
|
||||||
|
from psycopg2.extras import Json
|
||||||
|
class MyJson(Json):
|
||||||
|
def dumps(self, obj):
|
||||||
|
assert obj is None
|
||||||
|
return "hi"
|
||||||
|
|
||||||
|
self.assertEqual(MyJson(None).getquoted(), "'hi'")
|
||||||
|
|
||||||
@skip_if_no_json_module
|
@skip_if_no_json_module
|
||||||
def test_adapt(self):
|
def test_adapt(self):
|
||||||
|
@ -830,8 +848,7 @@ class JsonTestCase(unittest.TestCase):
|
||||||
psycopg2.extensions.QuotedString(json.dumps(obj)).getquoted())
|
psycopg2.extensions.QuotedString(json.dumps(obj)).getquoted())
|
||||||
|
|
||||||
@skip_if_no_json_module
|
@skip_if_no_json_module
|
||||||
def test_adapt_extended(self):
|
def test_adapt_dumps(self):
|
||||||
"""Json passes through kw arguments to dumps"""
|
|
||||||
from psycopg2.extras import json, Json
|
from psycopg2.extras import json, Json
|
||||||
|
|
||||||
class DecimalEncoder(json.JSONEncoder):
|
class DecimalEncoder(json.JSONEncoder):
|
||||||
|
@ -842,7 +859,27 @@ class JsonTestCase(unittest.TestCase):
|
||||||
|
|
||||||
curs = self.conn.cursor()
|
curs = self.conn.cursor()
|
||||||
obj = Decimal('123.45')
|
obj = Decimal('123.45')
|
||||||
self.assertEqual(curs.mogrify("%s", (Json(obj, cls=DecimalEncoder),)),
|
dumps = lambda obj: json.dumps(obj, cls=DecimalEncoder)
|
||||||
|
self.assertEqual(curs.mogrify("%s", (Json(obj, dumps=dumps),)),
|
||||||
|
b("'123.45'"))
|
||||||
|
|
||||||
|
@skip_if_no_json_module
|
||||||
|
def test_adapt_subclass(self):
|
||||||
|
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)
|
||||||
|
|
||||||
|
class MyJson(Json):
|
||||||
|
def dumps(self, obj):
|
||||||
|
return json.dumps(obj, cls=DecimalEncoder)
|
||||||
|
|
||||||
|
curs = self.conn.cursor()
|
||||||
|
obj = Decimal('123.45')
|
||||||
|
self.assertEqual(curs.mogrify("%s", (MyJson(obj),)),
|
||||||
b("'123.45'"))
|
b("'123.45'"))
|
||||||
|
|
||||||
@skip_if_no_json_module
|
@skip_if_no_json_module
|
||||||
|
|
Loading…
Reference in New Issue
Block a user