c0data-py 0.3.2__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.
- c0data_py-0.3.2/LICENSE +21 -0
- c0data_py-0.3.2/MANIFEST.in +3 -0
- c0data_py-0.3.2/PKG-INFO +159 -0
- c0data_py-0.3.2/README.md +146 -0
- c0data_py-0.3.2/c0-c/c0.h +3926 -0
- c0data_py-0.3.2/pyproject.toml +20 -0
- c0data_py-0.3.2/setup.cfg +4 -0
- c0data_py-0.3.2/setup.py +11 -0
- c0data_py-0.3.2/src/c0/__init__.py +1214 -0
- c0data_py-0.3.2/src/c0/_c0module.c +1150 -0
- c0data_py-0.3.2/src/c0/errors.py +41 -0
- c0data_py-0.3.2/src/c0data_py.egg-info/PKG-INFO +159 -0
- c0data_py-0.3.2/src/c0data_py.egg-info/SOURCES.txt +16 -0
- c0data_py-0.3.2/src/c0data_py.egg-info/dependency_links.txt +1 -0
- c0data_py-0.3.2/src/c0data_py.egg-info/top_level.txt +1 -0
- c0data_py-0.3.2/tests/test_basic.py +306 -0
- c0data_py-0.3.2/tests/test_conformance.py +316 -0
- c0data_py-0.3.2/tests/test_primitives.py +122 -0
c0data_py-0.3.2/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Thomas Sawyer <transfire@gmail.com>
|
|
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.
|
c0data_py-0.3.2/PKG-INFO
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: c0data-py
|
|
3
|
+
Version: 0.3.2
|
|
4
|
+
Summary: C0DATA 1.0 — structured data using ASCII C0 control codes (Python binding over the c0 C core)
|
|
5
|
+
Author-email: Thomas Sawyer <transfire@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Repository, https://github.com/c0data/c0-py
|
|
8
|
+
Keywords: c0,c0data,data-format,control-codes
|
|
9
|
+
Requires-Python: >=3.8
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Dynamic: license-file
|
|
13
|
+
|
|
14
|
+
# c0
|
|
15
|
+
|
|
16
|
+
A Python implementation of [C0DATA](https://github.com/c0data) 1.0 — structured
|
|
17
|
+
data built on ASCII C0 control codes.
|
|
18
|
+
|
|
19
|
+
This is **not** a reimplementation: it's a thin, fast binding over the
|
|
20
|
+
[c0 C core](https://github.com/c0data/c0-c) (a CPython extension, no third-party
|
|
21
|
+
dependencies), so the scan-heavy work runs as native C while the Python layer
|
|
22
|
+
provides an idiomatic API. The read path is **zero-copy** — raw accessors return
|
|
23
|
+
`memoryview` slices into the input buffer; logical values come back as `bytes`,
|
|
24
|
+
with `str` conveniences beside them.
|
|
25
|
+
|
|
26
|
+
## Usage
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
import c0
|
|
30
|
+
|
|
31
|
+
# Write: SYN heading, a group with a header row (␁␞), records, EOT
|
|
32
|
+
buf = c0.build(lambda b: (
|
|
33
|
+
b.syn(shape="tabular"),
|
|
34
|
+
b.group("users", ["name", "amount"]),
|
|
35
|
+
b.record("Alice", "100"),
|
|
36
|
+
b.record("Bob", "200"),
|
|
37
|
+
b.eot(),
|
|
38
|
+
))
|
|
39
|
+
# ␖␁shape␟tabular␝users␁␞name␟amount␞Alice␟100␞Bob␟200␄
|
|
40
|
+
|
|
41
|
+
# Read
|
|
42
|
+
doc = c0.Document(buf) # or c0.Container(buf) for several documents / an EM tail
|
|
43
|
+
doc.shape # 'tabular'
|
|
44
|
+
users = doc.group("users") # by name or index; doc["users"] too
|
|
45
|
+
users.headers # [b'name', b'amount']
|
|
46
|
+
for rec in users:
|
|
47
|
+
rec.values # [b'Alice', b'100'] — plain trimmed, text decoded
|
|
48
|
+
rec.string("amount") # '100', by header name or index
|
|
49
|
+
rec.field(0) # memoryview of the raw unit (zero-copy)
|
|
50
|
+
|
|
51
|
+
# The body is the canonical unit — hashable for content addressing
|
|
52
|
+
assert c0.canonical(c0.build(lambda b: (b.group("users", ["name", "amount"]),
|
|
53
|
+
b.record("Alice", "100"), b.record("Bob", "200"))))
|
|
54
|
+
c0.validate(buf) # raises c0.C0SyntaxError on malformed input
|
|
55
|
+
|
|
56
|
+
# Streams and pretty form
|
|
57
|
+
log = c0.StreamReader(open("claims.c0", "rb").read()) # .torn, .blocks, .damaged, .records
|
|
58
|
+
print(c0.pretty_format(buf)) # Unicode Control Pictures
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Values are written in their one spelling: plain when they can be, text
|
|
62
|
+
(`␂…␃`) when they hold a control byte or edge whitespace. Keyed records
|
|
63
|
+
(`␞␁host␟localhost␁port␟5432`) are maps that keep their order:
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
buf = c0.build(lambda b: (b.group("database"), b.map(host="localhost", port="5432")))
|
|
67
|
+
rec = c0.Body(buf).group(0).record(0)
|
|
68
|
+
rec.keyed, rec.keys, rec["port"] # True, [b'host', b'port'], b'5432'
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Nested levels, lists and references
|
|
72
|
+
|
|
73
|
+
A unit may be a nested level, `␐…␘`, whose contents are a whole body. A
|
|
74
|
+
level holding one record is a list; `Builder.list` writes one and
|
|
75
|
+
`Record.list` reads it back.
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
buf = c0.build(lambda b: (
|
|
79
|
+
b.group("users", ["name", "roles", "address"]),
|
|
80
|
+
b.record("Alice"),
|
|
81
|
+
b.list(["Admin", "Editor"]), # ␐␞Admin␟Editor␘
|
|
82
|
+
b.level(lambda l: (l.header("street", "city"), # ␐␁␞street␟city␞1 Main␟Springfield␘
|
|
83
|
+
l.record("1 Main", "Springfield"))),
|
|
84
|
+
))
|
|
85
|
+
rec = c0.Body(buf).group(0).record(0)
|
|
86
|
+
rec.list(1) # [b'Admin', b'Editor']
|
|
87
|
+
rec.level(2).record(0).string("city") # 'Springfield'
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
`level()` and `key_level(name)` also work as context managers (`with b.level(): …`).
|
|
91
|
+
References are `b.ref("tags")` (`␅tags`) or `b.ref("tags", "001", "label")`
|
|
92
|
+
(`␅␂tags␟001␟label␃`), read back with `Record.ref`.
|
|
93
|
+
|
|
94
|
+
The builder also has `file`, `section`, `header`, `field`, `key`, `note`,
|
|
95
|
+
`etb(payload)`, `em(tail)`, `block` and `item`, matching the Crystal reference.
|
|
96
|
+
Every error derives from `c0.C0Error`.
|
|
97
|
+
|
|
98
|
+
### Primitives
|
|
99
|
+
|
|
100
|
+
Underneath, the builder has one method per code: `fs`, `gs`, `rs`, `us`,
|
|
101
|
+
`sub` (C0-DIFF only), `soh`, `dle`, `can`, `bel`, `enq`, `syn`, `eot`,
|
|
102
|
+
`etb`, `em`. Each writes
|
|
103
|
+
its code and, when given, what follows it, so a chain reads like the bytes,
|
|
104
|
+
and anything the grammar allows can be written. The argument is optional
|
|
105
|
+
where the grammar allows the code alone (`gs()`, `rs()`, `us()`, `sub()`,
|
|
106
|
+
`soh()`, `bel()`, `etb()`); `enq` takes one to three names:
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
b.rs("Alice").us("1502.30") # ␞Alice␟1502.30
|
|
110
|
+
b.rs().soh("host").us("localhost").soh("port").us("5432") # ␞␁host␟localhost␁port␟5432
|
|
111
|
+
b.gs("users").soh().rs("name").us("amount") # ␝users␁␞name␟amount: a header row
|
|
112
|
+
b.rs("cfg").us().dle().rs().soh("b").us("2").can() # ␞cfg␟␐␞␁b␟2␘
|
|
113
|
+
b.bel("checked").rs("x") # ␇checked␞x: a note
|
|
114
|
+
b.bel().gs("old") # ␇␝old: a dead group
|
|
115
|
+
b.gs().gs("sub").rs().enq("users").us().enq("users", "1") # ␝␝sub␞␅users␟␅␂users␟1␃
|
|
116
|
+
b.syn().soh("version").us("1").rs("a").eot() # ␖␁version␟1␞a␄
|
|
117
|
+
b.rs("old").sub("new") # ␞old␚new: a C0-DIFF replace-all
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
The composed methods above are built on these and cannot write ill-formed
|
|
121
|
+
output; with the primitives, well-formedness is yours. Values keep their one
|
|
122
|
+
spelling either way (there is no STX/ETX primitive), and names are still
|
|
123
|
+
checked. The two layers share the record state and mix freely:
|
|
124
|
+
`b.rs("a").field("b")` writes `␞a␟b`, `b.record().us("c")` writes `␞␟c`.
|
|
125
|
+
|
|
126
|
+
## Install / build
|
|
127
|
+
|
|
128
|
+
```sh
|
|
129
|
+
pip install c0data-py # the package is c0data-py (PyPI treats c0data as codata); in code it is `import c0`
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
From a checkout, the C core and the shared conformance vectors are git submodules:
|
|
133
|
+
|
|
134
|
+
```sh
|
|
135
|
+
git submodule update --init # pulls in c0-c (the header) and c0-spec
|
|
136
|
+
pip install . # builds the extension
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Requires a C compiler and the Python development headers.
|
|
140
|
+
|
|
141
|
+
## Status
|
|
142
|
+
|
|
143
|
+
Binds the c0-c 1.0 core: validator, zero-copy body/document/container readers
|
|
144
|
+
(files, groups, header rows, positional and keyed records, nested levels,
|
|
145
|
+
references, headings, EM blobs), canonical check, the builder, ETB stream mode
|
|
146
|
+
and pretty form (`pretty_format`/`pretty_parse`). Passes the shared
|
|
147
|
+
conformance vectors from [c0-spec](https://github.com/c0data/c0-spec).
|
|
148
|
+
|
|
149
|
+
Converters (CSV / JSON / C0-DIFF) are not yet wrapped.
|
|
150
|
+
|
|
151
|
+
## Test
|
|
152
|
+
|
|
153
|
+
```sh
|
|
154
|
+
python -m unittest discover -s tests
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## License
|
|
158
|
+
|
|
159
|
+
MIT
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# c0
|
|
2
|
+
|
|
3
|
+
A Python implementation of [C0DATA](https://github.com/c0data) 1.0 — structured
|
|
4
|
+
data built on ASCII C0 control codes.
|
|
5
|
+
|
|
6
|
+
This is **not** a reimplementation: it's a thin, fast binding over the
|
|
7
|
+
[c0 C core](https://github.com/c0data/c0-c) (a CPython extension, no third-party
|
|
8
|
+
dependencies), so the scan-heavy work runs as native C while the Python layer
|
|
9
|
+
provides an idiomatic API. The read path is **zero-copy** — raw accessors return
|
|
10
|
+
`memoryview` slices into the input buffer; logical values come back as `bytes`,
|
|
11
|
+
with `str` conveniences beside them.
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
import c0
|
|
17
|
+
|
|
18
|
+
# Write: SYN heading, a group with a header row (␁␞), records, EOT
|
|
19
|
+
buf = c0.build(lambda b: (
|
|
20
|
+
b.syn(shape="tabular"),
|
|
21
|
+
b.group("users", ["name", "amount"]),
|
|
22
|
+
b.record("Alice", "100"),
|
|
23
|
+
b.record("Bob", "200"),
|
|
24
|
+
b.eot(),
|
|
25
|
+
))
|
|
26
|
+
# ␖␁shape␟tabular␝users␁␞name␟amount␞Alice␟100␞Bob␟200␄
|
|
27
|
+
|
|
28
|
+
# Read
|
|
29
|
+
doc = c0.Document(buf) # or c0.Container(buf) for several documents / an EM tail
|
|
30
|
+
doc.shape # 'tabular'
|
|
31
|
+
users = doc.group("users") # by name or index; doc["users"] too
|
|
32
|
+
users.headers # [b'name', b'amount']
|
|
33
|
+
for rec in users:
|
|
34
|
+
rec.values # [b'Alice', b'100'] — plain trimmed, text decoded
|
|
35
|
+
rec.string("amount") # '100', by header name or index
|
|
36
|
+
rec.field(0) # memoryview of the raw unit (zero-copy)
|
|
37
|
+
|
|
38
|
+
# The body is the canonical unit — hashable for content addressing
|
|
39
|
+
assert c0.canonical(c0.build(lambda b: (b.group("users", ["name", "amount"]),
|
|
40
|
+
b.record("Alice", "100"), b.record("Bob", "200"))))
|
|
41
|
+
c0.validate(buf) # raises c0.C0SyntaxError on malformed input
|
|
42
|
+
|
|
43
|
+
# Streams and pretty form
|
|
44
|
+
log = c0.StreamReader(open("claims.c0", "rb").read()) # .torn, .blocks, .damaged, .records
|
|
45
|
+
print(c0.pretty_format(buf)) # Unicode Control Pictures
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Values are written in their one spelling: plain when they can be, text
|
|
49
|
+
(`␂…␃`) when they hold a control byte or edge whitespace. Keyed records
|
|
50
|
+
(`␞␁host␟localhost␁port␟5432`) are maps that keep their order:
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
buf = c0.build(lambda b: (b.group("database"), b.map(host="localhost", port="5432")))
|
|
54
|
+
rec = c0.Body(buf).group(0).record(0)
|
|
55
|
+
rec.keyed, rec.keys, rec["port"] # True, [b'host', b'port'], b'5432'
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Nested levels, lists and references
|
|
59
|
+
|
|
60
|
+
A unit may be a nested level, `␐…␘`, whose contents are a whole body. A
|
|
61
|
+
level holding one record is a list; `Builder.list` writes one and
|
|
62
|
+
`Record.list` reads it back.
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
buf = c0.build(lambda b: (
|
|
66
|
+
b.group("users", ["name", "roles", "address"]),
|
|
67
|
+
b.record("Alice"),
|
|
68
|
+
b.list(["Admin", "Editor"]), # ␐␞Admin␟Editor␘
|
|
69
|
+
b.level(lambda l: (l.header("street", "city"), # ␐␁␞street␟city␞1 Main␟Springfield␘
|
|
70
|
+
l.record("1 Main", "Springfield"))),
|
|
71
|
+
))
|
|
72
|
+
rec = c0.Body(buf).group(0).record(0)
|
|
73
|
+
rec.list(1) # [b'Admin', b'Editor']
|
|
74
|
+
rec.level(2).record(0).string("city") # 'Springfield'
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`level()` and `key_level(name)` also work as context managers (`with b.level(): …`).
|
|
78
|
+
References are `b.ref("tags")` (`␅tags`) or `b.ref("tags", "001", "label")`
|
|
79
|
+
(`␅␂tags␟001␟label␃`), read back with `Record.ref`.
|
|
80
|
+
|
|
81
|
+
The builder also has `file`, `section`, `header`, `field`, `key`, `note`,
|
|
82
|
+
`etb(payload)`, `em(tail)`, `block` and `item`, matching the Crystal reference.
|
|
83
|
+
Every error derives from `c0.C0Error`.
|
|
84
|
+
|
|
85
|
+
### Primitives
|
|
86
|
+
|
|
87
|
+
Underneath, the builder has one method per code: `fs`, `gs`, `rs`, `us`,
|
|
88
|
+
`sub` (C0-DIFF only), `soh`, `dle`, `can`, `bel`, `enq`, `syn`, `eot`,
|
|
89
|
+
`etb`, `em`. Each writes
|
|
90
|
+
its code and, when given, what follows it, so a chain reads like the bytes,
|
|
91
|
+
and anything the grammar allows can be written. The argument is optional
|
|
92
|
+
where the grammar allows the code alone (`gs()`, `rs()`, `us()`, `sub()`,
|
|
93
|
+
`soh()`, `bel()`, `etb()`); `enq` takes one to three names:
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
b.rs("Alice").us("1502.30") # ␞Alice␟1502.30
|
|
97
|
+
b.rs().soh("host").us("localhost").soh("port").us("5432") # ␞␁host␟localhost␁port␟5432
|
|
98
|
+
b.gs("users").soh().rs("name").us("amount") # ␝users␁␞name␟amount: a header row
|
|
99
|
+
b.rs("cfg").us().dle().rs().soh("b").us("2").can() # ␞cfg␟␐␞␁b␟2␘
|
|
100
|
+
b.bel("checked").rs("x") # ␇checked␞x: a note
|
|
101
|
+
b.bel().gs("old") # ␇␝old: a dead group
|
|
102
|
+
b.gs().gs("sub").rs().enq("users").us().enq("users", "1") # ␝␝sub␞␅users␟␅␂users␟1␃
|
|
103
|
+
b.syn().soh("version").us("1").rs("a").eot() # ␖␁version␟1␞a␄
|
|
104
|
+
b.rs("old").sub("new") # ␞old␚new: a C0-DIFF replace-all
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
The composed methods above are built on these and cannot write ill-formed
|
|
108
|
+
output; with the primitives, well-formedness is yours. Values keep their one
|
|
109
|
+
spelling either way (there is no STX/ETX primitive), and names are still
|
|
110
|
+
checked. The two layers share the record state and mix freely:
|
|
111
|
+
`b.rs("a").field("b")` writes `␞a␟b`, `b.record().us("c")` writes `␞␟c`.
|
|
112
|
+
|
|
113
|
+
## Install / build
|
|
114
|
+
|
|
115
|
+
```sh
|
|
116
|
+
pip install c0data-py # the package is c0data-py (PyPI treats c0data as codata); in code it is `import c0`
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
From a checkout, the C core and the shared conformance vectors are git submodules:
|
|
120
|
+
|
|
121
|
+
```sh
|
|
122
|
+
git submodule update --init # pulls in c0-c (the header) and c0-spec
|
|
123
|
+
pip install . # builds the extension
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Requires a C compiler and the Python development headers.
|
|
127
|
+
|
|
128
|
+
## Status
|
|
129
|
+
|
|
130
|
+
Binds the c0-c 1.0 core: validator, zero-copy body/document/container readers
|
|
131
|
+
(files, groups, header rows, positional and keyed records, nested levels,
|
|
132
|
+
references, headings, EM blobs), canonical check, the builder, ETB stream mode
|
|
133
|
+
and pretty form (`pretty_format`/`pretty_parse`). Passes the shared
|
|
134
|
+
conformance vectors from [c0-spec](https://github.com/c0data/c0-spec).
|
|
135
|
+
|
|
136
|
+
Converters (CSV / JSON / C0-DIFF) are not yet wrapped.
|
|
137
|
+
|
|
138
|
+
## Test
|
|
139
|
+
|
|
140
|
+
```sh
|
|
141
|
+
python -m unittest discover -s tests
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## License
|
|
145
|
+
|
|
146
|
+
MIT
|