Skip to content

Commit

Permalink
fix minijson
Browse files Browse the repository at this point in the history
  • Loading branch information
piotrmaslanka committed Jun 29, 2021
1 parent 0b93926 commit 4715553
Show file tree
Hide file tree
Showing 7 changed files with 73 additions and 17 deletions.
1 change: 1 addition & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@
omit=
setup.py
docs/*
tests/*
plugins = Cython.Coverage
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@ Changelog is kept at [GitHub](https://github.com/Dronehub/minijson/releases),
here's only the changelog for the version in development

# v2.4

* added argument default
* fixing issue with serializing classes that subclass dict, list and tuple
6 changes: 3 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
FROM pypy:3.5
FROM python:3.5
RUN apt-get update && \
apt-get install -y patchelf
RUN pypy3 -m pip install Cython pytest coverage pytest-cov auditwheel doctor-wheel twine
RUN python -m pip install Cython pytest coverage pytest-cov auditwheel doctor-wheel twine

ENV DEBUG=1

WORKDIR /tmp/compile
ADD . /tmp/compile/

RUN pypy3 setup.py install && \
RUN python setup.py install && \
chmod ugo+x /tmp/compile/tests/test.sh

CMD ["/tmp/compile/tests/test.sh"]
3 changes: 3 additions & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ whose all keys are strings.
You should avoid objects with keys different than strings, since they will always use a
4-byte length field. This is to be improved in a future release.

.. warning:: Take care for your data to be without cycles. Feeding the encoder cycles
will probably dump your interpreter's core.

Indices and tables
==================

Expand Down
44 changes: 31 additions & 13 deletions minijson.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ cdef inline tuple parse_sdict(bytes data, int elem_count, int starting_position)
return offset, dct


cdef inline bint can_be_encoded_as_a_dict(dict dct):
cdef inline bint can_be_encoded_as_a_dict(dct):
for key in dct.keys():
if not isinstance(key, str):
return False
Expand Down Expand Up @@ -298,12 +298,18 @@ cpdef object loads(object data):
return parse(data, 0)[1]


cpdef int dump(object data, cio: io.BytesIO) except -1:
cdef inline bint is_jsonable(y):
return y is None or isinstance(y, (int, float, str, dict, list, tuple))


cpdef int dump(object data, cio: io.BytesIO, default: tp.Optional[tp.Callable] = None) except -1:
"""
Write an object to a stream
:param data: object to write
:param cio: stream to write to
:param default: a function that should be used to convert non-JSONable objects to JSONable ones.
Default, None, will raise an EncodingError upon encountering such a value
:return: amount of bytes written
:raises EncodingError: invalid data
"""
Expand Down Expand Up @@ -404,7 +410,7 @@ cpdef int dump(object data, cio: io.BytesIO) except -1:
cio.write(STRUCT_L.pack(length))
length = 5
for elem in data:
length += dump(elem, cio)
length += dump(elem, cio, default)
return length
elif isinstance(data, dict):
length = len(data)
Expand All @@ -426,7 +432,7 @@ cpdef int dump(object data, cio: io.BytesIO) except -1:
for field_name, elem in data.items():
cio.write(bytearray([len(field_name)]))
cio.write(field_name.encode('utf-8'))
length += dump(elem, cio)
length += dump(elem, cio, default)
return length
else:
if length <= 0xF:
Expand All @@ -445,35 +451,47 @@ cpdef int dump(object data, cio: io.BytesIO) except -1:
offset = 5

for key, value in data.items():
offset += dump(key, cio)
offset += dump(value, cio)
offset += dump(key, cio, default)
offset += dump(value, cio, default)
return offset
else:
elif default is None:
raise EncodingError('Unknown value type %s' % (data, ))
else:
v = default(data)
if not is_jsonable(v):
raise EncodingError('Default returned type %s, which is not jsonable' % (type(v), ))
return dump(v, cio, default)


cpdef bytes dumps(object data):
cpdef bytes dumps(object data, default: tp.Optional[tp.Callable] = None):
"""
Serialize given data to a MiniJSON representation
:param data: data to serialize
:param default: a function that should be used to convert non-JSONable objects to JSONable ones.
Default, None, will raise an EncodingError upon encountering such a value
:return: return MiniJSON representation
:raises DecodingError: object not serializable
:raises EncodingError: object not serializable
"""
cio = io.BytesIO()
dump(data, cio)
dump(data, cio, default)
return cio.getvalue()


cpdef bytes dumps_object(object data):
cpdef bytes dumps_object(object data, default: tp.Optional[tp.Callable] = None):
"""
Dump an object's __dict__
Dump an object's :code:`__dict__`.
Note that subobject's :code:`__dict__` will not be copied. Use default for that.
:param data: object to dump
:param default: a function that should be used to convert non-JSONable objects to JSONable ones.
Default, None, will raise an EncodingError upon encountering such a value
:return: resulting bytes
:raises EncodingError: encoding error
"""
return dumps(data.__dict__)
return dumps(data.__dict__, default)


cpdef object loads_object(data, object obj_class):
"""
Expand Down
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# coding: utf-8
[metadata]
version = 2.4a2
version = 2.4a4
name = minijson
long_description = file: README.md
long_description_content_type = text/markdown; charset=UTF-8
Expand Down
31 changes: 31 additions & 0 deletions tests/test_minijson.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,37 @@ def assertLoadingIsDecodingError(self, b: bytes):
def assertSameAfterDumpsAndLoads(self, c):
self.assertEqual(loads(dumps(c)), c)

def test_default(self):
def transform(c):
return c.real, c.imag

dumps(2 + 3j, transform)
dumps({'test': 2 + 3j}, transform)

def test_subclasses_of_dicts(self):
class Subclass(dict):
pass

a = Subclass({1: 2, 3: 4})
b = dumps(a)
self.assertEquals(loads(b), {1: 2, 3: 4})

def test_subclasses_of_lists(self):
class Subclass(list):
pass

a = Subclass([1, 2, 3])
b = dumps(a)
self.assertEquals(loads(b), [1, 2, 3])

def test_subclasses_of_tuples(self):
class Subclass(tuple):
pass

a = Subclass((1, 2, 3))
b = dumps(a)
self.assertEquals(loads(b), [1, 2, 3])

def test_malformed(self):
self.assertRaises(EncodingError, lambda: dumps(2 + 3j))
self.assertLoadingIsDecodingError(b'\x00\x02a')
Expand Down

0 comments on commit 4715553

Please sign in to comment.