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.
Files changed (34) hide show
  1. {gcf_python-2.0.0 → gcf_python-2.1.0}/CHANGELOG.md +12 -0
  2. {gcf_python-2.0.0 → gcf_python-2.1.0}/PKG-INFO +1 -1
  3. {gcf_python-2.0.0 → gcf_python-2.1.0}/pyproject.toml +1 -1
  4. {gcf_python-2.0.0 → gcf_python-2.1.0}/src/gcf/decode.py +1 -2
  5. {gcf_python-2.0.0 → gcf_python-2.1.0}/src/gcf/decode_generic.py +21 -0
  6. {gcf_python-2.0.0 → gcf_python-2.1.0}/src/gcf/scalar.py +1 -1
  7. {gcf_python-2.0.0 → gcf_python-2.1.0}/.github/FUNDING.yml +0 -0
  8. {gcf_python-2.0.0 → gcf_python-2.1.0}/.github/workflows/ci.yml +0 -0
  9. {gcf_python-2.0.0 → gcf_python-2.1.0}/.github/workflows/publish.yml +0 -0
  10. {gcf_python-2.0.0 → gcf_python-2.1.0}/.gitignore +0 -0
  11. {gcf_python-2.0.0 → gcf_python-2.1.0}/LICENSE +0 -0
  12. {gcf_python-2.0.0 → gcf_python-2.1.0}/README.md +0 -0
  13. {gcf_python-2.0.0 → gcf_python-2.1.0}/src/gcf/__init__.py +0 -0
  14. {gcf_python-2.0.0 → gcf_python-2.1.0}/src/gcf/__main__.py +0 -0
  15. {gcf_python-2.0.0 → gcf_python-2.1.0}/src/gcf/cli.py +0 -0
  16. {gcf_python-2.0.0 → gcf_python-2.1.0}/src/gcf/constants.py +0 -0
  17. {gcf_python-2.0.0 → gcf_python-2.1.0}/src/gcf/delta.py +0 -0
  18. {gcf_python-2.0.0 → gcf_python-2.1.0}/src/gcf/encode.py +0 -0
  19. {gcf_python-2.0.0 → gcf_python-2.1.0}/src/gcf/generic.py +0 -0
  20. {gcf_python-2.0.0 → gcf_python-2.1.0}/src/gcf/session.py +0 -0
  21. {gcf_python-2.0.0 → gcf_python-2.1.0}/src/gcf/stream.py +0 -0
  22. {gcf_python-2.0.0 → gcf_python-2.1.0}/src/gcf/stream_generic.py +0 -0
  23. {gcf_python-2.0.0 → gcf_python-2.1.0}/src/gcf/types.py +0 -0
  24. {gcf_python-2.0.0 → gcf_python-2.1.0}/tests/__init__.py +0 -0
  25. {gcf_python-2.0.0 → gcf_python-2.1.0}/tests/test_conformance_v2.py +0 -0
  26. {gcf_python-2.0.0 → gcf_python-2.1.0}/tests/test_decode.py +0 -0
  27. {gcf_python-2.0.0 → gcf_python-2.1.0}/tests/test_delta.py +0 -0
  28. {gcf_python-2.0.0 → gcf_python-2.1.0}/tests/test_encode.py +0 -0
  29. {gcf_python-2.0.0 → gcf_python-2.1.0}/tests/test_generic.py +0 -0
  30. {gcf_python-2.0.0 → gcf_python-2.1.0}/tests/test_roundtrip.py +0 -0
  31. {gcf_python-2.0.0 → gcf_python-2.1.0}/tests/test_roundtrip_v2.py +0 -0
  32. {gcf_python-2.0.0 → gcf_python-2.1.0}/tests/test_session.py +0 -0
  33. {gcf_python-2.0.0 → gcf_python-2.1.0}/tests/test_stream.py +0 -0
  34. {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.0.0
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.0.0"
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
- if not p.tool:
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