Permalink
Browse files

Initial work.

  • Loading branch information...
0 parents commit 9ea673f9f7daefb0a0727845471ad77e04abcfcc Alex Gaynor committed Dec 23, 2012
Showing with 329 additions and 0 deletions.
  1. +1 −0 .gitignore
  2. 0 bench/__init__.py
  3. +49 −0 bench/simple.py
  4. +12 −0 bench/utils.py
  5. +1 −0 json_writer/__init__.py
  6. +193 −0 json_writer/writer.py
  7. 0 tests/__init__.py
  8. +73 −0 tests/test_writer.py
@@ -0,0 +1 @@
+*.py[co]
No changes.
@@ -0,0 +1,49 @@
+import sys
+
+try:
+ import simplejson as json
+except ImportError:
+ import json
+
+try:
+ from json_writer import JSONWriter
+except ImportError:
+ pass
+
+from . import utils
+
+
+class Person(object):
+ def __init__(self, name):
+ self.name = name
+
+
+def dump_json(data):
+ return json.dumps([
+ {"name": p.name}
+ for p in data
+ ])
+
+
+def dump_json_writer(data):
+ w = JSONWriter()
+ with w.array():
+ for p in data:
+ with w.object():
+ w.write_key("name")
+ w.write(p.name)
+
+
+def main(argv):
+ data = [
+ Person(i)
+ for i in xrange(250000)
+ ]
+ if argv[1] == "json":
+ func = dump_json
+ elif argv[1] == "json_writer":
+ func = dump_json_writer
+ utils.run_benchmark(func, data)
+
+if __name__ == "__main__":
+ main(sys.argv)
@@ -0,0 +1,12 @@
+import time
+
+
+def run_benchmark(func, data):
+ times = []
+ for i in xrange(5):
+ t = time.time()
+ func(data)
+ times.append(time.time() - t)
+ print "Best: %f" % min(times)
+ print "Worst: %f" % max(times)
+ print "Average: %f" % (sum(times) / len(times))
@@ -0,0 +1 @@
+from json_writer.writer import JSONWriter, BytesIOWriter, PyPyStringBuilderWriter
@@ -0,0 +1,193 @@
+try:
+ from __pypy__.builders import StringBuilder
+except ImportError:
+ StringBuilder = None
+from abc import ABCMeta, abstractmethod
+from io import BytesIO
+import math
+
+
+class _ArrayElement(object):
+ def __init__(self, writer):
+ self.writer = writer
+ self.ctx = -1
+
+ def __enter__(self):
+ self.writer._starting_element()
+ self.ctx = self.writer.ctx
+ self.writer.ctx = self.writer.ARRAY_START_CTX
+ self.writer._write(b"[")
+
+ def __exit__(self, *exc_info):
+ self.writer._write(b"]")
+ self.writer.ctx = self.ctx
+
+
+class _ObjectElement(object):
+ def __init__(self, writer):
+ self.writer = writer
+ self.ctx = -1
+
+ def __enter__(self):
+ self.writer._starting_element()
+ self.ctx = self.writer.ctx
+ self.writer.ctx = self.writer.OBJECT_START_KEY_CTX
+ self.writer._write(b"{")
+
+ def __exit__(self, *exc_info):
+ self.writer._write(b"}")
+ self.writer.ctx = self.ctx
+
+
+class BaseJSONWriter(object):
+ __metaclass__ = ABCMeta
+
+ START_CTX = 0
+ ARRAY_START_CTX = 1
+ ARRAY_CTX = 2
+ OBJECT_START_KEY_CTX = 3
+ OBJECT_KEY_CTX = 4
+
+ def __init__(self):
+ if not self.is_available():
+ raise NotImplementedError("%s is not available on this platform" %
+ type(self))
+ self.ctx = self.START_CTX
+
+ @abstractmethod
+ def _write(self, s):
+ raise NotImplementedError
+
+ @abstractmethod
+ def build(self):
+ raise NotImplementedError
+
+ @classmethod
+ def is_available(cls):
+ raise NotImplementedError
+
+ def _starting_element(self):
+ if self.ctx == self.ARRAY_CTX:
+ self._write(b",")
+ elif self.ctx == self.ARRAY_START_CTX:
+ self.ctx = self.ARRAY_CTX
+
+ def array(self):
+ return _ArrayElement(self)
+
+ def object(self):
+ return _ObjectElement(self)
+
+ def _write_bytes(self, s):
+ start = 0
+ i = 0
+ while 0 <= i < len(s):
+ c = ord(s[i])
+ if not (c >= ord(" ") and c <= ord("~") and c != ord("\\") and c != ord('"')):
+ self._write(s[start:i])
+ start = i + 1
+ self._write(b"\\")
+ if c == ord("\\") or c == ord('"'):
+ self._write(c)
+ elif c == ord("\b"):
+ self._write(b"b")
+ elif c == ord("\f"):
+ self._write(b"f")
+ elif c == ord("\n"):
+ self._write(b"n")
+ elif c == ord("\r"):
+ self._write(b"r")
+ elif c == ord("\t"):
+ self._write(b"t")
+ else:
+ if c >= 0x10000:
+ v = c - 0x10000
+ c = 0xD800 | ((v >> 10) & 0x3FF)
+ self._write(b"u")
+ self._write("0123456789abcdef"[(c >> 12) & 0xF])
+ self._write("0123456789abcdef"[(c >> 8) & 0xF])
+ self._write("0123456789abcdef"[(c >> 4) & 0xF])
+ self._write("0123456789abcdef"[c & 0xF])
+ c = 0xDC00 | (v & 0x3FF)
+ self._write("\\")
+ self._write(b"u")
+ self._write("0123456789abcdef"[(c >> 12) & 0xF])
+ self._write("0123456789abcdef"[(c >> 8) & 0xF])
+ self._write("0123456789abcdef"[(c >> 4) & 0xF])
+ self._write("0123456789abcdef"[c & 0xF])
+ i += 1
+ self._write(s[start:])
+
+ def write(self, v):
+ if self.ctx == self.START_CTX:
+ raise TypeError("Top level JSON structure must be object or array")
+ self._starting_element()
+ if v is None:
+ self._write(b"null")
+ elif isinstance(v, int):
+ self._write(bytes(str(v)))
+ elif isinstance(v, float):
+ if math.isnan(v) or math.isinf(v):
+ raise ValueError("Out of range float values are not JSON "
+ "compliant: %r" % v)
+ self._write(bytes(repr(v)))
+ elif isinstance(v, bytes):
+ self._write(b'"')
+ self._write_bytes(v)
+ self._write(b'"')
+ else:
+ raise TypeError("Don't recognize object of type %s" % type(v))
+
+ def write_key(self, s):
+ if (self.ctx != self.OBJECT_KEY_CTX and
+ self.ctx != self.OBJECT_START_KEY_CTX):
+ raise TypeError("Not at object key position")
+ if self.ctx == self.OBJECT_KEY_CTX:
+ self._write(b",")
+ self._write(b'"')
+ if isinstance(s, bytes):
+ self._write_bytes(s)
+ elif isinstance(s, unicode):
+ self._write_unicode(s)
+ else:
+ raise TypeError("key must be either bytes or unicode")
+ self._write(b'"')
+ self._write(b":")
+ self.ctx = self.OBJECT_KEY_CTX
+
+
+class PyPyStringBuilderWriter(BaseJSONWriter):
+ def __init__(self):
+ super(PyPyStringBuilderWriter, self).__init__()
+ self._data = StringBuilder()
+
+ @classmethod
+ def is_available(cls):
+ return StringBuilder is not None
+
+ def _write(self, s):
+ self._data.append(s)
+
+ def build(self):
+ return self._data.build()
+
+
+class BytesIOWriter(BaseJSONWriter):
+ def __init__(self):
+ super(BytesIOWriter, self).__init__()
+ self._data = BytesIO()
+
+ @classmethod
+ def is_available(cls):
+ return True
+
+ def _write(self, s):
+ self._data.write(s)
+
+ def build(self):
+ return self._data.getvalue()
+
+if StringBuilder is not None:
+ JSONWriter = PyPyStringBuilderWriter
+else:
+ JSONWriter = BytesIOWriter
No changes.
@@ -0,0 +1,73 @@
+import json_writer
+
+import pytest
+
+
+@pytest.fixture(params=[json_writer.BytesIOWriter, json_writer.PyPyStringBuilderWriter])
+def writer(request):
+ if not request.param.is_available():
+ pytest.skip()
+ return request.param()
+
+
+class TestWriter(object):
+ def test_empty_arary(self, writer):
+ with writer.array():
+ pass
+ assert writer.build() == b"[]"
+
+ def test_array(self, writer):
+ with writer.array():
+ with writer.array():
+ pass
+ with writer.array():
+ pass
+ assert writer.build() == b"[[],[]]"
+
+ def test_number(self, writer):
+ with writer.array():
+ writer.write(2)
+ writer.write(5.2)
+ assert writer.build() == b"[2,5.2]"
+
+ def test_none(self, writer):
+ with writer.array():
+ writer.write(None)
+ assert writer.build() == b"[null]"
+
+ def test_top_level_non_object_array(self, writer):
+ with pytest.raises(TypeError):
+ writer.write(None)
+
+ def test_empty_object(self, writer):
+ with writer.object():
+ pass
+ assert writer.build() == b"{}"
+
+ def test_object(self, writer):
+ with writer.object():
+ writer.write_key("t")
+ writer.write(None)
+ assert writer.build() == b'{"t":null}'
+
+ def test_multi_key_object(self, writer):
+ with writer.object():
+ writer.write_key("a")
+ writer.write(123)
+
+ writer.write_key("b")
+ with writer.array():
+ pass
+
+ writer.write_key("c")
+ writer.write(None)
+ assert writer.build() == b'{"a":123,"b":[],"c":null}'
+
+ def test_write_str(self, writer):
+ with writer.array():
+ writer.write("\b")
+ writer.write("\f")
+ writer.write("\n")
+ writer.write("\r")
+ writer.write("\t")
+ assert writer.build() == b'["\\b","\\f","\\n","\\r","\\t"]'

0 comments on commit 9ea673f

Please sign in to comment.