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 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,10 @@
1
+ """
2
+ Python library to parse and emit Apalache ITF traces.
3
+ """
4
+
5
+ from .itf import ITFState
6
+
7
+ __version__ = "0.1.0"
8
+ __all__ = [
9
+ "ITFState",
10
+ ]
@@ -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