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/
|
||||
|
||||
.. autoclass:: Json
|
||||
:members: dumps
|
||||
|
||||
.. autofunction:: register_json
|
||||
|
||||
|
|
78
lib/_json.py
78
lib/_json.py
|
@ -48,51 +48,83 @@ JSON_OID = 114
|
|||
JSONARRAY_OID = 199
|
||||
|
||||
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` 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
|
||||
:py:func:`json.dumps()` function, allowing extension and customization. ::
|
||||
The basic usage is to wrap `!Json` around the object to be adapted::
|
||||
|
||||
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::
|
||||
|
||||
You can use `~psycopg2.extensions.register_adapter()` to adapt Python
|
||||
dictionaries to JSON::
|
||||
You can use `~psycopg2.extensions.register_adapter()` to adapt any
|
||||
Python dictionary to JSON, either using `!Json` or any subclass or
|
||||
factory creating a compatible adapter::
|
||||
|
||||
psycopg2.extensions.register_adapter(dict,
|
||||
psycopg2.extras.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.
|
||||
This setting is global though, so it is not compatible with similar
|
||||
adapters such as the one registered by `register_hstore()`. Any other
|
||||
object supported by JSON 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):
|
||||
def __init__(self, adapted, dumps=None):
|
||||
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):
|
||||
if proto is ISQLQuote:
|
||||
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):
|
||||
s = json.dumps(self.adapted, **self.kwargs)
|
||||
s = self.dumps(self.adapted)
|
||||
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,
|
||||
oid=None, array_oid=None):
|
||||
"""Create and register typecasters converting :sql:`json` type to Python objects.
|
||||
|
|
|
@ -781,6 +781,16 @@ class AdaptTypeTestCase(unittest.TestCase):
|
|||
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):
|
||||
"""Skip a test if no Python json module is available"""
|
||||
def skip_if_no_json_module_(self):
|
||||
|
@ -810,12 +820,20 @@ class JsonTestCase(unittest.TestCase):
|
|||
def tearDown(self):
|
||||
self.conn.close()
|
||||
|
||||
@skip_if_json_module
|
||||
def test_module_not_available(self):
|
||||
from psycopg2.extras import json, Json
|
||||
if json is not None:
|
||||
return self.skipTest("json module is available")
|
||||
from psycopg2.extras import Json
|
||||
self.assertRaises(ImportError, Json(None).getquoted)
|
||||
|
||||
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
|
||||
def test_adapt(self):
|
||||
|
@ -830,8 +848,7 @@ class JsonTestCase(unittest.TestCase):
|
|||
psycopg2.extensions.QuotedString(json.dumps(obj)).getquoted())
|
||||
|
||||
@skip_if_no_json_module
|
||||
def test_adapt_extended(self):
|
||||
"""Json passes through kw arguments to dumps"""
|
||||
def test_adapt_dumps(self):
|
||||
from psycopg2.extras import json, Json
|
||||
|
||||
class DecimalEncoder(json.JSONEncoder):
|
||||
|
@ -842,7 +859,27 @@ class JsonTestCase(unittest.TestCase):
|
|||
|
||||
curs = self.conn.cursor()
|
||||
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'"))
|
||||
|
||||
@skip_if_no_json_module
|
||||
|
|
Loading…
Reference in New Issue
Block a user