import of github code used for the hackathon
[github-barcamp-201407] / ijson-1.1 / ijson / backends / yajl.py
1 '''
2 Wrapper for YAJL C library version 1.x.
3 '''
4
5 from ctypes import Structure, c_uint, c_ubyte, c_int, c_long, c_double, \
6                    c_void_p, c_char_p, CFUNCTYPE, POINTER, byref, string_at, cast , \
7                    cdll, util, c_char
8 from decimal import Decimal
9
10 from ijson import common, backends
11 from ijson.compat import b2s
12
13
14 yajl = backends.find_yajl(1)
15
16 yajl.yajl_alloc.restype = POINTER(c_char)
17 yajl.yajl_get_error.restype = POINTER(c_char)
18
19 C_EMPTY = CFUNCTYPE(c_int, c_void_p)
20 C_INT = CFUNCTYPE(c_int, c_void_p, c_int)
21 C_LONG = CFUNCTYPE(c_int, c_void_p, c_long)
22 C_DOUBLE = CFUNCTYPE(c_int, c_void_p, c_double)
23 C_STR = CFUNCTYPE(c_int, c_void_p, POINTER(c_ubyte), c_uint)
24
25
26 def number(value):
27     '''
28     Helper function casting a string that represents any Javascript number
29     into appropriate Python value: either int or Decimal.
30     '''
31     try:
32         return int(value)
33     except ValueError:
34         return Decimal(value)
35
36 _callback_data = [
37     # Mapping of JSON parser events to callback C types and value converters.
38     # Used to define the Callbacks structure and actual callback functions
39     # inside the parse function.
40     ('null', C_EMPTY, lambda: None),
41     ('boolean', C_INT, lambda v: bool(v)),
42     # "integer" and "double" aren't actually yielded by yajl since "number"
43     # takes precedence if defined
44     ('integer', C_LONG, lambda v, l: int(string_at(v, l))),
45     ('double', C_DOUBLE, lambda v, l: float(string_at(v, l))),
46     ('number', C_STR, lambda v, l: number(b2s(string_at(v, l)))),
47     ('string', C_STR, lambda v, l: string_at(v, l).decode('utf-8')),
48     ('start_map', C_EMPTY, lambda: None),
49     ('map_key', C_STR, lambda v, l: b2s(string_at(v, l))),
50     ('end_map', C_EMPTY, lambda: None),
51     ('start_array', C_EMPTY, lambda: None),
52     ('end_array', C_EMPTY, lambda: None),
53 ]
54
55 class Callbacks(Structure):
56     _fields_ = [(name, type) for name, type, func in _callback_data]
57
58 class Config(Structure):
59     _fields_ = [
60         ("allowComments", c_uint),
61         ("checkUTF8", c_uint)
62     ]
63
64 YAJL_OK = 0
65 YAJL_CANCELLED = 1
66 YAJL_INSUFFICIENT_DATA = 2
67 YAJL_ERROR = 3
68
69
70 def basic_parse(f, allow_comments=False, check_utf8=False, buf_size=64 * 1024):
71     '''
72     Iterator yielding unprefixed events.
73
74     Parameters:
75
76     - f: a readable file-like object with JSON input
77     - allow_comments: tells parser to allow comments in JSON input
78     - check_utf8: if True, parser will cause an error if input is invalid utf-8
79     - buf_size: a size of an input buffer
80     '''
81     events = []
82
83     def callback(event, func_type, func):
84         def c_callback(context, *args):
85             events.append((event, func(*args)))
86             return 1
87         return func_type(c_callback)
88
89     callbacks = Callbacks(*[callback(*data) for data in _callback_data])
90     config = Config(allow_comments, check_utf8)
91     handle = yajl.yajl_alloc(byref(callbacks), byref(config), None, None)
92     try:
93         while True:
94             buffer = f.read(buf_size)
95             if buffer:
96                 result = yajl.yajl_parse(handle, buffer, len(buffer))
97             else:
98                 result = yajl.yajl_parse_complete(handle)
99             if result == YAJL_ERROR:
100                 perror = yajl.yajl_get_error(handle, 1, buffer, len(buffer))
101                 error = cast(perror, c_char_p).value
102                 yajl.yajl_free_error(handle, perror)
103                 raise common.JSONError(error)
104             if not buffer and not events:
105                 if result == YAJL_INSUFFICIENT_DATA:
106                     raise common.IncompleteJSONError()
107                 break
108
109             for event in events:
110                 yield event
111             events = []
112     finally:
113         yajl.yajl_free(handle)
114
115 def parse(file, **kwargs):
116     '''
117     Backend-specific wrapper for ijson.common.parse.
118     '''
119     return common.parse(basic_parse(file, **kwargs))
120
121 def items(file, prefix):
122     '''
123     Backend-specific wrapper for ijson.common.items.
124     '''
125     return common.items(parse(file), prefix)

Benjamin Mako Hill || Want to submit a patch?