33import codecs
44import logging
55import re
6+ from collections .abc import Sequence
67from pathlib import Path
7- from typing import Any , Sequence
8+ from typing import Any
89
910import pyjson5 as json5
10- from jsonpath_ng .ext import parse # type: ignore
11- from jsonpath_ng .jsonpath import DatumInContext # type: ignore
11+ from jsonpath_ng .ext import parse
1212from pyjson5 import Json5Exception , Json5IllegalCharacter
1313
1414logger = logging .getLogger (__name__ )
@@ -29,7 +29,13 @@ def json5_check(js5: dict[str, Any]) -> bool:
2929 return json5 .encode_noop (js5 )
3030
3131
32- def json5_write (js5 : dict , file : Path | str , * , indent : int = 3 , compact : bool = True ):
32+ def json5_write ( # noqa: C901, PLR0915
33+ js5 : dict [str , Any ],
34+ file : Path | str ,
35+ * ,
36+ indent : int = 3 ,
37+ compact : bool = True ,
38+ ) -> None :
3339 """Use pyjson5 to print the json5 code to file, optionally using indenting the code to make it human-readable.
3440
3541 Args:
@@ -38,7 +44,7 @@ def json5_write(js5: dict, file: Path | str, *, indent: int = 3, compact: bool =
3844 compact (bool) = True: compact file writing, i.e. try to keep keys unquoted and avoid escapes
3945 """
4046
41- def _unescape (chars : str ):
47+ def _unescape (chars : str ) -> str :
4248 """Try to unescape chars. If that results in a valid Json5 value we keep the unescaped version."""
4349 if len (chars ) and chars [0 ] in ("[" , "{" ):
4450 pre = chars [0 ]
@@ -59,7 +65,7 @@ def _unescape(chars: str):
5965 else : # unescaped this is still valid Json5
6066 return pre + unescaped + post
6167
62- def _pretty_print (chars : str ):
68+ def _pretty_print (chars : str ) -> None : # noqa: C901
6369 nonlocal fp , level , indent , _list , _collect
6470
6571 # first all actions with explicit fp.write
@@ -72,8 +78,8 @@ def _pretty_print(chars: str):
7278 except Json5Exception :
7379 _collect += ": "
7480 else :
75- _collect = no_quote + " : "
76- fp .write (_collect )
81+ _collect = f" { no_quote } : "
82+ _ = fp .write (_collect )
7783 _collect = ""
7884 elif chars == "{" :
7985 level += 1
@@ -90,37 +96,36 @@ def _pretty_print(chars: str):
9096 elif chars == "]" :
9197 _list -= 1
9298 # write to file and reset _collect
93- if chars in ( "{" , "}" , "[" , "]" , "," ) :
94- fp .write (_unescape (_collect ))
99+ if chars in { "{" , "}" , "[" , "]" , "," } :
100+ _ = fp .write (_unescape (_collect ))
95101 _collect = ""
96102
97103 assert json5 .encode_noop (js5 ), f"Python object { js5 } is not serializable as Json5"
98104 if indent == - 1 : # just dump it no pretty print, Json5 features, ...
99105 txt = json5 .encode (js5 , quotationmark = "'" )
100106 with Path .open (Path (file ), "w" ) as fp :
101- fp .write (txt )
107+ _ = fp .write (txt )
102108
103109 elif indent >= 0 : # pretty-print and other features are taken into account
104110 level : int = 0
105111 _list : int = 0
106112 _collect : str = ""
107113 with Path .open (Path (file ), "w" ) as fp :
108- json5 .encode_callback (js5 , _pretty_print , supply_bytes = False , quotationmark = "'" )
114+ _ = json5 .encode_callback (js5 , _pretty_print , supply_bytes = False , quotationmark = "'" )
109115
110116
111- def json5_find_identifier_start (txt : str , pos : int ):
117+ def json5_find_identifier_start (txt : str , pos : int ) -> int :
112118 """Find the position of the start of the identifier in txt going backwards from pos."""
113119 p : int = pos - 1
114120 while True :
115121 if p < 0 :
116122 return 0
117- elif txt [p ] in ("," , "{" , "}" , "[" , "]" , ":" ):
123+ if txt [p ] in ("," , "{" , "}" , "[" , "]" , ":" ):
118124 return p + 1
119- else :
120- p -= 1
125+ p -= 1
121126
122127
123- def json5_try_correct (txt : str , pos : int ):
128+ def json5_try_correct (txt : str , pos : int ) -> tuple [ bool , str ] :
124129 """Try to repair the json5 illegal character found at pos in txt.
125130
126131 1. Check whether pos points to a key and set the key in quotation marks.
@@ -145,7 +150,7 @@ def json5_try_correct(txt: str, pos: int):
145150 return (success , txt )
146151
147152
148- def json5_read (file : Path | str , * , save : int = 0 ) -> dict :
153+ def json5_read (file : Path | str , * , save : int = 0 ) -> dict [ str , Any ]: # noqa: C901, PLR0912
149154 """Read the Json5 file.
150155 If key or comment errors are encountered they are tried fixed 'en route'.
151156 save: 0: do not save, 1: save if changed, 2: save in any case. Overwrite file when saving.
@@ -161,8 +166,7 @@ def get_line(txt: str, pos: int) -> int:
161166 _p = txt .find ("\n " , _p ) + 1
162167 if _p > pos or _p <= 0 :
163168 return line + 1
164- else :
165- line += 1
169+ line += 1
166170
167171 with Path .open (Path (file ), "r" ) as fp :
168172 txt = fp .read ()
@@ -177,7 +181,7 @@ def get_line(txt: str, pos: int) -> int:
177181 _line = get_line (txt , pos )
178182 if err .args [0 ].startswith ("Expected b'comma'" ):
179183 raise ValueError (f"Missing comma? in { file } , line { _line } at { txt [pos : pos + 20 ]} " ) from err
180- elif err .args [0 ].startswith ("Expected b'IdentifierStart'" ):
184+ if err .args [0 ].startswith ("Expected b'IdentifierStart'" ):
181185 success , txt = json5_try_correct (txt , pos )
182186 if not success :
183187 raise ValueError (
@@ -197,13 +201,17 @@ def get_line(txt: str, pos: int) -> int:
197201 break
198202 if save == 0 and num_warn > 0 :
199203 logger .warning (f"Decoding the file { file } , { num_warn } illegal characters were detected. Not saved." )
200- elif (save == 1 and num_warn > 0 ) or save == 2 :
204+ elif (save == 1 and num_warn > 0 ) or save == 2 : # noqa: PLR2004
201205 logger .warning (f"Decoding the file { file } , { num_warn } illegal characters were detected. File re-saved." )
202206 json5_write (js5 , file , indent = 3 , compact = True )
203207 return js5
204208
205209
206- def json5_path (js5 : dict [str , Any ], path : str , typ : type | None = None ) -> Any :
210+ def json5_path (
211+ js5 : dict [str , Any ],
212+ path : str ,
213+ typ : type | None = None ,
214+ ) -> Any : # noqa: ANN401
207215 """Evaluate a JsonPath expression on the Json5 code and return the result.
208216
209217 Syntax see `RFC9535 <https://datatracker.ietf.org/doc/html/rfc9535>`_
@@ -231,26 +239,23 @@ def json5_path(js5: dict[str, Any], path: str, typ: type | None = None) -> Any:
231239 """
232240 compiled = parse (path )
233241 data = compiled .find (js5 )
234- # print("DATA", data)
235242 val = None
236- if not len ( data ) : # not found
243+ if not data : # not found
237244 return None
238- elif len (data ) == 1 : # found a single element
239- val = data [0 ].value
240- else : # multiple elements
241- if isinstance (data [0 ], DatumInContext ):
242- val = [x .value for x in data ]
243-
244- if val is not None and typ is not None : # check also the type
245- if not isinstance (val , typ ):
246- try : # try to convert
247- val = typ (val )
248- except ValueError :
249- raise ValueError (f"{ path } matches, but type { typ } does not match { type (val )} in { js5 } ." ) from None
245+ val = data [0 ].value if len (data ) == 1 else [x .value for x in data ]
246+ if val is not None and typ is not None and not isinstance (val , typ ):
247+ try : # try to convert
248+ val = typ (val )
249+ except ValueError :
250+ raise ValueError (f"{ path } matches, but type { typ } does not match { type (val )} in { js5 } ." ) from None
250251 return val
251252
252253
253- def json5_update (js5 : dict [str , Any ], keys : Sequence [str ], data : Any ):
254+ def json5_update (
255+ js5 : dict [str , Any ],
256+ keys : Sequence [str ],
257+ data : Any , # noqa: ANN401
258+ ) -> None :
254259 """Append data to the js_py dict at the path pointed to by keys.
255260 So far this is a minimum implementation for adding data.
256261
@@ -259,20 +264,19 @@ def json5_update(js5: dict[str, Any], keys: Sequence[str], data: Any):
259264 keys (Sequence): Sequence of keys. All keys down to the place where to update the dict shall be included
260265 data (Any): the data to be added/updated. Dicts are updated, lists are appended
261266 """
267+ value : Any = None
262268 for i , k in enumerate (keys ):
263269 if k not in js5 :
264270 for j in range (len (keys ) - 1 , i - 1 , - 1 ):
265271 data = {keys [j ]: data }
266272 break
267- else :
268- parent = js5
269- js5 = js5 [k ] # type: ignore [assignment]
270- # print(f"UPDATE path:{path}, parent:{parent}, k:{k}: {data}")
271- if isinstance (js5 , list ):
272- js5 .append (data )
273- elif isinstance (js5 , dict ):
274- js5 .update (data )
273+ parent = js5
274+ value = js5 [k ]
275+ if isinstance (value , list ):
276+ value .append (data )
277+ elif isinstance (value , dict ):
278+ value .update (data )
275279 elif isinstance (parent , dict ): # update the parent dict (replace a value)
276- js5 .update ({k : data })
280+ parent .update ({k : data })
277281 else :
278- raise ValueError (f"Unknown type of path: { js5 } " )
282+ raise TypeError (f"Unknown type of path: { js5 } " )
0 commit comments