Skip to content

Commit 2fbd1f4

Browse files
committed
resolved issues raised by ruff and pyright
1 parent 1c16edc commit 2fbd1f4

8 files changed

Lines changed: 72 additions & 68 deletions

File tree

src/sim_explorer/case.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
from sim_explorer.models import Temporal
3434
from sim_explorer.system_interface import SystemInterface
3535
from sim_explorer.system_interface_osp import SystemInterfaceOSP
36-
from sim_explorer.utils.json5 import json5_check
36+
from sim_explorer.utils.json5 import json5_check, json5_path, json5_read, json5_update, json5_write
3737
from sim_explorer.utils.misc import from_xml
3838
from sim_explorer.utils.paths import relative_path
3939
from sim_explorer.utils.types import (
@@ -749,7 +749,7 @@ def read_cases(self) -> None:
749749
"""
750750

751751
if not isinstance(self.js_py.get("base"), dict) or json5_path(self.js_py, "$.base.spec", dict) is None:
752-
raise CaseInitError(f"Main section 'base' is needed. Found {list(self.js_py.js_py.keys())}") from None
752+
raise CaseInitError(f"Main section 'base' is needed. Found {list(self.js_py.keys())}") from None
753753

754754
# we need to peek into the base case where startTime and stopTime should be defined
755755
start_time: float = json5_path(self.js_py, "$.base.spec.startTime", float) or 0.0
@@ -830,8 +830,6 @@ def disect_variable(
830830
raise ValueError(f"Unhandled index {p}[{i}] for variable {pre}") from e
831831
if not 0 <= idx < cvar_len:
832832
raise ValueError(f"Index {idx} of variable {pre} out of range") from None
833-
if not isinstance(rng, list):
834-
raise ValueError(f"A list was expected as range here. Found {rng}") from None
835833
rng.append(idx)
836834
else:
837835
assert len(parts_ellipses) == 2, f"RangeError: Exactly two indices expected in {p} of {pre}" # noqa: PLR2004
@@ -939,7 +937,7 @@ def __init__(
939937
) -> None:
940938
self.file: Path | None # None denotes that results are not automatically saved
941939
self.case: Case | None = None
942-
self.res: Json5
940+
self.res: dict[str, Any] = {}
943941
if (case is None or isinstance(case, str | Path)) and file is not None:
944942
self._init_from_existing(file) # instantiating from existing results file (work with data)
945943
elif isinstance(case, Case): # instantiating from cases file (for data collection)
@@ -962,7 +960,7 @@ def _init_from_existing(self, file: str | Path) -> None:
962960
raise CaseInitError(f"Cases {Path(_cases)} instantiation error") from ValueError
963961
case_name: str | None = header.get("case")
964962
assert case_name is not None, f"Case name not found in results file {file}"
965-
self.case: Case | None = cases.case_by_name(case_name)
963+
self.case = cases.case_by_name(case_name)
966964
assert self.case is not None, f"Case {case_name} not found."
967965
assert isinstance(self.case.cases, Cases), "Cases object not defined."
968966
self.case.add_results_object(res=self) # make Results object known to self.case

src/sim_explorer/system_interface.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ def read_system_structure(
8787
-------
8888
The system structure as (json) dict as if the structure was read through osp_system_structure_from_js5
8989
"""
90-
system_structure: dict[str, Any] | Json5
90+
system_structure: dict[str, Any]
9191
assert file.exists(), f"System structure {file} not found"
9292
if file.suffix == ".xml": # assume the standard OspSystemStructure.xml file
9393
system_structure = read_system_structure_xml(file)

src/sim_explorer/utils/json5.py

Lines changed: 52 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
import codecs
44
import logging
55
import re
6+
from collections.abc import Sequence
67
from pathlib import Path
7-
from typing import Any, Sequence
8+
from typing import Any
89

910
import 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
1212
from pyjson5 import Json5Exception, Json5IllegalCharacter
1313

1414
logger = 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}")

tests/test_case.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ def test_case_range(timetable: Cases):
152152
case_x = timetable.case_by_name("caseX")
153153
assert case_x is not None, "Case with name 'caseX' does not exist."
154154
with pytest.raises(ValueError) as err:
155-
assert case_x.cases.disect_variable("x[99]")
155+
_ = case_x.cases.disect_variable("x[99]")
156156
assert err.value.args[0] == "Index 99 of variable x out of range"
157157
assert case_x.cases.disect_variable("x[1]")[2] == [1]
158158
var_info = case_x.cases.disect_variable("i")[1]

