terset 0.2.1__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.
- terset-0.2.1/LICENSE +21 -0
- terset-0.2.1/PKG-INFO +132 -0
- terset-0.2.1/README.md +109 -0
- terset-0.2.1/pyproject.toml +36 -0
- terset-0.2.1/setup.cfg +4 -0
- terset-0.2.1/src/terset/__init__.py +5 -0
- terset-0.2.1/src/terset/_terset.py +514 -0
- terset-0.2.1/src/terset/py.typed +0 -0
- terset-0.2.1/src/terset.egg-info/PKG-INFO +132 -0
- terset-0.2.1/src/terset.egg-info/SOURCES.txt +16 -0
- terset-0.2.1/src/terset.egg-info/dependency_links.txt +1 -0
- terset-0.2.1/src/terset.egg-info/top_level.txt +1 -0
- terset-0.2.1/tests/test_decoder_errors.py +29 -0
- terset-0.2.1/tests/test_header_depth.py +52 -0
- terset-0.2.1/tests/test_header_performance.py +31 -0
- terset-0.2.1/tests/test_long_quoted.py +22 -0
- terset-0.2.1/tests/test_rules.py +31 -0
- terset-0.2.1/tests/test_terset.py +216 -0
terset-0.2.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 TrueWear, LLC
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
terset-0.2.1/PKG-INFO
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: terset
|
|
3
|
+
Version: 0.2.1
|
|
4
|
+
Summary: Encode JSON records as compact, typed tables.
|
|
5
|
+
Author: TrueWear, LLC
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://terset.dev
|
|
8
|
+
Project-URL: Source, https://github.com/TrueWear/terset
|
|
9
|
+
Keywords: json,llm,prompt,tokens,table,encoding
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
18
|
+
Classifier: Typing :: Typed
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Dynamic: license-file
|
|
23
|
+
|
|
24
|
+
# Terset for Python
|
|
25
|
+
|
|
26
|
+
Terset writes JSON records as compact, typed tables using format v2. Python 3.10 or newer.
|
|
27
|
+
Zero runtime dependencies.
|
|
28
|
+
|
|
29
|
+
## Install
|
|
30
|
+
|
|
31
|
+
Coming soon on PyPI. This package is not published yet.
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
pip install terset
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Use
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
from terset import decode, encode, rules_for
|
|
41
|
+
|
|
42
|
+
data = [{"id": "1", "name": "Ada"}, {"id": "2", "name": "Ben"}]
|
|
43
|
+
text = encode(data)
|
|
44
|
+
print(text)
|
|
45
|
+
print(decode(text) == data)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Output (the gaps are tabs):
|
|
49
|
+
|
|
50
|
+
```text
|
|
51
|
+
[2]
|
|
52
|
+
id name
|
|
53
|
+
1 Ada
|
|
54
|
+
2 Ben
|
|
55
|
+
True
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
`encode(value)` accepts a dictionary or list at the root. Values can be
|
|
59
|
+
strings, finite numbers, booleans, `None`, dictionaries with string keys,
|
|
60
|
+
and lists. It returns canonical Terset text. Put `rules_for(text)` before the data
|
|
61
|
+
when a reader needs the format explained. It validates canonical text and returns
|
|
62
|
+
the base rules, plus rules for keyed tables and dotted headers when present.
|
|
63
|
+
The exported `RULES` keeps all rules for any text. `decode(text)` returns the same
|
|
64
|
+
JSON value and rejects text the encoder would not produce.
|
|
65
|
+
|
|
66
|
+
Lists of at least two dictionaries become tables when the header and rows
|
|
67
|
+
save UTF-8 bytes. Named tables start with `name[N]:`. Root tables start
|
|
68
|
+
with `[N]` on its own line. Compact JSON lists have no count.
|
|
69
|
+
Missing fields, empty strings and `None` stay distinct.
|
|
70
|
+
|
|
71
|
+
A map in a direct root field can use `name{N}:` when the complete named
|
|
72
|
+
block is strictly shorter than `name: JSON`. Its first column is `@key`,
|
|
73
|
+
which holds each entry's key and is not a field. Root maps stay fields.
|
|
74
|
+
Uniform nested objects with identical ordered simple keys expand into
|
|
75
|
+
dotted headers, recursively. Literal dotted field names are quoted.
|
|
76
|
+
Other nested values stay compact JSON. The decoder rejects count mismatches
|
|
77
|
+
and accepts only the current canonical format.
|
|
78
|
+
See the full [format v2 specification](https://github.com/TrueWear/terset/blob/main/packages/terset/SPEC.md).
|
|
79
|
+
|
|
80
|
+
## Python details
|
|
81
|
+
|
|
82
|
+
- The format uses JavaScript binary64 numbers. `1.0` writes as `1`.
|
|
83
|
+
Decoded integer literals within `[-2**53, 2**53]` become Python `int`
|
|
84
|
+
values. Tokens with a fraction or exponent, and integers outside this
|
|
85
|
+
range, become `float` values.
|
|
86
|
+
Python integers must be exactly representable as binary64 numbers;
|
|
87
|
+
otherwise encoding raises `TypeError`. This avoids silently rounding
|
|
88
|
+
integers such as `9007199254740993`.
|
|
89
|
+
- Numeric negative zero, infinities, NaN, cycles, non-JSON types and depth
|
|
90
|
+
above 500 raise `TypeError`. Use plain built-in dictionaries and lists.
|
|
91
|
+
- Dictionary order is preserved, with JavaScript's array-index keys first
|
|
92
|
+
in numeric order. For example, `"2"` precedes `"10"`; `"01"` keeps its
|
|
93
|
+
insertion position among other keys.
|
|
94
|
+
- Unicode stays unchanged, including normalization forms and lone
|
|
95
|
+
surrogates. Explicit UTF-16 surrogate pairs become Python astral code
|
|
96
|
+
points. Keys that become identical through this conversion are rejected.
|
|
97
|
+
- Format validation errors raise `ValueError`, including non-finite numbers
|
|
98
|
+
and excessive depth. Passing a non-string to `decode` raises `TypeError`.
|
|
99
|
+
|
|
100
|
+
`encode` uses the covariant `JsonInputRoot` and `JsonInputValue` aliases,
|
|
101
|
+
based on read-only `Mapping` and `Sequence` interfaces. This lets typed
|
|
102
|
+
lists and dictionaries pass without widening their element types. Runtime
|
|
103
|
+
validation still requires plain built-in lists and dictionaries.
|
|
104
|
+
`decode` returns concrete `JsonRoot`, `JsonObject` and `JsonValue` types.
|
|
105
|
+
The package includes `py.typed`.
|
|
106
|
+
|
|
107
|
+
## Limits
|
|
108
|
+
|
|
109
|
+
The format rejects numeric negative zero and nesting deeper than 500.
|
|
110
|
+
With deep data and an already deep caller stack,
|
|
111
|
+
`encode`, `decode` and `rules_for` can raise `RecursionError`.
|
|
112
|
+
This depends on the available stack depth, not the Python version.
|
|
113
|
+
|
|
114
|
+
## Local tests
|
|
115
|
+
|
|
116
|
+
From the repository root, without installing the package:
|
|
117
|
+
|
|
118
|
+
```sh
|
|
119
|
+
python3 -B -m unittest discover -s packages/terset-py/tests -v
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Tests read the shared cases generated by the TypeScript encoder and check
|
|
123
|
+
exact output, decoding, the rules string, and invalid inputs. To regenerate
|
|
124
|
+
the cases from the repository root with Node 24:
|
|
125
|
+
|
|
126
|
+
```sh
|
|
127
|
+
node packages/terset/tests/generate-cases.ts
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## License
|
|
131
|
+
|
|
132
|
+
MIT. Copyright (c) 2026 TrueWear, LLC. The license text is included in `LICENSE`.
|
terset-0.2.1/README.md
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# Terset for Python
|
|
2
|
+
|
|
3
|
+
Terset writes JSON records as compact, typed tables using format v2. Python 3.10 or newer.
|
|
4
|
+
Zero runtime dependencies.
|
|
5
|
+
|
|
6
|
+
## Install
|
|
7
|
+
|
|
8
|
+
Coming soon on PyPI. This package is not published yet.
|
|
9
|
+
|
|
10
|
+
```sh
|
|
11
|
+
pip install terset
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Use
|
|
15
|
+
|
|
16
|
+
```python
|
|
17
|
+
from terset import decode, encode, rules_for
|
|
18
|
+
|
|
19
|
+
data = [{"id": "1", "name": "Ada"}, {"id": "2", "name": "Ben"}]
|
|
20
|
+
text = encode(data)
|
|
21
|
+
print(text)
|
|
22
|
+
print(decode(text) == data)
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Output (the gaps are tabs):
|
|
26
|
+
|
|
27
|
+
```text
|
|
28
|
+
[2]
|
|
29
|
+
id name
|
|
30
|
+
1 Ada
|
|
31
|
+
2 Ben
|
|
32
|
+
True
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
`encode(value)` accepts a dictionary or list at the root. Values can be
|
|
36
|
+
strings, finite numbers, booleans, `None`, dictionaries with string keys,
|
|
37
|
+
and lists. It returns canonical Terset text. Put `rules_for(text)` before the data
|
|
38
|
+
when a reader needs the format explained. It validates canonical text and returns
|
|
39
|
+
the base rules, plus rules for keyed tables and dotted headers when present.
|
|
40
|
+
The exported `RULES` keeps all rules for any text. `decode(text)` returns the same
|
|
41
|
+
JSON value and rejects text the encoder would not produce.
|
|
42
|
+
|
|
43
|
+
Lists of at least two dictionaries become tables when the header and rows
|
|
44
|
+
save UTF-8 bytes. Named tables start with `name[N]:`. Root tables start
|
|
45
|
+
with `[N]` on its own line. Compact JSON lists have no count.
|
|
46
|
+
Missing fields, empty strings and `None` stay distinct.
|
|
47
|
+
|
|
48
|
+
A map in a direct root field can use `name{N}:` when the complete named
|
|
49
|
+
block is strictly shorter than `name: JSON`. Its first column is `@key`,
|
|
50
|
+
which holds each entry's key and is not a field. Root maps stay fields.
|
|
51
|
+
Uniform nested objects with identical ordered simple keys expand into
|
|
52
|
+
dotted headers, recursively. Literal dotted field names are quoted.
|
|
53
|
+
Other nested values stay compact JSON. The decoder rejects count mismatches
|
|
54
|
+
and accepts only the current canonical format.
|
|
55
|
+
See the full [format v2 specification](https://github.com/TrueWear/terset/blob/main/packages/terset/SPEC.md).
|
|
56
|
+
|
|
57
|
+
## Python details
|
|
58
|
+
|
|
59
|
+
- The format uses JavaScript binary64 numbers. `1.0` writes as `1`.
|
|
60
|
+
Decoded integer literals within `[-2**53, 2**53]` become Python `int`
|
|
61
|
+
values. Tokens with a fraction or exponent, and integers outside this
|
|
62
|
+
range, become `float` values.
|
|
63
|
+
Python integers must be exactly representable as binary64 numbers;
|
|
64
|
+
otherwise encoding raises `TypeError`. This avoids silently rounding
|
|
65
|
+
integers such as `9007199254740993`.
|
|
66
|
+
- Numeric negative zero, infinities, NaN, cycles, non-JSON types and depth
|
|
67
|
+
above 500 raise `TypeError`. Use plain built-in dictionaries and lists.
|
|
68
|
+
- Dictionary order is preserved, with JavaScript's array-index keys first
|
|
69
|
+
in numeric order. For example, `"2"` precedes `"10"`; `"01"` keeps its
|
|
70
|
+
insertion position among other keys.
|
|
71
|
+
- Unicode stays unchanged, including normalization forms and lone
|
|
72
|
+
surrogates. Explicit UTF-16 surrogate pairs become Python astral code
|
|
73
|
+
points. Keys that become identical through this conversion are rejected.
|
|
74
|
+
- Format validation errors raise `ValueError`, including non-finite numbers
|
|
75
|
+
and excessive depth. Passing a non-string to `decode` raises `TypeError`.
|
|
76
|
+
|
|
77
|
+
`encode` uses the covariant `JsonInputRoot` and `JsonInputValue` aliases,
|
|
78
|
+
based on read-only `Mapping` and `Sequence` interfaces. This lets typed
|
|
79
|
+
lists and dictionaries pass without widening their element types. Runtime
|
|
80
|
+
validation still requires plain built-in lists and dictionaries.
|
|
81
|
+
`decode` returns concrete `JsonRoot`, `JsonObject` and `JsonValue` types.
|
|
82
|
+
The package includes `py.typed`.
|
|
83
|
+
|
|
84
|
+
## Limits
|
|
85
|
+
|
|
86
|
+
The format rejects numeric negative zero and nesting deeper than 500.
|
|
87
|
+
With deep data and an already deep caller stack,
|
|
88
|
+
`encode`, `decode` and `rules_for` can raise `RecursionError`.
|
|
89
|
+
This depends on the available stack depth, not the Python version.
|
|
90
|
+
|
|
91
|
+
## Local tests
|
|
92
|
+
|
|
93
|
+
From the repository root, without installing the package:
|
|
94
|
+
|
|
95
|
+
```sh
|
|
96
|
+
python3 -B -m unittest discover -s packages/terset-py/tests -v
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Tests read the shared cases generated by the TypeScript encoder and check
|
|
100
|
+
exact output, decoding, the rules string, and invalid inputs. To regenerate
|
|
101
|
+
the cases from the repository root with Node 24:
|
|
102
|
+
|
|
103
|
+
```sh
|
|
104
|
+
node packages/terset/tests/generate-cases.ts
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## License
|
|
108
|
+
|
|
109
|
+
MIT. Copyright (c) 2026 TrueWear, LLC. The license text is included in `LICENSE`.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "terset"
|
|
7
|
+
version = "0.2.1"
|
|
8
|
+
description = "Encode JSON records as compact, typed tables."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
authors = [{ name = "TrueWear, LLC" }]
|
|
14
|
+
dependencies = []
|
|
15
|
+
keywords = ["json", "llm", "prompt", "tokens", "table", "encoding"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 4 - Beta",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
20
|
+
"Programming Language :: Python :: 3.10",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Programming Language :: Python :: 3.13",
|
|
24
|
+
"Programming Language :: Python :: 3.14",
|
|
25
|
+
"Typing :: Typed",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[project.urls]
|
|
29
|
+
Homepage = "https://terset.dev"
|
|
30
|
+
Source = "https://github.com/TrueWear/terset"
|
|
31
|
+
|
|
32
|
+
[tool.setuptools.packages.find]
|
|
33
|
+
where = ["src"]
|
|
34
|
+
|
|
35
|
+
[tool.setuptools.package-data]
|
|
36
|
+
terset = ["py.typed"]
|
terset-0.2.1/setup.cfg
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
"""Encode and decode canonical Terset text without runtime dependencies."""
|
|
2
|
+
|
|
3
|
+
from ._terset import RULES, JsonInputRoot, JsonInputValue, JsonObject, JsonRoot, JsonValue, decode, encode, rules_for
|
|
4
|
+
|
|
5
|
+
__all__ = ["RULES", "JsonInputRoot", "JsonInputValue", "JsonObject", "JsonRoot", "JsonValue", "decode", "encode", "rules_for"]
|