gcf-python 2.0.0__tar.gz → 2.1.0__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.0}/CHANGELOG.md +12 -0
- {gcf_python-2.0.0 → gcf_python-2.1.0}/PKG-INFO +1 -1
- {gcf_python-2.0.0 → gcf_python-2.1.0}/pyproject.toml +1 -1
- {gcf_python-2.0.0 → gcf_python-2.1.0}/src/gcf/decode.py +1 -2
- {gcf_python-2.0.0 → gcf_python-2.1.0}/src/gcf/decode_generic.py +21 -0
- {gcf_python-2.0.0 → gcf_python-2.1.0}/src/gcf/scalar.py +1 -1
- {gcf_python-2.0.0 → gcf_python-2.1.0}/.github/FUNDING.yml +0 -0
- {gcf_python-2.0.0 → gcf_python-2.1.0}/.github/workflows/ci.yml +0 -0
- {gcf_python-2.0.0 → gcf_python-2.1.0}/.github/workflows/publish.yml +0 -0
- {gcf_python-2.0.0 → gcf_python-2.1.0}/.gitignore +0 -0
- {gcf_python-2.0.0 → gcf_python-2.1.0}/LICENSE +0 -0
- {gcf_python-2.0.0 → gcf_python-2.1.0}/README.md +0 -0
- {gcf_python-2.0.0 → gcf_python-2.1.0}/src/gcf/__init__.py +0 -0
- {gcf_python-2.0.0 → gcf_python-2.1.0}/src/gcf/__main__.py +0 -0
- {gcf_python-2.0.0 → gcf_python-2.1.0}/src/gcf/cli.py +0 -0
- {gcf_python-2.0.0 → gcf_python-2.1.0}/src/gcf/constants.py +0 -0
- {gcf_python-2.0.0 → gcf_python-2.1.0}/src/gcf/delta.py +0 -0
- {gcf_python-2.0.0 → gcf_python-2.1.0}/src/gcf/encode.py +0 -0
- {gcf_python-2.0.0 → gcf_python-2.1.0}/src/gcf/generic.py +0 -0
- {gcf_python-2.0.0 → gcf_python-2.1.0}/src/gcf/session.py +0 -0
- {gcf_python-2.0.0 → gcf_python-2.1.0}/src/gcf/stream.py +0 -0
- {gcf_python-2.0.0 → gcf_python-2.1.0}/src/gcf/stream_generic.py +0 -0
- {gcf_python-2.0.0 → gcf_python-2.1.0}/src/gcf/types.py +0 -0
- {gcf_python-2.0.0 → gcf_python-2.1.0}/tests/__init__.py +0 -0
- {gcf_python-2.0.0 → gcf_python-2.1.0}/tests/test_conformance_v2.py +0 -0
- {gcf_python-2.0.0 → gcf_python-2.1.0}/tests/test_decode.py +0 -0
- {gcf_python-2.0.0 → gcf_python-2.1.0}/tests/test_delta.py +0 -0
- {gcf_python-2.0.0 → gcf_python-2.1.0}/tests/test_encode.py +0 -0
- {gcf_python-2.0.0 → gcf_python-2.1.0}/tests/test_generic.py +0 -0
- {gcf_python-2.0.0 → gcf_python-2.1.0}/tests/test_roundtrip.py +0 -0
- {gcf_python-2.0.0 → gcf_python-2.1.0}/tests/test_roundtrip_v2.py +0 -0
- {gcf_python-2.0.0 → gcf_python-2.1.0}/tests/test_session.py +0 -0
- {gcf_python-2.0.0 → gcf_python-2.1.0}/tests/test_stream.py +0 -0
- {gcf_python-2.0.0 → gcf_python-2.1.0}/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.0
|
|
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/
|
|
@@ -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.0"
|
|
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
|
|
@@ -442,6 +442,10 @@ def _parse_tabular_body(
|
|
|
442
442
|
if a_content is None:
|
|
443
443
|
break
|
|
444
444
|
|
|
445
|
+
# Handle v2 indented attachments: strip one extra indent level.
|
|
446
|
+
if not a_content.startswith(".") and a_content.startswith(" ."):
|
|
447
|
+
a_content = a_content[2:]
|
|
448
|
+
|
|
445
449
|
# Line starts with ".": traditional or prefixed inline.
|
|
446
450
|
if a_content.startswith("."):
|
|
447
451
|
rest = a_content[1:]
|
|
@@ -459,6 +463,8 @@ def _parse_tabular_body(
|
|
|
459
463
|
p = parse_scalar(inline_vals[k], tabular_context=True)
|
|
460
464
|
if p is not MISSING:
|
|
461
465
|
obj[inf] = p
|
|
466
|
+
if att_name in attachment_values:
|
|
467
|
+
raise ValueError(f"duplicate_attachment: {att_name}")
|
|
462
468
|
attachment_values[att_name] = obj
|
|
463
469
|
i += 1
|
|
464
470
|
continue
|
|
@@ -469,6 +475,8 @@ def _parse_tabular_body(
|
|
|
469
475
|
)
|
|
470
476
|
if not rows and parsed_fields:
|
|
471
477
|
shared_array_schemas[att_name_t] = parsed_fields
|
|
478
|
+
if att_name_t in attachment_values:
|
|
479
|
+
raise ValueError(f"duplicate_attachment: {att_name_t}")
|
|
472
480
|
attachment_values[att_name_t] = att_val
|
|
473
481
|
i += consumed
|
|
474
482
|
continue
|
|
@@ -503,6 +511,19 @@ def _parse_tabular_body(
|
|
|
503
511
|
if f not in attachment_values:
|
|
504
512
|
raise ValueError(f"missing_attachment: {f}")
|
|
505
513
|
|
|
514
|
+
# Check for extra attachment lines after all fields resolved (duplicate).
|
|
515
|
+
if i < len(lines):
|
|
516
|
+
extra_line = lines[i]
|
|
517
|
+
extra_content = ""
|
|
518
|
+
if depth == 0 or extra_line.startswith(ind):
|
|
519
|
+
extra_content = extra_line[len(ind):] if depth > 0 else extra_line
|
|
520
|
+
if not extra_content.startswith(".") and extra_content.startswith(" ."):
|
|
521
|
+
extra_content = extra_content[2:]
|
|
522
|
+
if extra_content.startswith("."):
|
|
523
|
+
extra_name, _ = _parse_attachment_name(extra_content[1:])
|
|
524
|
+
if extra_name in attachment_values:
|
|
525
|
+
raise ValueError(f"duplicate_attachment: {extra_name}")
|
|
526
|
+
|
|
506
527
|
if not row_has_id or not all_att_fields:
|
|
507
528
|
att_indent = ind + " "
|
|
508
529
|
if i < len(lines) and lines[i].startswith(att_indent):
|
|
@@ -38,7 +38,7 @@ def needs_quote(s: str) -> bool:
|
|
|
38
38
|
return True
|
|
39
39
|
for c in s:
|
|
40
40
|
o = ord(c)
|
|
41
|
-
if c in ('"', "\\", "|") or o < 0x20 or c in ("\n", "\r"):
|
|
41
|
+
if c in ('"', "\\", "|", ",") or o < 0x20 or c in ("\n", "\r"):
|
|
42
42
|
return True
|
|
43
43
|
# C1 control characters
|
|
44
44
|
if 0x80 <= o <= 0x9F:
|
|
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
|
|
File without changes
|
|
File without changes
|