itf-py 0.1.0__tar.gz
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-0.1.0/PKG-INFO +22 -0
- itf_py-0.1.0/README.md +9 -0
- itf_py-0.1.0/pyproject.toml +83 -0
- itf_py-0.1.0/src/itf_py/__init__.py +10 -0
- itf_py-0.1.0/src/itf_py/itf.py +107 -0
itf_py-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: itf-py
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python library to parse and emit Apalache ITF traces
|
|
5
|
+
Author: Igor Konnov
|
|
6
|
+
Author-email: igor@konnov.phd
|
|
7
|
+
Requires-Python: >=3.12,<4.0
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
11
|
+
Project-URL: Homepage, https://github.com/konnov/itf-py
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
# ITF-py: Parser and Encoder for the ITF Trace Format
|
|
15
|
+
|
|
16
|
+
Python library to parse and emit Apalache ITF traces. Refer to
|
|
17
|
+
[ADR015](https://apalache-mc.org/docs/adr/015adr-trace.html) for the format. ITF
|
|
18
|
+
traces are emitted by [Apalache](https://github.com/apalache-mc/apalache) and
|
|
19
|
+
[Quint](https://github.com/informalsystems/quint).
|
|
20
|
+
|
|
21
|
+
**No fluff.** We keep this library intentionally minimalistic. You can use it in
|
|
22
|
+
your projects without worrying about pulling dozens of dependencies.
|
itf_py-0.1.0/README.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# ITF-py: Parser and Encoder for the ITF Trace Format
|
|
2
|
+
|
|
3
|
+
Python library to parse and emit Apalache ITF traces. Refer to
|
|
4
|
+
[ADR015](https://apalache-mc.org/docs/adr/015adr-trace.html) for the format. ITF
|
|
5
|
+
traces are emitted by [Apalache](https://github.com/apalache-mc/apalache) and
|
|
6
|
+
[Quint](https://github.com/informalsystems/quint).
|
|
7
|
+
|
|
8
|
+
**No fluff.** We keep this library intentionally minimalistic. You can use it in
|
|
9
|
+
your projects without worrying about pulling dozens of dependencies.
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "itf-py"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Python library to parse and emit Apalache ITF traces"
|
|
5
|
+
homepage = "https://github.com/konnov/itf-py"
|
|
6
|
+
authors = ["Igor Konnov <igor@konnov.phd>"]
|
|
7
|
+
readme = "README.md"
|
|
8
|
+
packages = [{include = "itf_py", from = "src"}]
|
|
9
|
+
|
|
10
|
+
[tool.poetry.dependencies]
|
|
11
|
+
python = "^3.12"
|
|
12
|
+
|
|
13
|
+
[tool.poetry.group.dev.dependencies]
|
|
14
|
+
pytest = "^8.0.0"
|
|
15
|
+
pytest-cov = "^5.0.0"
|
|
16
|
+
black = "^24.0.0"
|
|
17
|
+
isort = "^5.13.0"
|
|
18
|
+
flake8 = "^7.0.0"
|
|
19
|
+
mypy = "^1.8.0"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
[build-system]
|
|
23
|
+
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
|
24
|
+
build-backend = "poetry.core.masonry.api"
|
|
25
|
+
|
|
26
|
+
[tool.black]
|
|
27
|
+
line-length = 88
|
|
28
|
+
target-version = ['py312']
|
|
29
|
+
include = '\.pyi?$'
|
|
30
|
+
extend-exclude = '''
|
|
31
|
+
/(
|
|
32
|
+
# directories
|
|
33
|
+
\.eggs
|
|
34
|
+
| \.git
|
|
35
|
+
| \.hg
|
|
36
|
+
| \.mypy_cache
|
|
37
|
+
| \.tox
|
|
38
|
+
| \.venv
|
|
39
|
+
| build
|
|
40
|
+
| dist
|
|
41
|
+
)/
|
|
42
|
+
'''
|
|
43
|
+
|
|
44
|
+
[tool.isort]
|
|
45
|
+
profile = "black"
|
|
46
|
+
multi_line_output = 3
|
|
47
|
+
line_length = 88
|
|
48
|
+
known_first_party = ["itf_py"]
|
|
49
|
+
|
|
50
|
+
[tool.mypy]
|
|
51
|
+
python_version = "3.12"
|
|
52
|
+
warn_return_any = true
|
|
53
|
+
warn_unused_configs = true
|
|
54
|
+
disallow_untyped_defs = true
|
|
55
|
+
disallow_incomplete_defs = true
|
|
56
|
+
check_untyped_defs = true
|
|
57
|
+
disallow_untyped_decorators = true
|
|
58
|
+
no_implicit_optional = true
|
|
59
|
+
warn_redundant_casts = true
|
|
60
|
+
warn_unused_ignores = true
|
|
61
|
+
warn_no_return = true
|
|
62
|
+
warn_unreachable = true
|
|
63
|
+
strict_equality = true
|
|
64
|
+
|
|
65
|
+
[tool.pytest.ini_options]
|
|
66
|
+
testpaths = ["tests"]
|
|
67
|
+
python_files = ["test_*.py"]
|
|
68
|
+
python_classes = ["Test*"]
|
|
69
|
+
python_functions = ["test_*"]
|
|
70
|
+
addopts = "--strict-markers --strict-config --verbose"
|
|
71
|
+
|
|
72
|
+
[tool.flake8]
|
|
73
|
+
max-line-length = 88
|
|
74
|
+
extend-ignore = ["E203", "W503"]
|
|
75
|
+
exclude = [
|
|
76
|
+
".git",
|
|
77
|
+
"__pycache__",
|
|
78
|
+
".venv",
|
|
79
|
+
".eggs",
|
|
80
|
+
"*.egg",
|
|
81
|
+
"dist",
|
|
82
|
+
"build",
|
|
83
|
+
]
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from types import SimpleNamespace
|
|
3
|
+
from typing import Any, Dict, List, Optional
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass
|
|
7
|
+
class ITFState:
|
|
8
|
+
"""A single state in an ITF trace as a Python object."""
|
|
9
|
+
|
|
10
|
+
meta: Dict[str, Any]
|
|
11
|
+
values: Dict[str, Any]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class ITFTrace:
|
|
16
|
+
"""An ITF trace as a Python object."""
|
|
17
|
+
|
|
18
|
+
meta: Dict[str, Any]
|
|
19
|
+
params: List[str]
|
|
20
|
+
vars: List[str]
|
|
21
|
+
states: List[ITFState]
|
|
22
|
+
loop: Optional[int]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class ITFUnserializable:
|
|
27
|
+
"""A placeholder for unserializable values."""
|
|
28
|
+
|
|
29
|
+
value: str
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def value_from_json(val: Any) -> Any:
|
|
33
|
+
"""Deserialize a Python value from JSON"""
|
|
34
|
+
if isinstance(val, dict):
|
|
35
|
+
if "#bigint" in val:
|
|
36
|
+
return int(val["#bigint"])
|
|
37
|
+
elif "#tup" in val:
|
|
38
|
+
return tuple(value_from_json(v) for v in val["#tup"])
|
|
39
|
+
elif "#set" in val:
|
|
40
|
+
return frozenset(value_from_json(v) for v in val["#set"])
|
|
41
|
+
elif "#map" in val:
|
|
42
|
+
return {value_from_json(k): value_from_json(v) for (k, v) in val["#map"]}
|
|
43
|
+
elif "#unserializable" in val:
|
|
44
|
+
return ITFUnserializable(value=val["#unserializable"])
|
|
45
|
+
else:
|
|
46
|
+
return SimpleNamespace(**{k: value_from_json(v) for k, v in val.items()})
|
|
47
|
+
elif isinstance(val, list):
|
|
48
|
+
return [value_from_json(v) for v in val]
|
|
49
|
+
else:
|
|
50
|
+
return val # int, str, bool
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def value_to_json(val: Any) -> Any:
|
|
54
|
+
"""Serialize a Python value into JSON"""
|
|
55
|
+
if isinstance(val, bool):
|
|
56
|
+
return val
|
|
57
|
+
elif isinstance(val, int):
|
|
58
|
+
return {"#bigint": str(val)}
|
|
59
|
+
elif isinstance(val, tuple):
|
|
60
|
+
return {"#tup": [value_to_json(v) for v in val]}
|
|
61
|
+
elif isinstance(val, frozenset):
|
|
62
|
+
return {"#set": [value_to_json(v) for v in val]}
|
|
63
|
+
elif isinstance(val, dict):
|
|
64
|
+
return {"#map": [[value_to_json(k), value_to_json(v)] for k, v in val.items()]}
|
|
65
|
+
elif isinstance(val, list):
|
|
66
|
+
return [value_to_json(v) for v in val]
|
|
67
|
+
elif hasattr(val, "__dict__"):
|
|
68
|
+
return {k: value_to_json(v) for k, v in val.__dict__.items()}
|
|
69
|
+
elif isinstance(val, str):
|
|
70
|
+
return val
|
|
71
|
+
else:
|
|
72
|
+
return ITFUnserializable(value=str(val))
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def state_from_json(raw_state: Dict[str, Any]) -> ITFState:
|
|
76
|
+
"""Deserialize a single ITFState from JSON"""
|
|
77
|
+
state_meta = raw_state["#meta"] if "#meta" in raw_state else {}
|
|
78
|
+
values = {k: value_from_json(v) for k, v in raw_state.items() if k != "#meta"}
|
|
79
|
+
return ITFState(meta=state_meta, values=values)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def state_to_json(state: ITFState) -> Dict[str, Any]:
|
|
83
|
+
"""Serialize a single ITFState to JSON"""
|
|
84
|
+
result = {"#meta": state.meta}
|
|
85
|
+
for k, v in state.values.items():
|
|
86
|
+
result[k] = value_to_json(v)
|
|
87
|
+
return result
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def trace_from_json(data: Dict[str, Any]) -> ITFTrace:
|
|
91
|
+
"""Deserialize an ITFTrace from JSON"""
|
|
92
|
+
meta = data["#meta"] if "#meta" in data else {}
|
|
93
|
+
params = data.get("params", [])
|
|
94
|
+
vars_ = data["vars"]
|
|
95
|
+
loop = data.get("loop", None)
|
|
96
|
+
states = [state_from_json(s) for s in data["states"]]
|
|
97
|
+
return ITFTrace(meta=meta, params=params, vars=vars_, states=states, loop=loop)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def trace_to_json(trace: ITFTrace) -> Dict[str, Any]:
|
|
101
|
+
"""Serialize an ITFTrace to JSON"""
|
|
102
|
+
result: Dict[str, Any] = {"#meta": trace.meta}
|
|
103
|
+
result["params"] = trace.params
|
|
104
|
+
result["vars"] = trace.vars
|
|
105
|
+
result["loop"] = trace.loop
|
|
106
|
+
result["states"] = [state_to_json(s) for s in trace.states]
|
|
107
|
+
return result
|