Skip to content

Commit f5f6c91

Browse files
Rewrite README: add uv install, split design decisions from limitations, add acknowledgements
1 parent f0db7a6 commit f5f6c91

1 file changed

Lines changed: 50 additions & 60 deletions

File tree

README.md

Lines changed: 50 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,20 @@
11
# yamltrip
22

3-
A round-tripping YAML library for Python. Edit YAML files while preserving
4-
comments, formatting, and key ordering.
3+
[![usethis](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/usethis-python/usethis-python/main/assets/badge/v1.json)](https://github.com/usethis-python/yamltrip) [![PyPI Version](https://img.shields.io/pypi/v/yamltrip.svg)](https://pypi.python.org/pypi/yamltrip) [![PyPI License](https://img.shields.io/pypi/l/yamltrip.svg)](https://github.com/usethis-python/yamltrip?tab=MIT-1-ov-file) [![PyPI Supported Versions](https://img.shields.io/pypi/pyversions/yamltrip.svg)](https://pypi.python.org/pypi/yamltrip)
54

6-
Built on [tree-sitter-yaml](https://github.com/tree-sitter-grammars/tree-sitter-yaml)
7-
via the [yamlpath](https://crates.io/crates/yamlpath) and
8-
[yamlpatch](https://crates.io/crates/yamlpatch) Rust crates, with Python
9-
bindings through [PyO3](https://pyo3.rs).
5+
Edit YAML files from Python, while respecting format and comments during the round-trip.
6+
7+
Built on [tree-sitter-yaml](https://github.com/tree-sitter-grammars/tree-sitter-yaml) via the [yamlpath](https://crates.io/crates/yamlpath) and [yamlpatch](https://crates.io/crates/yamlpatch) Rust crates.
108

119
## Installation
1210

13-
```
14-
pip install yamltrip
15-
```
11+
```console
12+
# With uv
13+
$ uv add yamltrip
1614

17-
Requires Python 3.10+. Distributed as pre-built wheels (no Rust toolchain
18-
needed at install time).
15+
# With pip
16+
$ pip install yamltrip
17+
```
1918

2019
## Quick Start
2120

@@ -27,7 +26,7 @@ doc = yamltrip.loads("name: Alice\nage: 30")
2726
print(doc["name"]) # "Alice"
2827
print("name" in doc) # True
2928

30-
# Immutable mutationseach returns a new Document
29+
# Immutable mutations: each call returns a new Document
3130
doc2 = doc.replace("age", value=31)
3231
doc3 = doc2.add(key="city", value="Portland")
3332
print(doc3.dumps())
@@ -41,17 +40,17 @@ with yamltrip.edit("config.yml") as editor:
4140

4241
## API Overview
4342

44-
### Top-level functions
43+
### Top-level function
4544

46-
| Function | Description |
47-
|---|---|
48-
| `yamltrip.loads(source)` | Parse a YAML string into a `Document` |
49-
| `yamltrip.load(path)` | Read a YAML file into a `Document` |
50-
| `yamltrip.edit(path)` | Open a YAML file for editing (context manager) |
45+
| Function | Description |
46+
| -------------------------- | ---------------------------------------------- |
47+
| `yamltrip.loads(source)` | Parse a YAML string into a `Document` |
48+
| `yamltrip.load(path)` | Read a YAML file into a `Document` |
49+
| `yamltrip.edit(path)` | Open a YAML file for editing (context manager) |
5150

5251
### Document (immutable)
5352

54-
Each mutation method returns a **new** `Document` — the original is never
53+
Every mutation method returns a **new** `Document`. The original is never
5554
modified.
5655

5756
```python
@@ -80,8 +79,8 @@ doc.dump("output.yml") # write to file
8079

8180
### Editor (mutable context manager)
8281

83-
Wraps `Document` with the same methods, but mutates in place and writes back
84-
to disk on successful context exit:
82+
Wraps `Document` with the same mutation methods, but applies changes in place
83+
and writes back to disk when the context exits cleanly:
8584

8685
```python
8786
with yamltrip.edit("config.yml") as ed:
@@ -96,53 +95,44 @@ with yamltrip.edit("config.yml") as ed:
9695

9796
All yamltrip errors inherit from `YAMLTripError`:
9897

99-
- **`ParseError`**YAML input cannot be parsed
100-
- **`QueryError`**path not found during lookup
101-
- **`PatchError`**mutation operation failed
102-
- **`KeyExistsError`**`add()` target already exists
103-
- **`KeyMissingError`**`replace()` target doesn't exist
98+
- **`ParseError`**: YAML input cannot be parsed.
99+
- **`QueryError`**: path not found during lookup.
100+
- **`PatchError`**: mutation operation failed.
101+
- **`KeyExistsError`**: `add()` target already exists.
102+
- **`KeyMissingError`**: `replace()` target does not exist.
104103

105104
## Limitations
106105

107-
- **Multi-document YAML streams** (`---` separated) are not supported —
108-
behavior is undefined.
106+
- **Multi-document YAML streams** (`---` separated) are not supported.
109107
- **YAML tags** (`!!omap`, `!!set`, `!!merge`, custom tags) are not
110108
interpreted.
111109
- **Anchors and aliases** (`&anchor` / `*alias`) are detected
112110
(`doc.has_anchors()`) but not resolved during value extraction.
113-
- **No custom Python class serialization** — values are converted to/from
114-
basic Python types (`str`, `int`, `float`, `bool`, `None`, `list`, `dict`).
115-
- **UTF-8 only** — files must be UTF-8 encoded. Other encodings raise
116-
`ParseError`.
117-
- **Non-finite floats rejected**`float("inf")`, `float("-inf")`, and
118-
`float("nan")` cannot be serialized into YAML values.
119-
- **Integer keys cannot create structures**`upsert()` with integer path
111+
- **Large integers may lose precision.** YAML integers outside the signed
112+
64-bit range (i64) may become `float` during deserialization.
113+
- **Editor write-back is not atomic.** `Editor` detects external file changes
114+
between enter and exit, but the check-then-write is racy. Do not use it
115+
with concurrent writers.
116+
- **Line endings preserved as-is.** No CRLF/LF normalization. Mixed line
117+
endings pass through unchanged.
118+
119+
## Design Decisions
120+
121+
- **No custom Python class serialization.** Values convert to/from
122+
`str`, `int`, `float`, `bool`, `None`, `list`, and `dict` only.
123+
- **UTF-8 only.** Other encodings raise `ParseError`.
124+
- **Non-finite floats rejected.** `float("inf")`, `float("-inf")`, and
125+
`float("nan")` cannot be serialized.
126+
- **Integer keys cannot create structures.** `upsert()` with integer path
120127
components can update existing sequence entries but cannot create new
121-
intermediate structures. Only string keys can create new mappings.
122-
- **Large integers may lose precision** — YAML integers outside the signed
123-
64-bit range (i64) may be silently converted to `float` during
124-
deserialization.
125-
- **No negative sequence indices** — sequence indexing uses non-negative
126-
integers only. Python-style negative indices are not supported.
127-
- **Editor write-back is best-effort**`Editor` detects external file
128-
modifications between enter and exit, but the check-then-write is not
129-
atomic. It is not suitable for environments with concurrent writers.
130-
- **Line endings preserved as-is** — no CRLF/LF normalization is performed.
131-
Mixed line endings in the input are passed through unchanged.
132-
133-
## Development
134-
135-
```bash
136-
# Install dev dependencies
137-
uv sync
138-
139-
# Build and test
140-
uv run pytest
141-
142-
# Lint
143-
uv run ruff check
144-
uv run ruff format --check
145-
```
128+
intermediate mappings. Only string keys create new mappings.
129+
- **No negative sequence indices.** Python-style negative indexing is not
130+
supported.
131+
132+
## Acknowledgements
133+
134+
yamltrip depends entirely on the [yamlpath](https://github.com/zizmorcore/zizmor/tree/main/crates/yamlpath) and [yamlpatch](https://github.com/zizmorcore/zizmor/tree/main/crates/yamlpatch) Rust crates for format-preserving YAML parsing and patching. These libraries use tree-sitter to query and modify YAML source text without discarding comments, whitespace, or key ordering. All of the core logic in yamltrip
135+
passes through them. Thanks to [William Woodruff](https://github.com/woodruffw) for creating and maintaining both crates as part of [zizmor](https://github.com/zizmorcore/zizmor).
146136

147137
## License
148138

0 commit comments

Comments
 (0)