tests/test_cases.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,12 @@ def test_cases():
3434
"modelFile not as expected"
3535
)
3636
for c in ("base", "restitution", "restitutionAndGravity", "gravity"):
37-
assert c in cases.js_py.js_py, f"The case '{c}' is expected to be defined in {list(cases.js_py.js_py.keys())}"
38-
assert cases.js_py.jspath("$.header.variables.g[0]") == "bb"
39-
assert cases.js_py.jspath("$.header.variables.g[1]") == "g", f"Found {cases.js_py.jspath('$.variables.g[1]')}"
40-
assert cases.js_py.jspath("$.header.variables.g[2]") == "Gravity acting on the ball"
37+
assert c in cases.js_py, f"The case '{c}' is expected to be defined in {list(cases.js_py.keys())}"
38+
assert json5_path(cases.js_py, "$.header.variables.g[0]") == "bb"
39+
assert json5_path(cases.js_py, "$.header.variables.g[1]") == "g", (
40+
f"Found {json5_path(cases.js_py, '$.variables.g[1]')}"
41+
)
42+
assert json5_path(cases.js_py, "$.header.variables.g[2]") == "Gravity acting on the ball"
4143
# find_by_name
4244
for c in cases.base.list_cases(as_name=False, flat=True):
4345
assert isinstance(c, Case)

tests/test_pyjson5.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import logging
22
import time
33
from pathlib import Path
4+
from typing import Any
45

56
import pytest
67

@@ -67,7 +68,7 @@ def repair_all_json5(folder: Path, do_change: int):
6768
for f2 in ("data/BouncingBall3D/test_case", "data/BouncingBall3D/test_results"):
6869
f = Path(__file__).parent / f2
6970
js5 = json5_read(f, save=0) # we do not change these as the comments will vanish
70-
json5_check(js5)
71+
_ = json5_check(js5)
7172
logger.info(f"File {f} ok")
7273

7374

@@ -76,7 +77,7 @@ def do_change() -> int:
7677
return 0
7778

7879

79-
def test_all_json5(do_change):
80+
def test_all_json5(do_change: int) -> None:
8081
repair_all_json5(Path(__file__).parent, do_change=do_change)
8182

8283

@@ -93,7 +94,7 @@ def test_all_json5(do_change):
9394

9495

9596
def test_write():
96-
js5: dict[str, str|int|float|list|dict] = {
97+
js5: dict[str, str | int | float | list[Any] | dict[str, Any]] = {
9798
"Hello": "World",
9899
"t@his": ["is", "a", "simple", "test"],
99100
"h[0]w": "this dict",
@@ -106,8 +107,7 @@ def test_write():
106107
}
107108
# print("AS STRING", json5.dumps(js5, quotationmark="'", quote_keys=False))
108109
json5_write(js5, "test_write.js5", indent=3)
109-
with open("test_write.js5", "r") as fp:
110-
txt = fp.read()
110+
txt = Path("test_write.js5").read_text()
111111
expected = """
112112
{
113113
Hello: 'World',
@@ -121,7 +121,7 @@ def test_write():
121121
notANumber: 'NaN'}"""
122122
assert txt != expected, f"EXPECTED:\n{expected}\nFOUND:\n{txt}"
123123
# print(txt)
124-
json5_read("test_write.js5")
124+
_ = json5_read("test_write.js5")
125125

126126

127127
if __name__ == "__main__":

tests/test_run_bouncingball0.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ def test_run_cases(): # noqa: PLR0915
131131
}
132132
"""
133133
# key results data for base case
134-
h0 = res.jspath("$.['0'].bb.h")
134+
h0 = json5_path(res, "$.['0'].bb.h")
135135
assert h0 is not None
136136
t0 = sqrt(2 * h0 / 9.81) # half-period time with full restitution
137137
v_max = sqrt(2 * h0 * 9.81) # speed when hitting bottom

tests/test_system_interface_osp.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ def di(
6161
caus: str,
6262
expected: str,
6363
):
64-
res = SystemInterfaceOSP.valid_initial(causality=caus, variability=var, only_default=only_default)[0]
64+
res = SystemInterfaceOSP.valid_initial(causality=caus, variability=var)[0]
6565
assert res == expected, f"valid_initial({var}, {caus}): Found {res} but expected {expected}"
6666

6767
di(var="constant", caus="parameter", expected="ERROR_a")

0 commit comments

Comments
 (0)