itf-py 0.2.2__py3-none-any.whl → 0.4.0__py3-none-any.whl
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.
Potentially problematic release.
This version of itf-py might be problematic. Click here for more details.
itf_py/__init__.py
CHANGED
|
@@ -3,8 +3,8 @@ Python library to parse and emit Apalache ITF traces.
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
from .itf import (
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
State,
|
|
7
|
+
Trace,
|
|
8
8
|
state_from_json,
|
|
9
9
|
state_to_json,
|
|
10
10
|
trace_from_json,
|
|
@@ -15,8 +15,8 @@ from .itf import (
|
|
|
15
15
|
|
|
16
16
|
__version__ = "0.2.1"
|
|
17
17
|
__all__ = [
|
|
18
|
-
"
|
|
19
|
-
"
|
|
18
|
+
"State",
|
|
19
|
+
"Trace",
|
|
20
20
|
"value_to_json",
|
|
21
21
|
"value_from_json",
|
|
22
22
|
"state_to_json",
|
itf_py/itf.py
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
from collections import namedtuple
|
|
2
2
|
from dataclasses import dataclass
|
|
3
|
-
from typing import Any, Dict, List, Optional
|
|
3
|
+
from typing import Any, Dict, Iterable, List, NoReturn, Optional, SupportsIndex
|
|
4
4
|
|
|
5
5
|
from frozendict import frozendict
|
|
6
|
-
from frozenlist import FrozenList
|
|
7
6
|
|
|
8
7
|
|
|
9
8
|
@dataclass
|
|
10
|
-
class
|
|
9
|
+
class State:
|
|
11
10
|
"""A single state in an ITF trace as a Python object."""
|
|
12
11
|
|
|
13
12
|
meta: Dict[str, Any]
|
|
@@ -15,16 +14,78 @@ class ITFState:
|
|
|
15
14
|
|
|
16
15
|
|
|
17
16
|
@dataclass
|
|
18
|
-
class
|
|
17
|
+
class Trace:
|
|
19
18
|
"""An ITF trace as a Python object."""
|
|
20
19
|
|
|
21
20
|
meta: Dict[str, Any]
|
|
22
21
|
params: List[str]
|
|
23
22
|
vars: List[str]
|
|
24
|
-
states: List[
|
|
23
|
+
states: List[State]
|
|
25
24
|
loop: Optional[int]
|
|
26
25
|
|
|
27
26
|
|
|
27
|
+
class ImmutableList(list):
|
|
28
|
+
"""An immutable wrapper around list that supports hashing,
|
|
29
|
+
yet displays as a list."""
|
|
30
|
+
|
|
31
|
+
# frozenlist.FrozenList is what we want, but it does not display
|
|
32
|
+
# nicely in pretty-printing.
|
|
33
|
+
|
|
34
|
+
def __init__(self, items: Iterable[Any]):
|
|
35
|
+
super().__init__(items)
|
|
36
|
+
|
|
37
|
+
def __hash__(self) -> int: # type: ignore
|
|
38
|
+
return hash(tuple(self))
|
|
39
|
+
|
|
40
|
+
def _forbid_modification(self) -> NoReturn:
|
|
41
|
+
"""Forbid modification of the list."""
|
|
42
|
+
raise TypeError("This list is immutable and cannot be modified.")
|
|
43
|
+
|
|
44
|
+
def __setitem__(self, _key: Any, _value: Any) -> NoReturn:
|
|
45
|
+
self._forbid_modification()
|
|
46
|
+
|
|
47
|
+
def __delitem__(self, _key: Any) -> NoReturn:
|
|
48
|
+
self._forbid_modification()
|
|
49
|
+
|
|
50
|
+
def append(self, _value: Any) -> NoReturn:
|
|
51
|
+
self._forbid_modification()
|
|
52
|
+
|
|
53
|
+
def extend(self, _values: Iterable[Any]) -> NoReturn:
|
|
54
|
+
self._forbid_modification()
|
|
55
|
+
|
|
56
|
+
def insert(self, _index: SupportsIndex, _value: Any) -> None:
|
|
57
|
+
self._forbid_modification()
|
|
58
|
+
|
|
59
|
+
def pop(self, _index: SupportsIndex = -1) -> NoReturn:
|
|
60
|
+
self._forbid_modification()
|
|
61
|
+
|
|
62
|
+
def remove(self, _value: Any) -> NoReturn:
|
|
63
|
+
self._forbid_modification()
|
|
64
|
+
|
|
65
|
+
def clear(self) -> NoReturn:
|
|
66
|
+
self._forbid_modification()
|
|
67
|
+
|
|
68
|
+
def reverse(self) -> NoReturn:
|
|
69
|
+
self._forbid_modification()
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class ImmutableDict(frozendict):
|
|
73
|
+
"""A wrapper around frozendict that displays dictionaries as
|
|
74
|
+
`{k1: v_1, ..., k_n: v_n}`."""
|
|
75
|
+
|
|
76
|
+
def __new__(cls, items: Dict[str, Any]) -> Any:
|
|
77
|
+
return super().__new__(cls, items)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
ImmutableDict.__str__ = ( # type: ignore
|
|
81
|
+
dict.__str__
|
|
82
|
+
) # use the default dict representation in pretty-printing
|
|
83
|
+
|
|
84
|
+
ImmutableDict.__repr__ = ( # type: ignore
|
|
85
|
+
dict.__repr__
|
|
86
|
+
) # use the default dict representation in pretty-printing
|
|
87
|
+
|
|
88
|
+
|
|
28
89
|
@dataclass
|
|
29
90
|
class ITFUnserializable:
|
|
30
91
|
"""A placeholder for unserializable values."""
|
|
@@ -43,16 +104,24 @@ def value_from_json(val: Any) -> Any:
|
|
|
43
104
|
return frozenset(value_from_json(v) for v in val["#set"])
|
|
44
105
|
elif "#map" in val:
|
|
45
106
|
d = {value_from_json(k): value_from_json(v) for (k, v) in val["#map"]}
|
|
46
|
-
return
|
|
107
|
+
return ImmutableDict(d)
|
|
47
108
|
elif "#unserializable" in val:
|
|
48
109
|
return ITFUnserializable(value=val["#unserializable"])
|
|
49
110
|
else:
|
|
50
|
-
|
|
51
|
-
|
|
111
|
+
ks = val.keys()
|
|
112
|
+
if len(ks) == 2 and "tag" in ks and "value" in ks:
|
|
113
|
+
# This is a tagged union, e.g., {"tag": "Banana", "value": {...}}.
|
|
114
|
+
# Produce Banana(...)
|
|
115
|
+
union_type = namedtuple(val["tag"], val["value"].keys()) # type: ignore
|
|
116
|
+
return union_type(
|
|
117
|
+
**{k: value_from_json(v) for k, v in val["value"].items()}
|
|
118
|
+
)
|
|
119
|
+
else:
|
|
120
|
+
# This is a general record, e.g., {"field1": ..., "field2": ...}.
|
|
121
|
+
rec_type = namedtuple("Rec", val.keys()) # type: ignore
|
|
122
|
+
return rec_type(**{k: value_from_json(v) for k, v in val.items()})
|
|
52
123
|
elif isinstance(val, list):
|
|
53
|
-
|
|
54
|
-
lst.freeze() # make it immutable
|
|
55
|
-
return lst
|
|
124
|
+
return ImmutableList([value_from_json(v) for v in val])
|
|
56
125
|
else:
|
|
57
126
|
return val # int, str, bool
|
|
58
127
|
|
|
@@ -69,9 +138,11 @@ def value_to_json(val: Any) -> Any:
|
|
|
69
138
|
return {"#set": [value_to_json(v) for v in val]}
|
|
70
139
|
elif isinstance(val, dict):
|
|
71
140
|
return {"#map": [[value_to_json(k), value_to_json(v)] for k, v in val.items()]}
|
|
72
|
-
elif isinstance(val, list)
|
|
141
|
+
elif isinstance(val, list):
|
|
73
142
|
return [value_to_json(v) for v in val]
|
|
74
143
|
elif hasattr(val, "__dict__"):
|
|
144
|
+
# An object-like structure, e.g., a record, or a union.
|
|
145
|
+
# Note that we cannot distinguish between a record and a tagged union here.
|
|
75
146
|
return {k: value_to_json(v) for k, v in val.__dict__.items()}
|
|
76
147
|
elif isinstance(val, tuple) and hasattr(val, "_fields"):
|
|
77
148
|
# namedtuple
|
|
@@ -82,33 +153,33 @@ def value_to_json(val: Any) -> Any:
|
|
|
82
153
|
return ITFUnserializable(value=str(val))
|
|
83
154
|
|
|
84
155
|
|
|
85
|
-
def state_from_json(raw_state: Dict[str, Any]) ->
|
|
86
|
-
"""Deserialize a single
|
|
156
|
+
def state_from_json(raw_state: Dict[str, Any]) -> State:
|
|
157
|
+
"""Deserialize a single State from JSON"""
|
|
87
158
|
state_meta = raw_state["#meta"] if "#meta" in raw_state else {}
|
|
88
159
|
values = {k: value_from_json(v) for k, v in raw_state.items() if k != "#meta"}
|
|
89
|
-
return
|
|
160
|
+
return State(meta=state_meta, values=values)
|
|
90
161
|
|
|
91
162
|
|
|
92
|
-
def state_to_json(state:
|
|
93
|
-
"""Serialize a single
|
|
163
|
+
def state_to_json(state: State) -> Dict[str, Any]:
|
|
164
|
+
"""Serialize a single State to JSON"""
|
|
94
165
|
result = {"#meta": state.meta}
|
|
95
166
|
for k, v in state.values.items():
|
|
96
167
|
result[k] = value_to_json(v)
|
|
97
168
|
return result
|
|
98
169
|
|
|
99
170
|
|
|
100
|
-
def trace_from_json(data: Dict[str, Any]) ->
|
|
101
|
-
"""Deserialize
|
|
171
|
+
def trace_from_json(data: Dict[str, Any]) -> Trace:
|
|
172
|
+
"""Deserialize a Trace from JSON"""
|
|
102
173
|
meta = data["#meta"] if "#meta" in data else {}
|
|
103
174
|
params = data.get("params", [])
|
|
104
175
|
vars_ = data["vars"]
|
|
105
176
|
loop = data.get("loop", None)
|
|
106
177
|
states = [state_from_json(s) for s in data["states"]]
|
|
107
|
-
return
|
|
178
|
+
return Trace(meta=meta, params=params, vars=vars_, states=states, loop=loop)
|
|
108
179
|
|
|
109
180
|
|
|
110
|
-
def trace_to_json(trace:
|
|
111
|
-
"""Serialize
|
|
181
|
+
def trace_to_json(trace: Trace) -> Dict[str, Any]:
|
|
182
|
+
"""Serialize a Trace to JSON"""
|
|
112
183
|
result: Dict[str, Any] = {"#meta": trace.meta}
|
|
113
184
|
result["params"] = trace.params
|
|
114
185
|
result["vars"] = trace.vars
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: itf-py
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Python library to parse and emit Apalache/Quint traces in ITF JSON
|
|
5
5
|
Keywords: itf,trace,parser,tlaplus,apalache,quint
|
|
6
6
|
Author: Igor Konnov
|
|
@@ -10,17 +10,28 @@ Classifier: Programming Language :: Python :: 3
|
|
|
10
10
|
Classifier: Programming Language :: Python :: 3.12
|
|
11
11
|
Classifier: Programming Language :: Python :: 3.13
|
|
12
12
|
Requires-Dist: frozendict (>=2.4.6,<3.0.0)
|
|
13
|
-
Requires-Dist: frozenlist (>=1.7.0,<2.0.0)
|
|
14
13
|
Project-URL: Homepage, https://github.com/konnov/itf-py
|
|
15
14
|
Description-Content-Type: text/markdown
|
|
16
15
|
|
|
17
16
|
# ITF-py: Parser and Encoder for the ITF Trace Format
|
|
18
17
|
|
|
19
|
-
Python library to parse and emit Apalache ITF traces. Refer to
|
|
20
|
-
|
|
21
|
-
traces are emitted by [Apalache](https://github.com/apalache-mc/apalache) and
|
|
22
|
-
[Quint](https://github.com/informalsystems/quint).
|
|
18
|
+
Python library to parse and emit Apalache ITF traces. Refer to [ADR015][] for
|
|
19
|
+
the format. ITF traces are emitted by [Apalache][] and [Quint][].
|
|
23
20
|
|
|
24
21
|
**Intentionally minimalistic.** We keep this library intentionally minimalistic.
|
|
25
22
|
You can use it in your projects without worrying about pulling dozens of
|
|
26
|
-
dependencies.
|
|
23
|
+
dependencies. The package depends on `frozendict`.
|
|
24
|
+
|
|
25
|
+
**Why?** It's much more convenient to manipulate with trace data in an
|
|
26
|
+
interactive prompt, similar to SQL.
|
|
27
|
+
|
|
28
|
+
See usage examples on the [itf-py][] page on Github.
|
|
29
|
+
|
|
30
|
+
**Alternatives.** If you need to deserialize/serialize ITF traces in Rust, check
|
|
31
|
+
[itf-rs][].
|
|
32
|
+
|
|
33
|
+
[ADR015]: https://apalache-mc.org/docs/adr/015adr-trace.html
|
|
34
|
+
[Apalache]: https://github.com/apalache-mc/apalache
|
|
35
|
+
[Quint]: https://github.com/informalsystems/quint
|
|
36
|
+
[itf-rs]: https://github.com/informalsystems/itf-rs
|
|
37
|
+
[itf-py]: https://github.com/konnov/itf-py
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
itf_py/__init__.py,sha256=Mt8_Bce40YIC0PRZik2MN-IH7Ro6GsK69EuQUsU-leo,421
|
|
2
|
+
itf_py/itf.py,sha256=utBjWrN_rO4AGOtSBsD0Wa-nLH9OgMnlGqcpCuULCio,6289
|
|
3
|
+
itf_py-0.4.0.dist-info/METADATA,sha256=zcGdqS3KhL8DxiL_72HCBkI_1p6ZW2I8N_7pg-raD04,1465
|
|
4
|
+
itf_py-0.4.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
|
5
|
+
itf_py-0.4.0.dist-info/RECORD,,
|
itf_py-0.2.2.dist-info/RECORD
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
itf_py/__init__.py,sha256=hGN4R5969esBx4T9Cy5sIO7EP3QWiWra3_DhkmbKaso,433
|
|
2
|
-
itf_py/itf.py,sha256=tiZR3g3-HRoDNk7eH26M7hxVyK3tMVGXhxsUxw-PCrQ,3934
|
|
3
|
-
itf_py-0.2.2.dist-info/METADATA,sha256=h-fmihrktQZuM9g03H_3QYwWPAtJYHQWBO1FAWKWl-4,1085
|
|
4
|
-
itf_py-0.2.2.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
|
5
|
-
itf_py-0.2.2.dist-info/RECORD,,
|
|
File without changes
|