blob: 084d049c037b9d27f824e57d9eb3f9a06ea6b96d [file] [log] [blame]
global DEFAULT_MAX_NESTING_LEVEL, __version__, __all__
DEFAULT_MAX_NESTING_LEVEL = 32
'''
Maximum nesting level of data to decode if no ``maxdepth`` argument is specified.
'''
__version__ = PyUnicode_FromKindAndData(PyUnicode_1BYTE_KIND, VERSION, VERSION_LENGTH)
'''
Current library version.
'''
def decode(object data, object maxdepth=None, object some=False):
'''
Decodes JSON5 serialized data from an ``str`` object.
.. code:: python
decode('["Hello", "world!"]') == ['Hello', 'world!']
Parameters
----------
data : unicode
JSON5 serialized data
maxdepth : Optional[int]
Maximum nesting level before are the parsing is aborted.
* If ``None`` is supplied, then the value of the global variable \
``DEFAULT_MAX_NESTING_LEVEL`` is used instead.
* If the value is ``0``, then only literals are accepted, e.g. ``false``, \
``47.11``, or ``"string"``.
* If the value is negative, then the any nesting level is allowed until \
Python's recursion limit is hit.
some : bool
Allow trailing junk.
Raises
------
Json5DecoderException
An exception occured while decoding.
TypeError
An argument had a wrong type.
Returns
-------
object
Deserialized data.
'''
if maxdepth is None:
maxdepth = DEFAULT_MAX_NESTING_LEVEL
if isinstance(data, unicode):
return _decode_unicode(data, maxdepth, bool(some))
else:
raise TypeError(f'type(data) == {type(data)!r} not supported')
def decode_latin1(object data, object maxdepth=None, object some=False):
'''
Decodes JSON5 serialized data from a ``bytes`` object.
.. code:: python
decode_buffer(b'["Hello", "world!"]') == ['Hello', 'world!']
Parameters
----------
data : bytes
JSON5 serialized data, encoded as Latin-1 or ASCII.
maxdepth : Optional[int]
see `decode(...) <pyjson5.decode_>`_
some : bool
see `decode(...) <pyjson5.decode_>`_
Raises
------
Json5DecoderException
An exception occured while decoding.
TypeError
An argument had a wrong type.
Returns
-------
object
see `decode(...) <pyjson5.decode_>`_
'''
return decode_buffer(data, maxdepth, bool(some), 1)
def decode_buffer(object obj, object maxdepth=None, object some=False,
object wordlength=None):
'''
Decodes JSON5 serialized data from an object that supports the buffer
protocol, e.g. bytearray.
.. code:: python
obj = memoryview(b'["Hello", "world!"]')
decode_buffer(obj) == ['Hello', 'world!']
Parameters
----------
data : object
JSON5 serialized data.
The argument must support Python's buffer protocol, i.e.
``memoryview(...)`` must work. The buffer must be contigious.
maxdepth : Optional[int]
see `decode(...) <pyjson5.decode_>`_
some : bool
see `decode(...) <pyjson5.decode_>`_
wordlength : Optional[int]
Must be 1, 2, 4 to denote UCS1, USC2 or USC4 data.
Surrogates are not supported. Decode the data to an ``str`` if need be.
If ``None`` is supplied, then the buffer's ``itemsize`` is used.
Raises
------
Json5DecoderException
An exception occured while decoding.
TypeError
An argument had a wrong type.
ValueError
The value of ``wordlength`` was invalid.
Returns
-------
object
see `decode(...) <pyjson5.decode_>`_
'''
cdef Py_buffer view
if maxdepth is None:
maxdepth = DEFAULT_MAX_NESTING_LEVEL
PyObject_GetBuffer(obj, &view, PyBUF_CONTIG_RO)
try:
if wordlength is None:
wordlength = view.itemsize
return _decode_buffer(view, wordlength, maxdepth, bool(some))
finally:
PyBuffer_Release(&view)
def decode_callback(object cb, object maxdepth=None, object some=False,
object args=None):
'''
Decodes JSON5 serialized data by invoking a callback.
.. code:: python
cb = iter('["Hello","world!"]').__next__
decode_callback(cb) == ['Hello', 'world!']
Parameters
----------
cb : Callable[Any, Union[str|bytes|bytearray|int|None]]
A function to get values from.
The functions is called like ``cb(*args)``, and it returns:
* **str, bytes, bytearray:** \
``len(...) == 0`` denotes exhausted input. \
``len(...) == 1`` is the next character.
* **int:** \
``< 0`` denotes exhausted input. \
``>= 0`` is the ordinal value of the next character.
* **None:** \
input exhausted
maxdepth : Optional[int]
see `decode(...) <pyjson5.decode_>`_
some : bool
see `decode(...) <pyjson5.decode_>`_
args : Optional[Iterable[Any]]
Arguments to call ``cb`` with.
Raises
------
Json5DecoderException
An exception occured while decoding.
TypeError
An argument had a wrong type.
Returns
-------
object
see ``decode(...)``
'''
if not callable(cb):
raise TypeError(f'type(cb)=={type(cb)!r} is not callable')
if maxdepth is None:
maxdepth = DEFAULT_MAX_NESTING_LEVEL
if args:
args = tuple(args)
else:
args = ()
return _decode_callback(cb, args, maxdepth, bool(some))
def decode_io(object fp, object maxdepth=None, object some=True):
'''
Decodes JSON5 serialized data from a file-like object.
.. code:: python
fp = io.StringIO("""
['Hello', /* TODO look into specs whom to greet */]
'Wolrd' // FIXME: look for typos
""")
decode_io(fp) == ['Hello']
decode_io(fp) == 'Wolrd'
fp.seek(0)
decode_io(fp, some=False)
# raises Json5ExtraData('Extra data U+0027 near 56', ['Hello'], "'")
Parameters
----------
fp : IOBase
A file-like object to parse from.
maxdepth : Optional[int] = None
see `decode(...) <pyjson5.decode_>`_
some : bool
see `decode(...) <pyjson5.decode_>`_
Raises
------
Json5DecoderException
An exception occured while decoding.
TypeError
An argument had a wrong type.
Returns
-------
object
see ``decode(...)``
'''
if not isinstance(fp, IOBase):
raise TypeError(f'type(fp)=={type(fp)!r} is not IOBase compatible')
elif not fp.readable():
raise TypeError(f'fp is not readable')
elif fp.closed:
raise TypeError(f'fp is closed')
if maxdepth is None:
maxdepth = DEFAULT_MAX_NESTING_LEVEL
return _decode_callback(fp.read, (1,), maxdepth, bool(some))
def encode(object data, *, options=None, **options_kw):
'''
Serializes a Python object to a JSON5 compatible unicode string.
.. code:: python
encode(['Hello', 'world!']) == '["Hello","world!"]'
Parameters
----------
data : object
Python object to serialize.
options : Optional[Options]
Extra options for the encoder.
If ``options`` **and** ``options_kw`` are specified, then ``options.update(**options_kw)`` is used.
options_kw
See Option's arguments.
Raises
------
Json5EncoderException
An exception occured while encoding.
TypeError
An argument had a wrong type.
Returns
-------
str
Unless ``float('inf')`` or ``float('nan')`` is encountered, the result
will be valid JSON data (as of RFC8259).
The result is always ASCII. All characters outside of the ASCII range
are escaped.
The result safe to use in an HTML template, e.g.
``<a onclick='alert({{ encode(url) }})'>show message</a>``.
Apostrophes ``"'"`` are encoded as ``"\\u0027"``, less-than,
greater-than, and ampersand likewise.
'''
cdef void *temp
cdef object result
cdef Py_ssize_t start = (
<Py_ssize_t> <void*> &(<AsciiObject*> NULL).data[0]
)
cdef Py_ssize_t length
cdef object opts = _to_options(options, options_kw)
cdef WriterReallocatable writer = WriterReallocatable(
Writer(
_WriterReallocatable_reserve,
_WriterReallocatable_append_c,
_WriterReallocatable_append_s,
<PyObject*> opts,
),
start, 0, NULL,
)
try:
_encode(writer.base, data)
length = writer.position - start
if length <= 0:
# impossible
return u''
temp = ObjectRealloc(writer.obj, writer.position + 1)
if temp is not NULL:
writer.obj = temp
(<char*> writer.obj)[writer.position] = 0
result = ObjectInit(<PyObject*> writer.obj, unicode)
writer.obj = NULL
(<PyASCIIObject*> result).length = length
(<PyASCIIObject*> result).hash = -1
(<PyASCIIObject*> result).wstr = NULL
(<PyASCIIObject*> result).state.interned = SSTATE_NOT_INTERNED
(<PyASCIIObject*> result).state.kind = PyUnicode_1BYTE_KIND
(<PyASCIIObject*> result).state.compact = True
(<PyASCIIObject*> result).state.ready = True
(<PyASCIIObject*> result).state.ascii = True
return result
finally:
if writer.obj is not NULL:
ObjectFree(writer.obj)
def encode_bytes(object data, *, options=None, **options_kw):
'''
Serializes a Python object to a JSON5 compatible bytes string.
.. code:: python
encode_bytes(['Hello', 'world!']) == b'["Hello","world!"]'
Parameters
----------
data : object
see `encode(...) <pyjson5.encode_>`_
options : Optional[Options]
see `encode(...) <pyjson5.encode_>`_
options_kw
see `encode(...) <pyjson5.encode_>`_
Raises
------
Json5EncoderException
An exception occured while encoding.
TypeError
An argument had a wrong type.
Returns
-------
bytes
see `encode(...) <pyjson5.encode_>`_
'''
cdef void *temp
cdef object result
cdef Py_ssize_t start = (
<Py_ssize_t> <void*> &(<PyBytesObject*> NULL).ob_sval[0]
)
cdef Py_ssize_t length
cdef object opts = _to_options(options, options_kw)
cdef WriterReallocatable writer = WriterReallocatable(
Writer(
_WriterReallocatable_reserve,
_WriterReallocatable_append_c,
_WriterReallocatable_append_s,
<PyObject*> opts,
),
start, 0, NULL,
)
try:
_encode(writer.base, data)
length = writer.position - start
if length <= 0:
# impossible
return b''
temp = ObjectRealloc(writer.obj, writer.position + 1)
if temp is not NULL:
writer.obj = temp
(<char*> writer.obj)[writer.position] = 0
result = <object> <PyObject*> ObjectInitVar(
(<PyVarObject*> writer.obj), bytes, length,
)
writer.obj = NULL
(<PyBytesObject*> result).ob_shash = -1
return result
finally:
if writer.obj is not NULL:
ObjectFree(writer.obj)
def encode_callback(object data, object cb, object supply_bytes=False, *,
options=None, **options_kw):
'''
Serializes a Python object into a callback function.
The callback function ``cb`` gets called with single characters and strings
until the input ``data`` is fully serialized.
.. code:: python
encode_callback(['Hello', 'world!'], print)
#prints:
# [
# "
# Hello
# "
# ,
# "
# world!
# "
" ]
Parameters
----------
data : object
see `encode(...) <pyjson5.encode_>`_
cb : Callable[[Union[bytes|str]], None]
A callback function.
Depending on the truthyness of ``supply_bytes`` either ``bytes`` or
``str`` is supplied.
supply_bytes : bool
Call ``cb(...)`` with a ``bytes`` argument if true,
otherwise ``str``.
options : Optional[Options]
see `encode(...) <pyjson5.encode_>`_
options_kw
see `encode(...) <pyjson5.encode_>`_
Raises
------
Json5EncoderException
An exception occured while encoding.
TypeError
An argument had a wrong type.
Returns
-------
Callable[[Union[bytes|str]], None]
The supplied argument ``cb``.
'''
cdef boolean (*encoder)(object obj, object cb, object options) except False
cdef Options opts = _to_options(options, options_kw)
if supply_bytes:
encoder = _encode_callback_bytes
else:
encoder = _encode_callback_str
encoder(data, cb, options=opts)
return cb
def encode_io(object data, object fp, object supply_bytes=True, *,
options=None, **options_kw):
'''
Serializes a Python object into a file-object.
The return value of ``fp.write(...)`` is not checked.
If ``fp`` is unbuffered, then the result will be garbage!
Parameters
----------
data : object
see `encode(...) <pyjson5.encode_>`_
fp : IOBase
A file-like object to serialize into.
supply_bytes : bool
Call ``fp.write(...)`` with a ``bytes`` argument if true,
otherwise ``str``.
options : Optional[Options]
see `encode(...) <pyjson5.encode_>`_
options_kw
see `encode(...) <pyjson5.encode_>`_
Raises
------
Json5EncoderException
An exception occured while encoding.
TypeError
An argument had a wrong type.
Returns
-------
IOBase
The supplied argument ``fp``.
'''
cdef boolean (*encoder)(object obj, object cb, object options) except False
cdef object opts = _to_options(options, options_kw)
if not isinstance(fp, IOBase):
raise TypeError(f'type(fp)=={type(fp)!r} is not IOBase compatible')
elif not fp.writable():
raise TypeError(f'fp is not writable')
elif fp.closed:
raise TypeError(f'fp is closed')
if supply_bytes:
encoder = _encode_callback_bytes
else:
encoder = _encode_callback_str
encoder(data, fp.write, options=opts)
return fp
def encode_noop(object data, *, options=None, **options_kw):
'''
Test if the input is serializable.
Most likely you want to serialize ``data`` directly, and catch exceptions
instead of using this function!
.. code:: python
encode_noop({47: 11}) == True
encode_noop({47: object()}) == False
Parameters
----------
data : object
see `encode(...) <pyjson5.encode_>`_
options : Optional[Options]
see `encode(...) <pyjson5.encode_>`_
options_kw
see `encode(...) <pyjson5.encode_>`_
Returns
-------
bool
``True`` iff ``data`` is serializable.
'''
cdef object opts = _to_options(options, options_kw)
cdef Writer writer = Writer(
_WriterNoop_reserve,
_WriterNoop_append_c,
_WriterNoop_append_s,
<PyObject*> opts,
)
try:
_encode(writer, data)
except Exception:
return False
return True
__all__ = (
# DECODE
'decode', 'decode_latin1', 'decode_buffer', 'decode_callback', 'decode_io',
# ENCODE
'encode', 'encode_bytes', 'encode_callback', 'encode_io', 'encode_noop', 'Options',
# LEGACY
'loads', 'load', 'dumps', 'dump',
# EXCEPTIONS
'Json5Exception',
'Json5EncoderException', 'Json5UnstringifiableType',
'Json5DecoderException', 'Json5NestingTooDeep', 'Json5EOF', 'Json5IllegalCharacter', 'Json5ExtraData', 'Json5IllegalType',
)
__doc__ = PyUnicode_FromKindAndData(PyUnicode_1BYTE_KIND, LONGDESCRIPTION, LONGDESCRIPTION_LENGTH)