gcf-python 2.0.0__tar.gz → 2.1.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.
- {gcf_python-2.0.0 → gcf_python-2.1.1}/CHANGELOG.md +12 -0
- {gcf_python-2.0.0 → gcf_python-2.1.1}/PKG-INFO +21 -21
- {gcf_python-2.0.0 → gcf_python-2.1.1}/README.md +20 -20
- gcf_python-2.1.1/assets/gcf-python-diagram.png +0 -0
- {gcf_python-2.0.0 → gcf_python-2.1.1}/pyproject.toml +1 -1
- {gcf_python-2.0.0 → gcf_python-2.1.1}/src/gcf/decode.py +1 -2
- {gcf_python-2.0.0 → gcf_python-2.1.1}/src/gcf/decode_generic.py +39 -9
- {gcf_python-2.0.0 → gcf_python-2.1.1}/src/gcf/scalar.py +4 -1
- {gcf_python-2.0.0 → gcf_python-2.1.1}/tests/test_conformance_v2.py +10 -0
- {gcf_python-2.0.0 → gcf_python-2.1.1}/.github/FUNDING.yml +0 -0
- {gcf_python-2.0.0 → gcf_python-2.1.1}/.github/workflows/ci.yml +0 -0
- {gcf_python-2.0.0 → gcf_python-2.1.1}/.github/workflows/publish.yml +0 -0
- {gcf_python-2.0.0 → gcf_python-2.1.1}/.gitignore +0 -0
- {gcf_python-2.0.0 → gcf_python-2.1.1}/LICENSE +0 -0
- {gcf_python-2.0.0 → gcf_python-2.1.1}/src/gcf/__init__.py +0 -0
- {gcf_python-2.0.0 → gcf_python-2.1.1}/src/gcf/__main__.py +0 -0
- {gcf_python-2.0.0 → gcf_python-2.1.1}/src/gcf/cli.py +0 -0
- {gcf_python-2.0.0 → gcf_python-2.1.1}/src/gcf/constants.py +0 -0
- {gcf_python-2.0.0 → gcf_python-2.1.1}/src/gcf/delta.py +0 -0
- {gcf_python-2.0.0 → gcf_python-2.1.1}/src/gcf/encode.py +0 -0
- {gcf_python-2.0.0 → gcf_python-2.1.1}/src/gcf/generic.py +0 -0
- {gcf_python-2.0.0 → gcf_python-2.1.1}/src/gcf/session.py +0 -0
- {gcf_python-2.0.0 → gcf_python-2.1.1}/src/gcf/stream.py +0 -0
- {gcf_python-2.0.0 → gcf_python-2.1.1}/src/gcf/stream_generic.py +0 -0
- {gcf_python-2.0.0 → gcf_python-2.1.1}/src/gcf/types.py +0 -0
- {gcf_python-2.0.0 → gcf_python-2.1.1}/tests/__init__.py +0 -0
- {gcf_python-2.0.0 → gcf_python-2.1.1}/tests/test_decode.py +0 -0
- {gcf_python-2.0.0 → gcf_python-2.1.1}/tests/test_delta.py +0 -0
- {gcf_python-2.0.0 → gcf_python-2.1.1}/tests/test_encode.py +0 -0
- {gcf_python-2.0.0 → gcf_python-2.1.1}/tests/test_generic.py +0 -0
- {gcf_python-2.0.0 → gcf_python-2.1.1}/tests/test_roundtrip.py +0 -0
- {gcf_python-2.0.0 → gcf_python-2.1.1}/tests/test_roundtrip_v2.py +0 -0
- {gcf_python-2.0.0 → gcf_python-2.1.1}/tests/test_session.py +0 -0
- {gcf_python-2.0.0 → gcf_python-2.1.1}/tests/test_stream.py +0 -0
- {gcf_python-2.0.0 → gcf_python-2.1.1}/tests/test_stream_generic.py +0 -0
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v2.1.0 (2026-06-14)
|
|
4
|
+
|
|
5
|
+
### Spec v3.1
|
|
6
|
+
|
|
7
|
+
- `tool` field in graph profile header is now optional (SHOULD be present for MCP, not required)
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
- Quote strings containing commas (conformance: `inline-schema/006_inline_with_quoted_values`)
|
|
12
|
+
- Decode v2-format indented attachments in tabular rows (conformance: `decode/002_attachment`)
|
|
13
|
+
- Reject duplicate attachments on the same row (conformance: `errors-v2/027_duplicate_attachment`)
|
|
14
|
+
|
|
3
15
|
## v2.0.0 (2026-06-12)
|
|
4
16
|
|
|
5
17
|
### Breaking Changes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gcf-python
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.1.1
|
|
4
4
|
Summary: Drop-in JSON replacement for AI pipelines. 79% fewer tokens. 90.7% comprehension across 10 models. Zero dependencies.
|
|
5
5
|
Project-URL: Homepage, https://github.com/blackwell-systems/gcf-python
|
|
6
6
|
Project-URL: Documentation, https://blackwell-systems.github.io/gcf/
|
|
@@ -217,26 +217,26 @@ Works on dicts, lists, and primitives. Lists of uniform dicts get tabular rows.
|
|
|
217
217
|
|
|
218
218
|
GCF wins 13/15 datasets on the expanded [token efficiency benchmark](https://github.com/blackwell-systems/toon/tree/gcf-comparison). Full results: [gcformat.com/guide/benchmarks](https://gcformat.com/guide/benchmarks.html)
|
|
219
219
|
|
|
220
|
-
##
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
- [
|
|
225
|
-
- [
|
|
226
|
-
- [
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
- [
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
220
|
+
## Implementations
|
|
221
|
+
|
|
222
|
+
| Language | Package | Repository |
|
|
223
|
+
|----------|---------|-----------|
|
|
224
|
+
| Go | `go get github.com/blackwell-systems/gcf-go` | [gcf-go](https://github.com/blackwell-systems/gcf-go) |
|
|
225
|
+
| TypeScript | `npm install @blackwell-systems/gcf` | [gcf-typescript](https://github.com/blackwell-systems/gcf-typescript) |
|
|
226
|
+
| Python | `pip install gcf-python` | [gcf-python](https://github.com/blackwell-systems/gcf-python) |
|
|
227
|
+
| Rust | `cargo add gcf` | [gcf-rust](https://github.com/blackwell-systems/gcf-rust) |
|
|
228
|
+
| Swift | Swift Package Manager | [gcf-swift](https://github.com/blackwell-systems/gcf-swift) |
|
|
229
|
+
| Kotlin | JitPack | [gcf-kotlin](https://github.com/blackwell-systems/gcf-kotlin) |
|
|
230
|
+
| MCP Proxy | `pip install gcf-proxy` | [gcf-proxy](https://github.com/blackwell-systems/gcf-proxy) (bidirectional, session dedup, HTTP frontend) |
|
|
231
|
+
| Claude Code Plugin | `/plugin install` | [gcf-claude-plugin](https://github.com/blackwell-systems/gcf-claude-plugin) (one-command install, session stats hook) |
|
|
232
|
+
| Codex Plugin | `codex plugin add` | [gcf-codex-plugin](https://github.com/blackwell-systems/gcf-codex-plugin) (one-command install, session stats hook) |
|
|
233
|
+
| VS Code | `ext install blackwell-systems.gcf-vscode` | [gcf-vscode](https://marketplace.visualstudio.com/items?itemName=blackwell-systems.gcf-vscode) (syntax highlighting) |
|
|
234
|
+
| n8n | `npm install n8n-nodes-gcf` | [gcf-n8n-nodes](https://github.com/blackwell-systems/gcf-n8n-nodes) (workflow encode/decode) |
|
|
235
|
+
| Tree-sitter | `npm install tree-sitter-gcf` | [tree-sitter-gcf](https://github.com/blackwell-systems/tree-sitter-gcf) |
|
|
236
|
+
|
|
237
|
+
Zero runtime dependencies. MIT licensed. All implementations support both generic profile (`encodeGeneric`) and graph profile (`encode`). CLI included in all 6 languages.
|
|
238
|
+
|
|
239
|
+
**Specification:** [SPEC v3.1 Stable](https://github.com/blackwell-systems/gcf/blob/main/SPEC.md) with 157 conformance fixtures, 33,000,000,000+ lossless round-trips verified across 5 formats and 6 languages. All implementations at v2.1.0+ (Go v1.2.0). Cross-language 6x6 matrix verified.
|
|
240
240
|
|
|
241
241
|
## License
|
|
242
242
|
|
|
@@ -192,26 +192,26 @@ Works on dicts, lists, and primitives. Lists of uniform dicts get tabular rows.
|
|
|
192
192
|
|
|
193
193
|
GCF wins 13/15 datasets on the expanded [token efficiency benchmark](https://github.com/blackwell-systems/toon/tree/gcf-comparison). Full results: [gcformat.com/guide/benchmarks](https://gcformat.com/guide/benchmarks.html)
|
|
194
194
|
|
|
195
|
-
##
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
- [
|
|
200
|
-
- [
|
|
201
|
-
- [
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
- [
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
195
|
+
## Implementations
|
|
196
|
+
|
|
197
|
+
| Language | Package | Repository |
|
|
198
|
+
|----------|---------|-----------|
|
|
199
|
+
| Go | `go get github.com/blackwell-systems/gcf-go` | [gcf-go](https://github.com/blackwell-systems/gcf-go) |
|
|
200
|
+
| TypeScript | `npm install @blackwell-systems/gcf` | [gcf-typescript](https://github.com/blackwell-systems/gcf-typescript) |
|
|
201
|
+
| Python | `pip install gcf-python` | [gcf-python](https://github.com/blackwell-systems/gcf-python) |
|
|
202
|
+
| Rust | `cargo add gcf` | [gcf-rust](https://github.com/blackwell-systems/gcf-rust) |
|
|
203
|
+
| Swift | Swift Package Manager | [gcf-swift](https://github.com/blackwell-systems/gcf-swift) |
|
|
204
|
+
| Kotlin | JitPack | [gcf-kotlin](https://github.com/blackwell-systems/gcf-kotlin) |
|
|
205
|
+
| MCP Proxy | `pip install gcf-proxy` | [gcf-proxy](https://github.com/blackwell-systems/gcf-proxy) (bidirectional, session dedup, HTTP frontend) |
|
|
206
|
+
| Claude Code Plugin | `/plugin install` | [gcf-claude-plugin](https://github.com/blackwell-systems/gcf-claude-plugin) (one-command install, session stats hook) |
|
|
207
|
+
| Codex Plugin | `codex plugin add` | [gcf-codex-plugin](https://github.com/blackwell-systems/gcf-codex-plugin) (one-command install, session stats hook) |
|
|
208
|
+
| VS Code | `ext install blackwell-systems.gcf-vscode` | [gcf-vscode](https://marketplace.visualstudio.com/items?itemName=blackwell-systems.gcf-vscode) (syntax highlighting) |
|
|
209
|
+
| n8n | `npm install n8n-nodes-gcf` | [gcf-n8n-nodes](https://github.com/blackwell-systems/gcf-n8n-nodes) (workflow encode/decode) |
|
|
210
|
+
| Tree-sitter | `npm install tree-sitter-gcf` | [tree-sitter-gcf](https://github.com/blackwell-systems/tree-sitter-gcf) |
|
|
211
|
+
|
|
212
|
+
Zero runtime dependencies. MIT licensed. All implementations support both generic profile (`encodeGeneric`) and graph profile (`encode`). CLI included in all 6 languages.
|
|
213
|
+
|
|
214
|
+
**Specification:** [SPEC v3.1 Stable](https://github.com/blackwell-systems/gcf/blob/main/SPEC.md) with 157 conformance fixtures, 33,000,000,000+ lossless round-trips verified across 5 formats and 6 languages. All implementations at v2.1.0+ (Go v1.2.0). Cross-language 6x6 matrix verified.
|
|
215
215
|
|
|
216
216
|
## License
|
|
217
217
|
|
|
Binary file
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "gcf-python"
|
|
7
|
-
version = "2.
|
|
7
|
+
version = "2.1.1"
|
|
8
8
|
description = "Drop-in JSON replacement for AI pipelines. 79% fewer tokens. 90.7% comprehension across 10 models. Zero dependencies."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {text = "MIT"}
|
|
@@ -34,8 +34,7 @@ def decode(input_text: str) -> Payload:
|
|
|
34
34
|
raise DecodeError(f"invalid header, expected 'GCF ...' got {header!r}")
|
|
35
35
|
_parse_header(header[4:], p)
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
raise DecodeError("missing_tool: header missing required 'tool' field")
|
|
37
|
+
# v3.1: tool field is optional (SHOULD be present for MCP tool responses, not required).
|
|
39
38
|
|
|
40
39
|
# Detect delta mode.
|
|
41
40
|
is_delta = "delta=true" in header
|
|
@@ -151,6 +151,17 @@ def _parse_object_body(
|
|
|
151
151
|
i += consumed
|
|
152
152
|
continue
|
|
153
153
|
|
|
154
|
+
# Key=value. Check before inline array so bracket patterns in quoted
|
|
155
|
+
# values (e.g. text="ERR[404]: Not Found") are not misinterpreted.
|
|
156
|
+
eq_idx = _find_kv_split(content)
|
|
157
|
+
if eq_idx > 0:
|
|
158
|
+
name = _parse_key_from_header(content[:eq_idx])
|
|
159
|
+
_check_dup(out, name)
|
|
160
|
+
out[name] = parse_scalar(content[eq_idx + 1:])
|
|
161
|
+
i += 1
|
|
162
|
+
continue
|
|
163
|
+
|
|
164
|
+
# Inline array (e.g. items[3]: a,b,c). Only reached if no = found.
|
|
154
165
|
if not content.startswith("@") and not content.startswith("##"):
|
|
155
166
|
bracket_idx = content.find("[")
|
|
156
167
|
if bracket_idx > 0:
|
|
@@ -166,14 +177,6 @@ def _parse_object_body(
|
|
|
166
177
|
i += 1
|
|
167
178
|
continue
|
|
168
179
|
|
|
169
|
-
eq_idx = _find_kv_split(content)
|
|
170
|
-
if eq_idx > 0:
|
|
171
|
-
name = _parse_key_from_header(content[:eq_idx])
|
|
172
|
-
_check_dup(out, name)
|
|
173
|
-
out[name] = parse_scalar(content[eq_idx + 1:])
|
|
174
|
-
i += 1
|
|
175
|
-
continue
|
|
176
|
-
|
|
177
180
|
i += 1
|
|
178
181
|
return i - start
|
|
179
182
|
|
|
@@ -191,7 +194,13 @@ def _find_kv_split(s: str) -> int:
|
|
|
191
194
|
return i + 1 if i + 1 < len(s) and s[i + 1] == "=" else -1
|
|
192
195
|
i += 1
|
|
193
196
|
return -1
|
|
194
|
-
|
|
197
|
+
eq_idx = s.find("=")
|
|
198
|
+
if eq_idx < 0:
|
|
199
|
+
return -1
|
|
200
|
+
bracket_idx = s.find("[")
|
|
201
|
+
if bracket_idx >= 0 and bracket_idx < eq_idx:
|
|
202
|
+
return -1
|
|
203
|
+
return eq_idx
|
|
195
204
|
|
|
196
205
|
|
|
197
206
|
def _parse_key_from_header(s: str) -> str:
|
|
@@ -442,6 +451,10 @@ def _parse_tabular_body(
|
|
|
442
451
|
if a_content is None:
|
|
443
452
|
break
|
|
444
453
|
|
|
454
|
+
# Handle v2 indented attachments: strip one extra indent level.
|
|
455
|
+
if not a_content.startswith(".") and a_content.startswith(" ."):
|
|
456
|
+
a_content = a_content[2:]
|
|
457
|
+
|
|
445
458
|
# Line starts with ".": traditional or prefixed inline.
|
|
446
459
|
if a_content.startswith("."):
|
|
447
460
|
rest = a_content[1:]
|
|
@@ -459,6 +472,8 @@ def _parse_tabular_body(
|
|
|
459
472
|
p = parse_scalar(inline_vals[k], tabular_context=True)
|
|
460
473
|
if p is not MISSING:
|
|
461
474
|
obj[inf] = p
|
|
475
|
+
if att_name in attachment_values:
|
|
476
|
+
raise ValueError(f"duplicate_attachment: {att_name}")
|
|
462
477
|
attachment_values[att_name] = obj
|
|
463
478
|
i += 1
|
|
464
479
|
continue
|
|
@@ -469,6 +484,8 @@ def _parse_tabular_body(
|
|
|
469
484
|
)
|
|
470
485
|
if not rows and parsed_fields:
|
|
471
486
|
shared_array_schemas[att_name_t] = parsed_fields
|
|
487
|
+
if att_name_t in attachment_values:
|
|
488
|
+
raise ValueError(f"duplicate_attachment: {att_name_t}")
|
|
472
489
|
attachment_values[att_name_t] = att_val
|
|
473
490
|
i += consumed
|
|
474
491
|
continue
|
|
@@ -503,6 +520,19 @@ def _parse_tabular_body(
|
|
|
503
520
|
if f not in attachment_values:
|
|
504
521
|
raise ValueError(f"missing_attachment: {f}")
|
|
505
522
|
|
|
523
|
+
# Check for extra attachment lines after all fields resolved (duplicate).
|
|
524
|
+
if i < len(lines):
|
|
525
|
+
extra_line = lines[i]
|
|
526
|
+
extra_content = ""
|
|
527
|
+
if depth == 0 or extra_line.startswith(ind):
|
|
528
|
+
extra_content = extra_line[len(ind):] if depth > 0 else extra_line
|
|
529
|
+
if not extra_content.startswith(".") and extra_content.startswith(" ."):
|
|
530
|
+
extra_content = extra_content[2:]
|
|
531
|
+
if extra_content.startswith("."):
|
|
532
|
+
extra_name, _ = _parse_attachment_name(extra_content[1:])
|
|
533
|
+
if extra_name in attachment_values:
|
|
534
|
+
raise ValueError(f"duplicate_attachment: {extra_name}")
|
|
535
|
+
|
|
506
536
|
if not row_has_id or not all_att_fields:
|
|
507
537
|
att_indent = ind + " "
|
|
508
538
|
if i < len(lines) and lines[i].startswith(att_indent):
|
|
@@ -8,6 +8,7 @@ from typing import Any
|
|
|
8
8
|
|
|
9
9
|
_JSON_NUMBER_RE = re.compile(r"^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$")
|
|
10
10
|
_NUMERIC_LIKE_RE = re.compile(r"^[+-]\.?\d|^\.\d|^0\d")
|
|
11
|
+
_INLINE_ARRAY_RE = re.compile(r"\[[^\]]*\]\s*:")
|
|
11
12
|
_BARE_KEY_RE = re.compile(r"^[a-zA-Z_][a-zA-Z0-9_]*$")
|
|
12
13
|
|
|
13
14
|
|
|
@@ -36,9 +37,11 @@ def needs_quote(s: str) -> bool:
|
|
|
36
37
|
return True
|
|
37
38
|
if s[0] in ("#", "@", "."):
|
|
38
39
|
return True
|
|
40
|
+
if _INLINE_ARRAY_RE.search(s):
|
|
41
|
+
return True
|
|
39
42
|
for c in s:
|
|
40
43
|
o = ord(c)
|
|
41
|
-
if c in ('"', "\\", "|") or o < 0x20 or c in ("\n", "\r"):
|
|
44
|
+
if c in ('"', "\\", "|", ",") or o < 0x20 or c in ("\n", "\r"):
|
|
42
45
|
return True
|
|
43
46
|
# C1 control characters
|
|
44
47
|
if 0x80 <= o <= 0x9F:
|
|
@@ -97,6 +97,16 @@ def test_conformance(rel_path, data):
|
|
|
97
97
|
_json_norm(data["expected"]), _json_norm(got)
|
|
98
98
|
), f"decode mismatch:\n got: {got}\n exp: {data['expected']}"
|
|
99
99
|
|
|
100
|
+
elif op == "roundtrip":
|
|
101
|
+
# Encode, verify GCF output matches expected, then decode and verify round-trip.
|
|
102
|
+
got = encode_generic(data["input"])
|
|
103
|
+
if isinstance(data["expected"], str):
|
|
104
|
+
assert got == data["expected"], f"encode mismatch:\n got: {got!r}\n exp: {data['expected']!r}"
|
|
105
|
+
decoded = decode_generic(got)
|
|
106
|
+
assert _structural_equal(
|
|
107
|
+
_json_norm(data["input"]), _json_norm(decoded)
|
|
108
|
+
), f"round-trip mismatch:\n input: {data['input']}\n decoded: {decoded}"
|
|
109
|
+
|
|
100
110
|
elif op == "error":
|
|
101
111
|
# v3 decoder may surface different error categories for same invalid input.
|
|
102
112
|
# The requirement is that it rejects.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|