Skip to content

Commit 9fa5911

Browse files
committed
refactor umbi.io -> umbi.tar with improved API
- rename umbi.io to umbi.tar - umbi.tar: flip optional parameter in read_file (new default: mandatory) - add tar module tests - fix circular import in ats module - improve docstrings
1 parent 0717fe7 commit 9fa5911

50 files changed

Lines changed: 920 additions & 400 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 47 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,26 @@
11
# umbi
22

3-
Library for input/output of transition systems in a *unified Markov binary (UMB)* format. See the [format specification](https://github.com/pmc-tools/umb/) for details.
3+
[![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue)](https://www.python.org/)
4+
[![License MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE)
5+
[![PyPI](https://img.shields.io/pypi/v/umbi)](https://pypi.org/project/umbi/)
6+
[![Latest Tag](https://img.shields.io/github/tag/pmc-tools/umbi.svg)](https://github.com/pmc-tools/umbi/tags)
7+
[![Build Status](https://img.shields.io/github/actions/workflow/status/pmc-tools/umbi/test-build.yml)](https://github.com/pmc-tools/umbi/actions)
48

5-
## Installation:
6-
7-
(optional) create and activate a python environment:
9+
Library for input/output of annotated transition systems (ATSs) in a *unified Markov binary (UMB)* format. See the [format specification](https://github.com/pmc-tools/umb/) for details.
810

9-
```bash
10-
python -m venv .venv
11-
source .venv/bin/activate
12-
```
11+
## Installation:
1312

14-
Install `umbi` via
1513
```bash
1614
pip install umbi
1715
```
1816

1917
## Quick start
2018

21-
Read a umbfile into an `ExplicitAts` object, modify initial states, and write it back:
19+
A short example where we read a umbfile into an [`ExplicitAts`](umbi/ats/explicit_ats.py) object, modify initial states, and write it back:
2220

2321
```python
2422
import umbi
25-
ats : ExplicitAts = umbi.ats.read("in.umb")
23+
ats = umbi.ats.read("in.umb")
2624
ats.set_initial_states([ats.num_states - 1])
2725
umbi.ats.write(ats, "out.umb")
2826
```
@@ -31,23 +29,23 @@ More examples can be found in the [./examples](./examples) folder.
3129

3230
## API
3331

34-
`umbi` offers three levels of abstraction for working with UMB files:
32+
`umbi` offers multiple levels of abstraction for working with UMB files:
3533

36-
**[`TarFile`](umbi/io/tar_file.py) and [`TarCoder`](umbi/io/tar_coder.py)** - Low-level access to tarfile contents.
34+
**[`TarFile`](umbi/tar/tar_file.py) and [`TarCoder`](umbi/tar/tar_coder.py)** - low-level access to umbfile contents.
3735

38-
**[`ExplicitUmb`](umbi/umb/explicit_umb.py)** - In-memory representation of a typical umbfile. Attributes are standard Python objects (lists, dicts, dataclasses) providing a deserialized view of the file contents.
36+
**[`ExplicitUmb`](umbi/umb/explicit_umb.py)** - in-memory representation of a typical umbfile. Attributes are standard Python objects (lists, dicts, dataclasses) providing a deserialized view of the file contents.
3937

40-
**[`ExplicitAts`](umbi/ats/explicit_ats.py)** - Format-agnostic abstraction for annotated transition systems (states, transitions, annotations). Recommended for most use cases: easiest to use programmatically and remains stable across UMB format changes.
38+
**[`ExplicitAts`](umbi/ats/explicit_ats.py)** - format-agnostic abstraction for annotated transition systems (states, transitions, annotations). Recommended for most use cases: easiest to use programmatically and remains stable across UMB format changes.
4139

4240
## CLI
4341

4442
`umbi` provides a basic CLI for umbfile manipulation.
4543

4644
**Options:**
47-
- `--import-umb <path>` - Import .umb file as `ExplicitUmb`
48-
- `--import-ats <path>` - Import .umb file as `ExplicitAts`
49-
- `--export <path>` - Export to .umb file (requires `--import-umb` or `--import-ats`)
50-
- `--log-level <LEVEL>` - Set logging level: `DEBUG`, `INFO` (default), `WARNING`, `ERROR`, `CRITICAL`
45+
- `--import-umb <path>` - import umbfile as `ExplicitUmb`
46+
- `--import-ats <path>` - import umbfile as `ExplicitAts`
47+
- `--export <path>` - export to umbfile (requires `--import-umb` or `--import-ats`)
48+
- `--log-level <LEVEL>` - set logging level: `DEBUG`, `INFO` (default), `WARNING`, `ERROR`, `CRITICAL`
5149

5250
**Example:**
5351
```bash
@@ -56,6 +54,13 @@ umbi --import-umb input.umb --export output.umb --log-level DEBUG
5654

5755
## Development
5856

57+
(optional) create and activate a python environment:
58+
59+
```bash
60+
python -m venv .venv
61+
source .venv/bin/activate
62+
```
63+
5964
### Setup
6065

6166
Install development dependencies:
@@ -64,6 +69,21 @@ Install development dependencies:
6469
pip install .[dev]
6570
```
6671

72+
### Testing
73+
74+
Run the test suite with [pytest](https://pytest.org/):
75+
76+
```bash
77+
python -m pytest # run all tests
78+
python -m pytest tests/tar/ # run specific test directory
79+
python -m pytest -k test_name # run tests matching pattern
80+
```
81+
82+
Current test coverage:
83+
- **binary** - serialization and binary data handling
84+
- **datatypes** - data type definitions and conversions
85+
- **tar** - tarfile I/O and utilities
86+
6787
### Code Quality
6888

6989
Pre-commit hooks automatically run code quality checks before each commit. Configuration: [.pre-commit-config.yaml](.pre-commit-config.yaml)
@@ -85,8 +105,8 @@ Individual tools can be run manually:
85105
**[Ruff](https://github.com/astral-sh/ruff)** -- Code formatting and linting. Config: [pyproject.toml](pyproject.toml) (`[tool.ruff]`)
86106
```bash
87107
ruff check . # check for issues
88-
ruff format . # format code
89108
ruff check . --fix # check and fix
109+
ruff format . # format code
90110
```
91111

92112
**[Pyright](https://github.com/microsoft/pyright)** -- Static type checking. Config: [pyproject.toml](pyproject.toml) (`[tool.pyright]`)
@@ -95,24 +115,14 @@ pyright # check entire project
95115
pyright umbi/ # check specific directory
96116
```
97117

98-
#### Lockfiles
99-
100-
Dependencies are pinned in the [uv.lock](uv.lock) lockfile for reproducible builds. To update the lockfile:
101-
102-
```bash
103-
uv lock
104-
```
105-
106118
### Release
107119

108-
New versions are published to PyPI via the [release workflow](.github/workflows/release.yml). The workflow is triggered automatically when:
109-
- A new version tag is pushed (format: `v*.*.*`)
110-
- The [bump version workflow](.github/workflows/bump.yml) completes successfully
111-
112-
Alternatively, the workflow can be triggered manually via GitHub Actions.
120+
Dependencies are pinned in the [uv.lock](uv.lock) lockfile for reproducible builds. New versions are published to [PyPI](https://pypi.org/project/umbi/) via the [release workflow](.github/workflows/release.yml). The workflow is triggered automatically when:
121+
- a new version tag is pushed (format: `v*.*.*`)
122+
- the [bump version workflow](.github/workflows/bump.yml) completes successfully (can be run manually via GitHub actions)
113123

114124
The release workflow:
115-
1. Updates the [uv.lock](uv.lock) lockfile to reflect any dependency changes
116-
2. Builds the distribution packages
117-
3. Publishes to PyPI via trusted publishing
118-
4. Updates the stable branch pointer to track the latest release
125+
1. updates the [uv.lock](uv.lock) lockfile to reflect any dependency changes
126+
2. builds the distribution packages
127+
3. publishes to PyPI via trusted publishing
128+
4. updates the stable branch pointer to track the latest release

examples/demo/ats.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"""Demonstration of umbfile manipulation via ExplicitAts."""
33

44
import click
5+
56
import umbi
67

78

examples/demo/tar.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"""Demonstration of tarfile manipulation via TarCoder."""
33

44
import click
5+
56
import umbi
67

78

examples/demo/umb.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"""Demonstration of umbfile manipulation via ExplicitUmb."""
33

44
import click
5+
56
import umbi
67

78

examples/misc/ats_smg_to_prism.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
import pathlib
99

1010
import umbi
11-
import umbi.ats.examples
1211

1312
log = logging.getLogger(__name__)
1413

@@ -33,6 +32,7 @@ def ats_smg_to_prism(ats: umbi.ats.ExplicitAts, output_path: pathlib.Path) -> No
3332
f.write(f"global state: [0..{ats.num_states - 1}];\n\n")
3433

3534
# player declarations
35+
player_to_states = None
3636
if ats.num_players > 0:
3737
# group states by player
3838
player_to_states = {}
@@ -60,6 +60,7 @@ def ats_smg_to_prism(ats: umbi.ats.ExplicitAts, output_path: pathlib.Path) -> No
6060

6161
# group states by player for module generation
6262
if ats.num_players > 0:
63+
assert player_to_states is not None
6364
for player in sorted(player_to_states.keys()):
6465
f.write(f"module player{player}\n")
6566

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,9 @@ exclude = [
7373
]
7474
target-version = "py311"
7575

76+
[tool.ruff.lint]
77+
select = ["I"] # enable isort (import sorting) rules
78+
7679
[tool.pyright]
7780
include = [
7881
"umbi",

tests/binary/test_numeric_primitive.py

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,28 @@
11
"""Tests for umbi.binary.numeric_primitive module."""
22

3-
import pytest
43
from fractions import Fraction
4+
5+
import pytest
56
from bitstring import BitArray
67

78
from umbi.binary.numeric_primitive import (
8-
bytes_to_double,
9-
double_to_bytes,
109
bits_to_double,
11-
double_to_bits,
12-
bytes_to_integer,
13-
integer_to_bytes,
1410
bits_to_integer,
15-
integer_to_bits,
16-
bytes_to_rational,
17-
rational_to_bytes,
11+
bits_to_numeric_primitive,
1812
bits_to_rational,
19-
rational_to_bits,
20-
normalize_rational,
13+
bytes_to_double,
14+
bytes_to_integer,
2115
bytes_to_numeric_primitive,
22-
numeric_primitive_to_bytes,
23-
bits_to_numeric_primitive,
16+
bytes_to_rational,
17+
double_to_bits,
18+
double_to_bytes,
19+
integer_to_bits,
20+
integer_to_bytes,
21+
normalize_rational,
2422
numeric_primitive_to_bits,
23+
numeric_primitive_to_bytes,
24+
rational_to_bits,
25+
rational_to_bytes,
2526
)
2627
from umbi.datatypes import NumericPrimitiveType
2728

tests/binary/test_primitive.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@
44
from bitstring import BitArray
55

66
from umbi.binary.primitive import (
7-
bool_to_bits,
87
bits_to_bool,
8+
bits_to_string,
9+
bool_to_bits,
910
bool_to_bytes,
1011
bytes_to_bool,
1112
bytes_to_string,
12-
string_to_bytes,
1313
string_to_bits,
14-
bits_to_string,
14+
string_to_bytes,
1515
)
1616

1717

tests/binary/test_scalar.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
"""Tests for umbi.binary.scalar module."""
22

3-
import pytest
43
from fractions import Fraction
4+
5+
import pytest
56
from bitstring import BitArray
67

7-
from umbi.binary import scalar_to_bytes, bytes_to_scalar
8+
from umbi.binary import bytes_to_scalar, scalar_to_bytes
89
from umbi.binary.scalar import bits_to_scalar, scalar_to_bits
910
from umbi.datatypes import (
10-
PrimitiveType,
11-
NumericPrimitiveType,
1211
Interval,
1312
IntervalType,
13+
NumericPrimitiveType,
14+
PrimitiveType,
1415
)
1516

1617

tests/binary/test_sequence.py

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,15 @@
33
import pytest
44

55
from umbi.binary import (
6+
BOOL1,
7+
UINT32,
8+
SizedType,
69
bytes_to_vector,
7-
vector_to_bytes,
810
bytes_with_ranges_to_vector,
11+
vector_to_bytes,
912
vector_to_bytes_with_ranges,
10-
SizedType,
11-
BOOL1,
12-
UINT32,
1313
)
14-
from umbi.datatypes import PrimitiveType, NumericPrimitiveType
15-
from umbi.io.utils import csr_to_ranges
14+
from umbi.datatypes import NumericPrimitiveType, PrimitiveType
1615

1716

1817
class TestBytesToBitvector:
@@ -141,8 +140,6 @@ def test_vector_to_bytes_with_ranges(self):
141140
bytes_data, chunk_csr = vector_to_bytes_with_ranges(values, sized_type, little_endian=True)
142141
assert chunk_csr is not None
143142
assert len(bytes_data) == 12 # 3 * 4 bytes
144-
chunk_ranges = csr_to_ranges(chunk_csr)
145-
assert len(chunk_ranges) == 3 # 3 values
146143

147144
def test_ranges_roundtrip(self):
148145
"""Test round-trip with ranges."""
@@ -151,7 +148,8 @@ def test_ranges_roundtrip(self):
151148

152149
bytes_data, chunk_csr = vector_to_bytes_with_ranges(original, sized_type, little_endian=True)
153150
assert chunk_csr is not None
154-
chunk_ranges = csr_to_ranges(chunk_csr)
151+
# For 3 values of 4 bytes each: ranges are (0,4), (4,8), (8,12)
152+
chunk_ranges = [(0, 4), (4, 8), (8, 12)]
155153
result = bytes_with_ranges_to_vector(bytes_data, sized_type.type, chunk_ranges, little_endian=True)
156154

157155
assert result == original

0 commit comments

Comments
 (0)