rapydscript-ns 0.9.0 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/README.md +6 -5
- package/TODO.md +1 -3
- package/language-service/index.js +4 -4
- package/package.json +1 -1
- package/release/compiler.js +2 -2
- package/release/signatures.json +3 -3
- package/src/lib/contextlib.pyj +379 -0
- package/src/lib/datetime.pyj +712 -0
- package/src/lib/io.pyj +500 -0
- package/src/lib/json.pyj +227 -0
- package/src/monaco-language-service/diagnostics.js +2 -2
- package/src/tokenizer.pyj +1 -1
- package/test/contextlib.pyj +362 -0
- package/test/datetime.pyj +500 -0
- package/test/debugger_stmt.pyj +41 -0
- package/test/io.pyj +316 -0
- package/test/json.pyj +196 -0
- package/test/unit/web-repl.js +533 -0
- package/web-repl/rapydscript.js +2 -2
package/test/io.pyj
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
# globals: assrt
|
|
2
|
+
# vim:fileencoding=utf-8
|
|
3
|
+
#
|
|
4
|
+
# io.pyj
|
|
5
|
+
# Tests for the io standard library module (StringIO and BytesIO).
|
|
6
|
+
|
|
7
|
+
from io import StringIO, BytesIO, UnsupportedOperation
|
|
8
|
+
|
|
9
|
+
ae = assrt.equal
|
|
10
|
+
ade = assrt.deepEqual
|
|
11
|
+
ok = assrt.ok
|
|
12
|
+
throws = assrt.throws
|
|
13
|
+
|
|
14
|
+
# ── 1. StringIO — construction ────────────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
_sio = StringIO()
|
|
17
|
+
ae(_sio.getvalue(), '')
|
|
18
|
+
ae(_sio.tell(), 0)
|
|
19
|
+
ok(not _sio.closed)
|
|
20
|
+
|
|
21
|
+
_sio2 = StringIO('hello')
|
|
22
|
+
ae(_sio2.getvalue(), 'hello')
|
|
23
|
+
ae(_sio2.tell(), 0)
|
|
24
|
+
|
|
25
|
+
# ── 2. StringIO — read ────────────────────────────────────────────────────────
|
|
26
|
+
|
|
27
|
+
_s = StringIO('hello world')
|
|
28
|
+
ae(_s.read(5), 'hello')
|
|
29
|
+
ae(_s.read(1), ' ')
|
|
30
|
+
ae(_s.read(), 'world')
|
|
31
|
+
ae(_s.read(), '') # at end
|
|
32
|
+
|
|
33
|
+
_s.seek(0)
|
|
34
|
+
ae(_s.read(), 'hello world')
|
|
35
|
+
|
|
36
|
+
# read with size=0
|
|
37
|
+
_s.seek(0)
|
|
38
|
+
ae(_s.read(0), '')
|
|
39
|
+
ae(_s.tell(), 0)
|
|
40
|
+
|
|
41
|
+
# ── 3. StringIO — readline ────────────────────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
_ml = StringIO('line1\nline2\nline3')
|
|
44
|
+
ae(_ml.readline(), 'line1\n')
|
|
45
|
+
ae(_ml.readline(), 'line2\n')
|
|
46
|
+
ae(_ml.readline(), 'line3')
|
|
47
|
+
ae(_ml.readline(), '') # past end
|
|
48
|
+
|
|
49
|
+
# readline with size limit
|
|
50
|
+
_ml2 = StringIO('abcdef\nghijkl')
|
|
51
|
+
ae(_ml2.readline(3), 'abc')
|
|
52
|
+
ae(_ml2.readline(100), 'def\n')
|
|
53
|
+
|
|
54
|
+
# ── 4. StringIO — readlines ───────────────────────────────────────────────────
|
|
55
|
+
|
|
56
|
+
_rls = StringIO('a\nb\nc\n')
|
|
57
|
+
ade(_rls.readlines(), ['a\n', 'b\n', 'c\n'])
|
|
58
|
+
|
|
59
|
+
_rls2 = StringIO('x\ny\nz')
|
|
60
|
+
ade(_rls2.readlines(), ['x\n', 'y\n', 'z'])
|
|
61
|
+
|
|
62
|
+
# hint stops early (total >= hint)
|
|
63
|
+
_hint = StringIO('aa\nbb\ncc\n')
|
|
64
|
+
_hlines = _hint.readlines(hint=4)
|
|
65
|
+
ok(_hlines.length >= 1)
|
|
66
|
+
ok(_hlines.length <= 3)
|
|
67
|
+
|
|
68
|
+
# ── 5. StringIO — write ───────────────────────────────────────────────────────
|
|
69
|
+
|
|
70
|
+
_w = StringIO()
|
|
71
|
+
_w.write('hello')
|
|
72
|
+
_w.write(' ')
|
|
73
|
+
_w.write('world')
|
|
74
|
+
ae(_w.getvalue(), 'hello world')
|
|
75
|
+
ae(_w.tell(), 11)
|
|
76
|
+
|
|
77
|
+
# write returns the number of chars written
|
|
78
|
+
_w2 = StringIO()
|
|
79
|
+
ae(_w2.write('abc'), 3)
|
|
80
|
+
ae(_w2.write(''), 0)
|
|
81
|
+
|
|
82
|
+
# overwrite in the middle
|
|
83
|
+
_ow = StringIO('hello world')
|
|
84
|
+
_ow.seek(6)
|
|
85
|
+
_ow.write('Python')
|
|
86
|
+
ae(_ow.getvalue(), 'hello Python')
|
|
87
|
+
|
|
88
|
+
# ── 6. StringIO — writelines ──────────────────────────────────────────────────
|
|
89
|
+
|
|
90
|
+
_wl = StringIO()
|
|
91
|
+
_wl.writelines(['one', ' ', 'two'])
|
|
92
|
+
ae(_wl.getvalue(), 'one two')
|
|
93
|
+
|
|
94
|
+
# ── 7. StringIO — seek / tell ─────────────────────────────────────────────────
|
|
95
|
+
|
|
96
|
+
_sk = StringIO('abcde')
|
|
97
|
+
ae(_sk.seek(2), 2)
|
|
98
|
+
ae(_sk.tell(), 2)
|
|
99
|
+
ae(_sk.read(2), 'cd')
|
|
100
|
+
|
|
101
|
+
# whence=1 (relative)
|
|
102
|
+
_sk.seek(0)
|
|
103
|
+
_sk.seek(2, 1)
|
|
104
|
+
ae(_sk.tell(), 2)
|
|
105
|
+
|
|
106
|
+
# whence=2 (from end)
|
|
107
|
+
_sk.seek(-2, 2)
|
|
108
|
+
ae(_sk.tell(), 3)
|
|
109
|
+
ae(_sk.read(), 'de')
|
|
110
|
+
|
|
111
|
+
# seek before start clamps to 0
|
|
112
|
+
_sk.seek(-99)
|
|
113
|
+
ae(_sk.tell(), 0)
|
|
114
|
+
|
|
115
|
+
# ── 8. StringIO — truncate ────────────────────────────────────────────────────
|
|
116
|
+
|
|
117
|
+
_tr = StringIO('hello world')
|
|
118
|
+
_tr.seek(5)
|
|
119
|
+
ae(_tr.truncate(), 5)
|
|
120
|
+
ae(_tr.getvalue(), 'hello')
|
|
121
|
+
|
|
122
|
+
_tr2 = StringIO('hello world')
|
|
123
|
+
ae(_tr2.truncate(3), 3)
|
|
124
|
+
ae(_tr2.getvalue(), 'hel')
|
|
125
|
+
|
|
126
|
+
# ── 9. StringIO — closed / context manager ────────────────────────────────────
|
|
127
|
+
|
|
128
|
+
_cm = StringIO('text')
|
|
129
|
+
with _cm as _f:
|
|
130
|
+
ae(_f.read(), 'text')
|
|
131
|
+
ok(_cm.closed)
|
|
132
|
+
|
|
133
|
+
_err_raised = False
|
|
134
|
+
try:
|
|
135
|
+
_cm.read()
|
|
136
|
+
except ValueError:
|
|
137
|
+
_err_raised = True
|
|
138
|
+
ok(_err_raised, 'read on closed StringIO should raise ValueError')
|
|
139
|
+
|
|
140
|
+
# ── 10. StringIO — readable / writable / seekable ────────────────────────────
|
|
141
|
+
|
|
142
|
+
_rws = StringIO()
|
|
143
|
+
ok(_rws.readable())
|
|
144
|
+
ok(_rws.writable())
|
|
145
|
+
ok(_rws.seekable())
|
|
146
|
+
|
|
147
|
+
# ── 11. StringIO — iteration ──────────────────────────────────────────────────
|
|
148
|
+
|
|
149
|
+
_it = StringIO('line1\nline2\nline3\n')
|
|
150
|
+
_lines = list(_it)
|
|
151
|
+
ade(_lines, ['line1\n', 'line2\n', 'line3\n'])
|
|
152
|
+
|
|
153
|
+
# ── 12. BytesIO — construction ────────────────────────────────────────────────
|
|
154
|
+
|
|
155
|
+
_bio = BytesIO()
|
|
156
|
+
ae(len(_bio.getvalue()), 0)
|
|
157
|
+
ae(_bio.tell(), 0)
|
|
158
|
+
ok(not _bio.closed)
|
|
159
|
+
|
|
160
|
+
_bio2 = BytesIO(bytes([72, 101, 108, 108, 111]))
|
|
161
|
+
ae(len(_bio2.getvalue()), 5)
|
|
162
|
+
ae(_bio2.getvalue()[0], 72)
|
|
163
|
+
ae(_bio2.getvalue()[4], 111)
|
|
164
|
+
|
|
165
|
+
# init from bytearray
|
|
166
|
+
_bio3 = BytesIO(bytearray([1, 2, 3]))
|
|
167
|
+
ae(len(_bio3.getvalue()), 3)
|
|
168
|
+
|
|
169
|
+
# ── 13. BytesIO — read ────────────────────────────────────────────────────────
|
|
170
|
+
|
|
171
|
+
_br = BytesIO(bytes([10, 20, 30, 40, 50]))
|
|
172
|
+
ae(_br.read(2)[0], 10)
|
|
173
|
+
ae(_br.read(2)[0], 30)
|
|
174
|
+
ae(len(_br.read()), 1)
|
|
175
|
+
ae(len(_br.read()), 0) # at end
|
|
176
|
+
|
|
177
|
+
_br.seek(0)
|
|
178
|
+
ae(len(_br.read()), 5)
|
|
179
|
+
|
|
180
|
+
# ── 14. BytesIO — readline ────────────────────────────────────────────────────
|
|
181
|
+
|
|
182
|
+
# b'line1\nline2' (0x0a is newline)
|
|
183
|
+
_brl = BytesIO(bytes([108, 49, 10, 108, 50])) # 'l1\nl2'
|
|
184
|
+
_first = _brl.readline()
|
|
185
|
+
ae(len(_first), 3)
|
|
186
|
+
ae(_first[2], 10) # includes the newline byte
|
|
187
|
+
|
|
188
|
+
_second = _brl.readline()
|
|
189
|
+
ae(len(_second), 2)
|
|
190
|
+
|
|
191
|
+
ae(len(_brl.readline()), 0) # at end
|
|
192
|
+
|
|
193
|
+
# ── 15. BytesIO — write ───────────────────────────────────────────────────────
|
|
194
|
+
|
|
195
|
+
_bw = BytesIO()
|
|
196
|
+
ae(_bw.write(bytes([1, 2, 3])), 3)
|
|
197
|
+
ae(_bw.write(bytes([4, 5])), 2)
|
|
198
|
+
_bw.seek(0)
|
|
199
|
+
ae(len(_bw.read()), 5)
|
|
200
|
+
ae(_bw.getvalue()[0], 1)
|
|
201
|
+
ae(_bw.getvalue()[4], 5)
|
|
202
|
+
|
|
203
|
+
# write returns byte count
|
|
204
|
+
_bw2 = BytesIO()
|
|
205
|
+
ae(_bw2.write(bytes()), 0)
|
|
206
|
+
|
|
207
|
+
# overwrite in the middle
|
|
208
|
+
_bo = BytesIO(bytes([1, 2, 3, 4, 5]))
|
|
209
|
+
_bo.seek(1)
|
|
210
|
+
_bo.write(bytes([20, 30]))
|
|
211
|
+
_bo.seek(0)
|
|
212
|
+
_boval = _bo.read()
|
|
213
|
+
ae(_boval[0], 1)
|
|
214
|
+
ae(_boval[1], 20)
|
|
215
|
+
ae(_boval[2], 30)
|
|
216
|
+
ae(_boval[3], 4)
|
|
217
|
+
ae(_boval[4], 5)
|
|
218
|
+
|
|
219
|
+
# write past end extends with zeros
|
|
220
|
+
_bext = BytesIO(bytes([1, 2]))
|
|
221
|
+
_bext.seek(4)
|
|
222
|
+
_bext.write(bytes([99]))
|
|
223
|
+
_bextval = _bext.getvalue()
|
|
224
|
+
ae(len(_bextval), 5)
|
|
225
|
+
ae(_bextval[2], 0)
|
|
226
|
+
ae(_bextval[3], 0)
|
|
227
|
+
ae(_bextval[4], 99)
|
|
228
|
+
|
|
229
|
+
# ── 16. BytesIO — writelines ─────────────────────────────────────────────────
|
|
230
|
+
|
|
231
|
+
_bwl = BytesIO()
|
|
232
|
+
_bwl.writelines([bytes([1, 2]), bytes([3, 4])])
|
|
233
|
+
ae(len(_bwl.getvalue()), 4)
|
|
234
|
+
|
|
235
|
+
# ── 17. BytesIO — seek / tell ─────────────────────────────────────────────────
|
|
236
|
+
|
|
237
|
+
_bsk = BytesIO(bytes([10, 20, 30, 40, 50]))
|
|
238
|
+
ae(_bsk.seek(3), 3)
|
|
239
|
+
ae(_bsk.tell(), 3)
|
|
240
|
+
ae(_bsk.read(1)[0], 40)
|
|
241
|
+
|
|
242
|
+
# whence=1
|
|
243
|
+
_bsk.seek(0)
|
|
244
|
+
_bsk.seek(2, 1)
|
|
245
|
+
ae(_bsk.tell(), 2)
|
|
246
|
+
|
|
247
|
+
# whence=2
|
|
248
|
+
_bsk.seek(-1, 2)
|
|
249
|
+
ae(_bsk.tell(), 4)
|
|
250
|
+
ae(_bsk.read(1)[0], 50)
|
|
251
|
+
|
|
252
|
+
# clamp below 0
|
|
253
|
+
_bsk.seek(-999)
|
|
254
|
+
ae(_bsk.tell(), 0)
|
|
255
|
+
|
|
256
|
+
# ── 18. BytesIO — truncate ────────────────────────────────────────────────────
|
|
257
|
+
|
|
258
|
+
_btr = BytesIO(bytes([1, 2, 3, 4, 5]))
|
|
259
|
+
_btr.seek(3)
|
|
260
|
+
ae(_btr.truncate(), 3)
|
|
261
|
+
ae(len(_btr.getvalue()), 3)
|
|
262
|
+
|
|
263
|
+
_btr2 = BytesIO(bytes([10, 20, 30, 40]))
|
|
264
|
+
ae(_btr2.truncate(2), 2)
|
|
265
|
+
ae(len(_btr2.getvalue()), 2)
|
|
266
|
+
ae(_btr2.getvalue()[1], 20)
|
|
267
|
+
|
|
268
|
+
# ── 19. BytesIO — closed / context manager ───────────────────────────────────
|
|
269
|
+
|
|
270
|
+
_bcm = BytesIO(bytes([7, 8, 9]))
|
|
271
|
+
with _bcm as _bf:
|
|
272
|
+
_bf.seek(0)
|
|
273
|
+
ae(_bf.read()[0], 7)
|
|
274
|
+
ok(_bcm.closed)
|
|
275
|
+
|
|
276
|
+
_berr_raised = False
|
|
277
|
+
try:
|
|
278
|
+
_bcm.read()
|
|
279
|
+
except ValueError:
|
|
280
|
+
_berr_raised = True
|
|
281
|
+
ok(_berr_raised, 'read on closed BytesIO should raise ValueError')
|
|
282
|
+
|
|
283
|
+
# ── 20. BytesIO — readable / writable / seekable ─────────────────────────────
|
|
284
|
+
|
|
285
|
+
_brws = BytesIO()
|
|
286
|
+
ok(_brws.readable())
|
|
287
|
+
ok(_brws.writable())
|
|
288
|
+
ok(_brws.seekable())
|
|
289
|
+
|
|
290
|
+
# ── 21. BytesIO — iteration ───────────────────────────────────────────────────
|
|
291
|
+
|
|
292
|
+
_bit = BytesIO(bytes([65, 66, 10, 67, 68, 10])) # 'AB\nCD\n'
|
|
293
|
+
_bitlines = list(_bit)
|
|
294
|
+
ae(_bitlines.length, 2)
|
|
295
|
+
ae(_bitlines[0][0], 65) # 'A'
|
|
296
|
+
ae(_bitlines[0][2], 10) # '\n'
|
|
297
|
+
|
|
298
|
+
# ── 22. getvalue() does not depend on position ────────────────────────────────
|
|
299
|
+
|
|
300
|
+
_gv = StringIO('hello')
|
|
301
|
+
_gv.seek(3)
|
|
302
|
+
ae(_gv.getvalue(), 'hello') # full buffer regardless of pos
|
|
303
|
+
|
|
304
|
+
_bgv = BytesIO(bytes([1, 2, 3]))
|
|
305
|
+
_bgv.seek(2)
|
|
306
|
+
ae(len(_bgv.getvalue()), 3)
|
|
307
|
+
|
|
308
|
+
# ── 23. round-trip with json.dump / json.load ────────────────────────────────
|
|
309
|
+
|
|
310
|
+
from json import dump, load
|
|
311
|
+
_jsio = StringIO()
|
|
312
|
+
dump({'key': 'value', 'n': 42}, _jsio)
|
|
313
|
+
_jsio.seek(0)
|
|
314
|
+
_result = load(_jsio)
|
|
315
|
+
ae(_result.key, 'value')
|
|
316
|
+
ae(_result.n, 42)
|
package/test/json.pyj
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# globals: assrt
|
|
2
|
+
# vim:fileencoding=utf-8
|
|
3
|
+
#
|
|
4
|
+
# json.pyj
|
|
5
|
+
# Tests for the json standard library module.
|
|
6
|
+
|
|
7
|
+
from json import dumps, loads, dump, load, JSONDecodeError
|
|
8
|
+
|
|
9
|
+
ae = assrt.equal
|
|
10
|
+
ade = assrt.deepEqual
|
|
11
|
+
ok = assrt.ok
|
|
12
|
+
throws = assrt.throws
|
|
13
|
+
|
|
14
|
+
# ── 1. dumps — basic types ────────────────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
ae(dumps(None), 'null')
|
|
17
|
+
ae(dumps(True), 'true')
|
|
18
|
+
ae(dumps(False), 'false')
|
|
19
|
+
ae(dumps(42), '42')
|
|
20
|
+
ae(dumps(3.14), '3.14')
|
|
21
|
+
ae(dumps('hello'), '"hello"')
|
|
22
|
+
ae(dumps('with "quotes"'), '"with \\"quotes\\""')
|
|
23
|
+
|
|
24
|
+
# ── 2. dumps — collections ────────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
ae(dumps([]), '[]')
|
|
27
|
+
ae(dumps([1, 2, 3]), '[1,2,3]')
|
|
28
|
+
ae(dumps({}), '{}')
|
|
29
|
+
|
|
30
|
+
# Object with single key (no ordering ambiguity)
|
|
31
|
+
_d1 = dumps({'a': 1})
|
|
32
|
+
ae(_d1, '{"a":1}')
|
|
33
|
+
|
|
34
|
+
# ── 3. dumps — indent ─────────────────────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
_pretty = dumps([1, 2], indent=2)
|
|
37
|
+
ok(_pretty.indexOf('\n') >= 0, 'indented output should contain newlines')
|
|
38
|
+
ok(_pretty.indexOf(' ') >= 0, 'indented output should contain spaces')
|
|
39
|
+
|
|
40
|
+
# ── 4. dumps — sort_keys ──────────────────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
_sk = dumps({'b': 2, 'a': 1}, sort_keys=True)
|
|
43
|
+
ae(_sk, '{"a":1,"b":2}')
|
|
44
|
+
|
|
45
|
+
# Nested sort_keys
|
|
46
|
+
_sk2 = dumps({'z': {'y': 3, 'x': 4}, 'a': 1}, sort_keys=True)
|
|
47
|
+
ae(_sk2, '{"a":1,"z":{"x":4,"y":3}}')
|
|
48
|
+
|
|
49
|
+
# ── 5. dumps — separators ─────────────────────────────────────────────────────
|
|
50
|
+
|
|
51
|
+
# Compact form used in Python: separators=(',', ':')
|
|
52
|
+
_compact = dumps({'a': 1, 'b': 2}, sort_keys=True, separators=(',', ':'))
|
|
53
|
+
ae(_compact, '{"a":1,"b":2}')
|
|
54
|
+
|
|
55
|
+
# ── 6. dumps — default callback ───────────────────────────────────────────────
|
|
56
|
+
|
|
57
|
+
class _Point:
|
|
58
|
+
def __init__(self, x, y):
|
|
59
|
+
self.x = x
|
|
60
|
+
self.y = y
|
|
61
|
+
|
|
62
|
+
def _point_default(obj):
|
|
63
|
+
if isinstance(obj, _Point):
|
|
64
|
+
return {'x': obj.x, 'y': obj.y}
|
|
65
|
+
raise TypeError('not serializable')
|
|
66
|
+
|
|
67
|
+
_pt_json = dumps(_Point(3, 4), dflt=_point_default)
|
|
68
|
+
_pt_obj = loads(_pt_json)
|
|
69
|
+
ae(_pt_obj.x, 3)
|
|
70
|
+
ae(_pt_obj.y, 4)
|
|
71
|
+
|
|
72
|
+
# ── 7. dumps — allow_nan ──────────────────────────────────────────────────────
|
|
73
|
+
|
|
74
|
+
_nan_raised = False
|
|
75
|
+
try:
|
|
76
|
+
dumps(v'NaN')
|
|
77
|
+
except ValueError:
|
|
78
|
+
_nan_raised = True
|
|
79
|
+
ok(_nan_raised, 'dumps(NaN) should raise ValueError by default')
|
|
80
|
+
|
|
81
|
+
_inf_raised = False
|
|
82
|
+
try:
|
|
83
|
+
dumps(v'Infinity')
|
|
84
|
+
except ValueError:
|
|
85
|
+
_inf_raised = True
|
|
86
|
+
ok(_inf_raised, 'dumps(Infinity) should raise ValueError by default')
|
|
87
|
+
|
|
88
|
+
# allow_nan=True should not raise
|
|
89
|
+
_nan_str = dumps(v'NaN', allow_nan=True)
|
|
90
|
+
ok(_nan_str.length > 0, 'allow_nan=True should return a string')
|
|
91
|
+
|
|
92
|
+
# ── 8. loads — basic types ────────────────────────────────────────────────────
|
|
93
|
+
|
|
94
|
+
ae(loads('null'), None)
|
|
95
|
+
ae(loads('true'), True)
|
|
96
|
+
ae(loads('false'), False)
|
|
97
|
+
ae(loads('42'), 42)
|
|
98
|
+
ae(loads('3.14'), 3.14)
|
|
99
|
+
ae(loads('"hello"'), 'hello')
|
|
100
|
+
|
|
101
|
+
# ── 9. loads — collections ────────────────────────────────────────────────────
|
|
102
|
+
|
|
103
|
+
_arr = loads('[1, 2, 3]')
|
|
104
|
+
ade(_arr, [1, 2, 3])
|
|
105
|
+
|
|
106
|
+
_obj = loads('{"a": 1, "b": 2}')
|
|
107
|
+
ae(_obj.a, 1)
|
|
108
|
+
ae(_obj.b, 2)
|
|
109
|
+
|
|
110
|
+
# ── 10. loads — nested structures ─────────────────────────────────────────────
|
|
111
|
+
|
|
112
|
+
_nested = loads('{"users": [{"name": "Alice", "age": 30}, {"name": "Bob", "age": 25}]}')
|
|
113
|
+
ae(_nested.users.length, 2)
|
|
114
|
+
ae(_nested.users[0].name, 'Alice')
|
|
115
|
+
ae(_nested.users[1].age, 25)
|
|
116
|
+
|
|
117
|
+
# ── 11. loads — object_hook ───────────────────────────────────────────────────
|
|
118
|
+
|
|
119
|
+
class _NameAge:
|
|
120
|
+
def __init__(self, d):
|
|
121
|
+
self.name = d.name if d.name else ''
|
|
122
|
+
self.age = d.age if d.age else 0
|
|
123
|
+
|
|
124
|
+
def _obj_hook(d):
|
|
125
|
+
if d.name is not None:
|
|
126
|
+
return _NameAge(d)
|
|
127
|
+
return d
|
|
128
|
+
|
|
129
|
+
_h = loads('{"name": "Alice", "age": 30}', object_hook=_obj_hook)
|
|
130
|
+
ok(isinstance(_h, _NameAge), 'object_hook should produce a _NameAge instance')
|
|
131
|
+
ae(_h.name, 'Alice')
|
|
132
|
+
ae(_h.age, 30)
|
|
133
|
+
|
|
134
|
+
# ── 12. loads — object_pairs_hook ─────────────────────────────────────────────
|
|
135
|
+
|
|
136
|
+
_pairs_result = []
|
|
137
|
+
def _pairs_hook(pairs):
|
|
138
|
+
_pairs_result.push(pairs.length)
|
|
139
|
+
return pairs
|
|
140
|
+
|
|
141
|
+
loads('{"x": 1, "y": 2}', object_pairs_hook=_pairs_hook)
|
|
142
|
+
ae(_pairs_result[0], 2, 'object_pairs_hook should receive 2 pairs')
|
|
143
|
+
|
|
144
|
+
# ── 13. loads — parse_float / parse_int ───────────────────────────────────────
|
|
145
|
+
|
|
146
|
+
_floats = []
|
|
147
|
+
def _capture_float(s):
|
|
148
|
+
_floats.push(s)
|
|
149
|
+
return float(s)
|
|
150
|
+
|
|
151
|
+
loads('3.14', parse_float=_capture_float)
|
|
152
|
+
ae(_floats.length, 1)
|
|
153
|
+
ae(_floats[0], '3.14')
|
|
154
|
+
|
|
155
|
+
_ints = []
|
|
156
|
+
def _capture_int(s):
|
|
157
|
+
_ints.push(s)
|
|
158
|
+
return int(s)
|
|
159
|
+
|
|
160
|
+
loads('42', parse_int=_capture_int)
|
|
161
|
+
ae(_ints.length, 1)
|
|
162
|
+
ae(_ints[0], '42')
|
|
163
|
+
|
|
164
|
+
# ── 14. loads — JSONDecodeError ───────────────────────────────────────────────
|
|
165
|
+
|
|
166
|
+
_err_raised = False
|
|
167
|
+
try:
|
|
168
|
+
loads('{invalid json}')
|
|
169
|
+
except JSONDecodeError as e:
|
|
170
|
+
_err_raised = True
|
|
171
|
+
ok(isinstance(e, JSONDecodeError), 'should be JSONDecodeError')
|
|
172
|
+
ok(isinstance(e, ValueError), 'JSONDecodeError should be a ValueError')
|
|
173
|
+
ok(_err_raised, 'invalid JSON should raise JSONDecodeError')
|
|
174
|
+
|
|
175
|
+
# ── 15. round-trip ────────────────────────────────────────────────────────────
|
|
176
|
+
|
|
177
|
+
_original = {'name': 'test', 'values': [1, 2, 3], 'nested': {'ok': True}}
|
|
178
|
+
_rt = loads(dumps(_original))
|
|
179
|
+
ae(_rt.name, _original.name)
|
|
180
|
+
ade(_rt.values, _original.values)
|
|
181
|
+
ae(_rt.nested.ok, _original.nested.ok)
|
|
182
|
+
|
|
183
|
+
# ── 16. dump / load via file-like object ──────────────────────────────────────
|
|
184
|
+
|
|
185
|
+
class _StringIO:
|
|
186
|
+
def __init__(self):
|
|
187
|
+
self._buf = ''
|
|
188
|
+
def write(self, s):
|
|
189
|
+
self._buf += s
|
|
190
|
+
def read(self):
|
|
191
|
+
return self._buf
|
|
192
|
+
|
|
193
|
+
_sio = _StringIO()
|
|
194
|
+
dump({'key': 'value'}, _sio)
|
|
195
|
+
_loaded = load(_sio)
|
|
196
|
+
ae(_loaded.key, 'value')
|