PyTOON
======
PyTOON is a Python port of the excellent [`toon`](https://github.com/johannschopplich/toon) project by Johann Schopplich. It converts arbitrary Python data structures into the same concise, human-readable text representation produced by the original TypeScript encoder.
The goal of this repository is feature parity with the upstream project while providing a first-class experience for Python applications and tooling.
Contents
--------
- [Features](#features)
- [Installation](#installation)
- [Quick Start](#quick-start)
- [Normalization Reference](#normalization-reference)
- [Encoding Behaviour](#encoding-behaviour)
- [Options](#options)
- [Advanced Usage](#advanced-usage)
- [Testing](#testing)
- [Project Structure](#project-structure)
- [Versioning & Compatibility](#versioning--compatibility)
- [Contributing](#contributing)
- [License](#license)
Features
--------
- Normalises native Python types (dicts, lists, dataclasses, sets, `datetime`, numeric edge cases, etc.) to a JSON-like value space before encoding.
- Produces the familiar Toon text format with support for inline arrays, tabular object arrays, and deeply nested structures.
- Configurable indentation, row delimiter (`','`, `'|'`, `'\t'`), and optional length marker flag (`[#N]` style headers).
- Safe string quoting and escaping rules that match the upstream implementation.
- Pure-Python, dependency-free package targeting Python 3.8+.
Installation
------------
```bash
pip install pytoon-encoder
```
Working from a clone of this repository? Install in editable mode:
```bash
pip install -e .
```
Quick Start
-----------
```python
from pytoon import encode
payload = {
"user": {
"id": 123,
"name": "Ada",
"tags": ["reading", "gaming"],
"active": True,
"prefs": [],
}
}
print(encode(payload))
```
Output:
```
user:
id: 123
name: Ada
tags[2]: reading,gaming
active: true
prefs[0]:
```
Normalization Reference
-----------------------
PyTOON first normalises values into a JSON-compatible shape. The table below mirrors the upstream Toon rules:
| Python value | Normalised result | Notes |
|-----------------------------------------------|-------------------------------------------|-----------------------------------------------------------------------|
| `None` | `None` | Encodes as `null`. |
| `bool`, `int`, `float` | Same numeric/boolean value | Floats that are `NaN`, `±inf` become `None`; `-0.0` becomes `0`. |
| `str` | Same string | Subject to quoting/escaping rules during encoding. |
| `datetime`, `date` | ISO-8601 string | Uses `.isoformat()`. |
| `set`, `frozenset` | List of normalised elements | Order is the iteration order of the set. |
| `list`, `tuple`, other sequences (not bytes) | List of normalised elements | Preserves order. |
| `dict`, `Mapping` | Dict with stringified keys | Keys are coerced with `str(key)`; values normalised recursively. |
| `dataclass` instances | Dict via `dataclasses.asdict` | Deep conversion, then recurses. |
| Unsupported objects, functions, generators | `None` | Matches behaviour of upstream encoder. |
Encoding Behaviour
------------------
PyTOON selects the most legible format while preserving structure:
- **Objects** render as `key: value` lines in insertion order. Empty objects emit just `key:`.
- **Arrays of primitives** appear inline: `tags[3]: a,b,c`.
- **Arrays of objects** become tabular if every object shares identical primitive keys. Otherwise, items are rendered as `-` list entries.
- **Arrays of arrays** with primitive inner arrays become nested lists; mixed or non-primitive inner arrays fall back to expanded list items.
- **Mixed arrays** (primitives, objects, arrays combined) always degrade to list entries.
- **Strings** remain unquoted only if they contain no structural characters, delimiters, leading/trailing whitespace, or ambiguous literals (`"true"`, `"42"`, etc.). Otherwise, they are quoted and escaped.
See [`examples/basic_usage.py`](examples/basic_usage.py) for ready-to-run scenarios.
Options
-------
`encode` accepts an optional `EncodeOptions` dictionary that mirrors the TypeScript API:
```python
from pytoon import encode, DELIMITERS
encode(
{"items": [{"id": 1, "name": "Ada"}, {"id": 2, "name": "Bob"}]},
{
"indent": 4,
"delimiter": DELIMITERS["pipe"], # or ',' / '\t'
"length_marker": "#", # renders headers as [#N]
},
)
```
Output:
```
items[#2|]{id|name}:
1|Ada
2|Bob
```
Option reference:
| Option | Type | Default | Description |
|-----------------|----------------------|---------|---------------------------------------------------------------------------------------------|
| `indent` | `int` | `2` | Spaces per indentation level for nested structures. |
| `delimiter` | `','`, `'|'`, `'\t'` | `','` | Active delimiter for inline arrays and tabular rows. |
| `length_marker` | `'#'` or `False` | `False` | When `'#'`, emits headers like `[#3]`; helpful when the consumer expects marked lengths. |
Advanced Usage
--------------
- **Pre-normalisation**: Import `normalize_value` from `pytoon.normalize` to convert data once, then reuse the normalised structure across multiple encodings or transport layers.
- **Direct value encoding**: `pytoon.encoders.encode_value` accepts a pre-normalised JSON value plus resolved options, allowing integration with custom line writers or alternative indentation strategies.
- **Custom delimiters**: The `DELIMITERS` constant exposes the supported delimiter characters. Pass alternate values (e.g. `DELIMITERS["tab"]`) to match TSV-style pipelines.
- **Examples**: The [`examples/`](examples/README.md) directory contains scripts that highlight default behaviour and option combinations. Extend these for domain-specific onboarding material.
- **Automation**: `.github/workflows/ci.yml` runs unit tests on every push and publishes tagged releases (`v*`) to PyPI once tests pass.
Testing
-------
Tests use Python's built-in `unittest` framework:
```bash
python3 -m unittest discover -s tests
```
When porting new behaviour from `toon`, add corresponding tests here to keep parity strong.
Project Structure
-----------------
- `pytoon/` – Core encoder modules (`constants`, `normalize`, `primitives`, `encoders`, `writer`).
- `tests/` – Unit tests covering primitives, objects, arrays, options, and edge cases.
- `examples/` – Runnable scripts that demonstrate practical usage patterns.
Versioning & Compatibility
--------------------------
PyTOON targets Python 3.8+ and strives to remain aligned with the latest upstream `toon` behaviour. Breaking format changes follow upstream and will be clearly documented in release notes.
Contributing
------------
Contributions are welcome! To get started:
1. Fork and clone the repository.
2. Create a virtual environment for Python 3.8 or newer.
3. Install in editable mode with dev extras: `pip install -e .[dev]`.
4. Run `python3 -m unittest discover -s tests` before submitting a pull request.
License
-------
PyTOON retains the licensing of the original [`toon`](https://github.com/johannschopplich/toon) project. Refer to [`LICENSE`](LICENSE) for details.
Raw data
{
"_id": null,
"home_page": null,
"name": "pytoon-encoder",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.8",
"maintainer_email": null,
"keywords": "toon, serialization, data-format, text, encoding",
"author": "Bintang Pradana Erlangga Putra",
"author_email": null,
"download_url": "https://files.pythonhosted.org/packages/1b/7e/7d6ff1f0450a687d0c22f8539c319f100bd6bbb0cace33ef9f64fa0b6ed1/pytoon_encoder-0.1.1.tar.gz",
"platform": null,
"description": "PyTOON\n======\n\nPyTOON is a Python port of the excellent [`toon`](https://github.com/johannschopplich/toon) project by Johann Schopplich. It converts arbitrary Python data structures into the same concise, human-readable text representation produced by the original TypeScript encoder.\n\nThe goal of this repository is feature parity with the upstream project while providing a first-class experience for Python applications and tooling.\n\nContents\n--------\n- [Features](#features)\n- [Installation](#installation)\n- [Quick Start](#quick-start)\n- [Normalization Reference](#normalization-reference)\n- [Encoding Behaviour](#encoding-behaviour)\n- [Options](#options)\n- [Advanced Usage](#advanced-usage)\n- [Testing](#testing)\n- [Project Structure](#project-structure)\n- [Versioning & Compatibility](#versioning--compatibility)\n- [Contributing](#contributing)\n- [License](#license)\n\nFeatures\n--------\n- Normalises native Python types (dicts, lists, dataclasses, sets, `datetime`, numeric edge cases, etc.) to a JSON-like value space before encoding.\n- Produces the familiar Toon text format with support for inline arrays, tabular object arrays, and deeply nested structures.\n- Configurable indentation, row delimiter (`','`, `'|'`, `'\\t'`), and optional length marker flag (`[#N]` style headers).\n- Safe string quoting and escaping rules that match the upstream implementation.\n- Pure-Python, dependency-free package targeting Python 3.8+.\n\nInstallation\n------------\n\n```bash\npip install pytoon-encoder\n```\n\nWorking from a clone of this repository? Install in editable mode:\n\n```bash\npip install -e .\n```\n\nQuick Start\n-----------\n\n```python\nfrom pytoon import encode\n\npayload = {\n \"user\": {\n \"id\": 123,\n \"name\": \"Ada\",\n \"tags\": [\"reading\", \"gaming\"],\n \"active\": True,\n \"prefs\": [],\n }\n}\n\nprint(encode(payload))\n```\n\nOutput:\n\n```\nuser:\n id: 123\n name: Ada\n tags[2]: reading,gaming\n active: true\n prefs[0]:\n```\n\nNormalization Reference\n-----------------------\n\nPyTOON first normalises values into a JSON-compatible shape. The table below mirrors the upstream Toon rules:\n\n| Python value | Normalised result | Notes |\n|-----------------------------------------------|-------------------------------------------|-----------------------------------------------------------------------|\n| `None` | `None` | Encodes as `null`. |\n| `bool`, `int`, `float` | Same numeric/boolean value | Floats that are `NaN`, `\u00b1inf` become `None`; `-0.0` becomes `0`. |\n| `str` | Same string | Subject to quoting/escaping rules during encoding. |\n| `datetime`, `date` | ISO-8601 string | Uses `.isoformat()`. |\n| `set`, `frozenset` | List of normalised elements | Order is the iteration order of the set. |\n| `list`, `tuple`, other sequences (not bytes) | List of normalised elements | Preserves order. |\n| `dict`, `Mapping` | Dict with stringified keys | Keys are coerced with `str(key)`; values normalised recursively. |\n| `dataclass` instances | Dict via `dataclasses.asdict` | Deep conversion, then recurses. |\n| Unsupported objects, functions, generators | `None` | Matches behaviour of upstream encoder. |\n\nEncoding Behaviour\n------------------\n\nPyTOON selects the most legible format while preserving structure:\n\n- **Objects** render as `key: value` lines in insertion order. Empty objects emit just `key:`.\n- **Arrays of primitives** appear inline: `tags[3]: a,b,c`.\n- **Arrays of objects** become tabular if every object shares identical primitive keys. Otherwise, items are rendered as `-` list entries.\n- **Arrays of arrays** with primitive inner arrays become nested lists; mixed or non-primitive inner arrays fall back to expanded list items.\n- **Mixed arrays** (primitives, objects, arrays combined) always degrade to list entries.\n- **Strings** remain unquoted only if they contain no structural characters, delimiters, leading/trailing whitespace, or ambiguous literals (`\"true\"`, `\"42\"`, etc.). Otherwise, they are quoted and escaped.\n\nSee [`examples/basic_usage.py`](examples/basic_usage.py) for ready-to-run scenarios.\n\nOptions\n-------\n\n`encode` accepts an optional `EncodeOptions` dictionary that mirrors the TypeScript API:\n\n```python\nfrom pytoon import encode, DELIMITERS\n\nencode(\n {\"items\": [{\"id\": 1, \"name\": \"Ada\"}, {\"id\": 2, \"name\": \"Bob\"}]},\n {\n \"indent\": 4,\n \"delimiter\": DELIMITERS[\"pipe\"], # or ',' / '\\t'\n \"length_marker\": \"#\", # renders headers as [#N]\n },\n)\n```\n\nOutput:\n\n```\nitems[#2|]{id|name}:\n 1|Ada\n 2|Bob\n```\n\nOption reference:\n\n| Option | Type | Default | Description |\n|-----------------|----------------------|---------|---------------------------------------------------------------------------------------------|\n| `indent` | `int` | `2` | Spaces per indentation level for nested structures. |\n| `delimiter` | `','`, `'|'`, `'\\t'` | `','` | Active delimiter for inline arrays and tabular rows. |\n| `length_marker` | `'#'` or `False` | `False` | When `'#'`, emits headers like `[#3]`; helpful when the consumer expects marked lengths. |\n\nAdvanced Usage\n--------------\n\n- **Pre-normalisation**: Import `normalize_value` from `pytoon.normalize` to convert data once, then reuse the normalised structure across multiple encodings or transport layers.\n- **Direct value encoding**: `pytoon.encoders.encode_value` accepts a pre-normalised JSON value plus resolved options, allowing integration with custom line writers or alternative indentation strategies.\n- **Custom delimiters**: The `DELIMITERS` constant exposes the supported delimiter characters. Pass alternate values (e.g. `DELIMITERS[\"tab\"]`) to match TSV-style pipelines.\n- **Examples**: The [`examples/`](examples/README.md) directory contains scripts that highlight default behaviour and option combinations. Extend these for domain-specific onboarding material.\n- **Automation**: `.github/workflows/ci.yml` runs unit tests on every push and publishes tagged releases (`v*`) to PyPI once tests pass.\n\nTesting\n-------\n\nTests use Python's built-in `unittest` framework:\n\n```bash\npython3 -m unittest discover -s tests\n```\n\nWhen porting new behaviour from `toon`, add corresponding tests here to keep parity strong.\n\nProject Structure\n-----------------\n\n- `pytoon/` \u2013 Core encoder modules (`constants`, `normalize`, `primitives`, `encoders`, `writer`).\n- `tests/` \u2013 Unit tests covering primitives, objects, arrays, options, and edge cases.\n- `examples/` \u2013 Runnable scripts that demonstrate practical usage patterns.\n\nVersioning & Compatibility\n--------------------------\n\nPyTOON targets Python 3.8+ and strives to remain aligned with the latest upstream `toon` behaviour. Breaking format changes follow upstream and will be clearly documented in release notes.\n\nContributing\n------------\n\nContributions are welcome! To get started:\n\n1. Fork and clone the repository.\n2. Create a virtual environment for Python 3.8 or newer.\n3. Install in editable mode with dev extras: `pip install -e .[dev]`.\n4. Run `python3 -m unittest discover -s tests` before submitting a pull request.\n\nLicense\n-------\n\nPyTOON retains the licensing of the original [`toon`](https://github.com/johannschopplich/toon) project. Refer to [`LICENSE`](LICENSE) for details.\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "Python port of the TOON encoder, producing a concise, human-readable data format.",
"version": "0.1.1",
"project_urls": {
"Homepage": "https://github.com/bpradana/pytoon",
"Issues": "https://github.com/bpradana/pytoon/issues",
"Repository": "https://github.com/bpradana/pytoon",
"Upstream": "https://github.com/johannschopplich/toon"
},
"split_keywords": [
"toon",
" serialization",
" data-format",
" text",
" encoding"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "60401afc7e86b2d7afba2271c60721d3d259a54c4434ecccc3c6ad3f071c3604",
"md5": "02803ffb2d2d96581b50e891fe4d57fe",
"sha256": "2720349f4cf342c6126451a329da2f3a6f3fa27574e24f50d987da1e12d877f6"
},
"downloads": -1,
"filename": "pytoon_encoder-0.1.1-py3-none-any.whl",
"has_sig": false,
"md5_digest": "02803ffb2d2d96581b50e891fe4d57fe",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.8",
"size": 11643,
"upload_time": "2025-10-27T08:37:28",
"upload_time_iso_8601": "2025-10-27T08:37:28.668120Z",
"url": "https://files.pythonhosted.org/packages/60/40/1afc7e86b2d7afba2271c60721d3d259a54c4434ecccc3c6ad3f071c3604/pytoon_encoder-0.1.1-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "1b7e7d6ff1f0450a687d0c22f8539c319f100bd6bbb0cace33ef9f64fa0b6ed1",
"md5": "19ada66df77b1c72b5fef82065bd2499",
"sha256": "5ee3ad6984efe553953c3c282020386eeeade59658dc0cb5ead1853499a742de"
},
"downloads": -1,
"filename": "pytoon_encoder-0.1.1.tar.gz",
"has_sig": false,
"md5_digest": "19ada66df77b1c72b5fef82065bd2499",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.8",
"size": 15014,
"upload_time": "2025-10-27T08:37:29",
"upload_time_iso_8601": "2025-10-27T08:37:29.386372Z",
"url": "https://files.pythonhosted.org/packages/1b/7e/7d6ff1f0450a687d0c22f8539c319f100bd6bbb0cace33ef9f64fa0b6ed1/pytoon_encoder-0.1.1.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-10-27 08:37:29",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "bpradana",
"github_project": "pytoon",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "pytoon-encoder"
}