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
- ITFState,
7
- ITFTrace,
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
- "ITFState",
19
- "ITFTrace",
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 ITFState:
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 ITFTrace:
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[ITFState]
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 frozendict(d) # immutable dictionary
107
+ return ImmutableDict(d)
47
108
  elif "#unserializable" in val:
48
109
  return ITFUnserializable(value=val["#unserializable"])
49
110
  else:
50
- tup_type = namedtuple("ITFRecord", val.keys()) # type: ignore
51
- return tup_type(**{k: value_from_json(v) for k, v in val.items()})
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
- lst = FrozenList([value_from_json(v) for v in val])
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) or isinstance(val, FrozenList):
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]) -> ITFState:
86
- """Deserialize a single ITFState from JSON"""
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 ITFState(meta=state_meta, values=values)
160
+ return State(meta=state_meta, values=values)
90
161
 
91
162
 
92
- def state_to_json(state: ITFState) -> Dict[str, Any]:
93
- """Serialize a single ITFState to JSON"""
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]) -> ITFTrace:
101
- """Deserialize an ITFTrace from JSON"""
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 ITFTrace(meta=meta, params=params, vars=vars_, states=states, loop=loop)
178
+ return Trace(meta=meta, params=params, vars=vars_, states=states, loop=loop)
108
179
 
109
180
 
110
- def trace_to_json(trace: ITFTrace) -> Dict[str, Any]:
111
- """Serialize an ITFTrace to JSON"""
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.2.2
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
- [ADR015](https://apalache-mc.org/docs/adr/015adr-trace.html) for the format. ITF
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,,
@@ -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