Skip to content

Commit 036d0ef

Browse files
committed
Added json to enhanced preimports
1 parent 7bbec46 commit 036d0ef

File tree

5 files changed

+130
-2
lines changed

5 files changed

+130
-2
lines changed

docs/pages/enhancements.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,15 @@ A context manager is also available:
8989

9090
-----
9191

92+
## `json`
93+
94+
`json`, while imported with Tinyscript, is enhanced with two additional convenience functions, that is:
95+
96+
- `dumpc`: this dumps a JSON with its comments previously loaded while using `loadc`.
97+
- `loadc`: this loads a JSON that has comments (starting with `#`) so that it does not fail when loading with `json` while annotated.
98+
99+
-----
100+
92101
## `logging`
93102

94103
`logging` is slightly enhanced with a few things:

src/tinyscript/VERSION.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.30.10
1+
1.30.12

src/tinyscript/preimports/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
'hashlib': "hash",
1919
'inspect': "inspectp",
2020
'itertools': "itools",
21+
'json': "jsonp",
2122
'logging': "log",
2223
'random': "rand",
2324
're': "regex",
@@ -35,7 +36,6 @@
3536
"configparser",
3637
"ctypes",
3738
"fileinput",
38-
"json",
3939
"os",
4040
"platform",
4141
"shlex",

src/tinyscript/preimports/jsonp.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# -*- coding: UTF-8 -*-
2+
"""Module for enhancing json preimport.
3+
4+
"""
5+
import json
6+
7+
from ..helpers import ensure_str
8+
9+
10+
_CACHE = dict()
11+
12+
13+
def dumpc(obj, fp, skipkeys=False, ensure_ascii=True, check_circular=True, allow_nan=True, cls=None, indent=None,
14+
separators=None, default=None, sort_keys=False, **kw):
15+
""" Serialize ``obj`` as a JSON formatted stream to ``fp`` (a ``.write()``-supporting file-like object. """
16+
comments = _CACHE.get(id(obj), {})
17+
indent = comments.get('indent', indent)
18+
s = json.dumps(obj, skipkeys=skipkeys, ensure_ascii=ensure_ascii, check_circular=check_circular,
19+
allow_nan=allow_nan, cls=cls, indent=indent, separators=separators, default=default,
20+
sort_keys=sort_keys, **kw)
21+
if indent:
22+
s, lines = "", s.split("\n")
23+
for l in lines:
24+
try:
25+
ws, c = comments.get('body', {}).get(l.strip().rstrip(","))
26+
s += f"{l}{' '*ws}#{c}\n"
27+
except TypeError:
28+
s += f"{l}\n"
29+
s = "\n".join(f"{' '*ws}#{c}" for ws, c in comments.get('header', [])) + s
30+
fp.write(s.encode() if 'b' in fp.mode else s)
31+
json.dumpc = dumpc
32+
33+
34+
def loadc(fp, cls=None, object_hook=None, parse_float=None, parse_int=None, parse_constant=None, object_pairs_hook=None,
35+
**kw):
36+
""" Deserialize ``fp`` (a ``.read()``-supporting file-like object containing a JSON document with comments) to a
37+
Python object. """
38+
s, comments, header, indent = [], {}, True, None
39+
# collect comments from the header then from the body ; keep track of indentation
40+
for l in ensure_str(fp.read()).split("\n"):
41+
i = len(l) - len(l.lstrip())
42+
if i > 0:
43+
indent = i if indent is None else min(indent, i)
44+
try:
45+
l, c = l.split("#", 1)
46+
ws = len(l) - len(l.rstrip())
47+
except ValueError:
48+
c = None
49+
if header:
50+
if l.strip() == "":
51+
if c:
52+
comments.setdefault('header', [])
53+
comments['header'].append((ws, c.rstrip()))
54+
continue
55+
else:
56+
header = False
57+
s.append(l)
58+
if c:
59+
comments.setdefault('body', {})
60+
comments['body'][l.strip().rstrip(",")] = (ws, c.rstrip())
61+
comments['indent'] = indent
62+
# now parse the comment-free JSON
63+
obj = json.loads("\n".join(s), cls=cls, object_hook=object_hook, parse_float=parse_float, parse_int=parse_int,
64+
parse_constant=parse_constant, object_pairs_hook=object_pairs_hook, **kw)
65+
_CACHE[id(obj)] = comments
66+
return obj
67+
json.loadc = loadc
68+

tests/test_preimports_json.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# -*- coding: UTF-8 -*-
2+
"""Preimports string assets' tests.
3+
4+
"""
5+
from tinyscript.preimports import json
6+
7+
from utils import *
8+
9+
10+
FNAME = ".test.json"
11+
TEST_JSON = """
12+
# test long comment 1
13+
# with another line
14+
{
15+
"test": ["a", "b", "c"],
16+
"other": 1 # test comment 2
17+
}
18+
"""
19+
20+
21+
class TestPreimportsJson(TestCase):
22+
@classmethod
23+
def tearDownClass(cls):
24+
remove(FNAME)
25+
26+
def test_commented_json_dumping(self):
27+
with open(FNAME, 'wt') as f:
28+
f.write(TEST_JSON)
29+
with open(FNAME) as f:
30+
d = json.loadc(f)
31+
d['another'] = True
32+
with open(FNAME, 'wb') as f:
33+
json.dumpc(d, f)
34+
with open(FNAME) as f:
35+
content = f.read()
36+
self.assertIn(" # test long comment 1", content)
37+
self.assertIn(" # with another line", content)
38+
self.assertIn(" # test comment 2", content)
39+
40+
def test_commented_json_loading(self):
41+
with open(FNAME, 'wt') as f:
42+
f.write(TEST_JSON)
43+
with open(FNAME) as f:
44+
self.assertIsNotNone(json.loadc(f))
45+
with open(FNAME, 'wb') as f:
46+
f.write(TEST_JSON.encode())
47+
with open(FNAME, 'rb') as f:
48+
d = json.loadc(f)
49+
self.assertIn('test', d)
50+
self.assertIn('other', d)
51+

0 commit comments

Comments
 (0)