gcf-python 2.5.2__tar.gz → 2.6.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.5.2 → gcf_python-2.6.0}/CHANGELOG.md +8 -0
- {gcf_python-2.5.2 → gcf_python-2.6.0}/PKG-INFO +3 -3
- {gcf_python-2.5.2 → gcf_python-2.6.0}/README.md +1 -1
- {gcf_python-2.5.2 → gcf_python-2.6.0}/pyproject.toml +1 -1
- {gcf_python-2.5.2 → gcf_python-2.6.0}/src/gcf/__init__.py +1 -1
- {gcf_python-2.5.2 → gcf_python-2.6.0}/src/gcf/scalar.py +32 -8
- {gcf_python-2.5.2 → gcf_python-2.6.0}/tests/test_conformance_v2.py +17 -0
- {gcf_python-2.5.2 → gcf_python-2.6.0}/.github/FUNDING.yml +0 -0
- {gcf_python-2.5.2 → gcf_python-2.6.0}/.github/workflows/ci.yml +0 -0
- {gcf_python-2.5.2 → gcf_python-2.6.0}/.github/workflows/publish.yml +0 -0
- {gcf_python-2.5.2 → gcf_python-2.6.0}/.gitignore +0 -0
- {gcf_python-2.5.2 → gcf_python-2.6.0}/LICENSE +0 -0
- {gcf_python-2.5.2 → gcf_python-2.6.0}/assets/divider-wave-2.png +0 -0
- {gcf_python-2.5.2 → gcf_python-2.6.0}/assets/divider.png +0 -0
- {gcf_python-2.5.2 → gcf_python-2.6.0}/assets/gcf-hero-wire-delta.png +0 -0
- {gcf_python-2.5.2 → gcf_python-2.6.0}/assets/gcf-python-diagram.png +0 -0
- {gcf_python-2.5.2 → gcf_python-2.6.0}/src/gcf/__main__.py +0 -0
- {gcf_python-2.5.2 → gcf_python-2.6.0}/src/gcf/cli.py +0 -0
- {gcf_python-2.5.2 → gcf_python-2.6.0}/src/gcf/constants.py +0 -0
- {gcf_python-2.5.2 → gcf_python-2.6.0}/src/gcf/decode.py +0 -0
- {gcf_python-2.5.2 → gcf_python-2.6.0}/src/gcf/decode_generic.py +0 -0
- {gcf_python-2.5.2 → gcf_python-2.6.0}/src/gcf/delta.py +0 -0
- {gcf_python-2.5.2 → gcf_python-2.6.0}/src/gcf/encode.py +0 -0
- {gcf_python-2.5.2 → gcf_python-2.6.0}/src/gcf/generic.py +0 -0
- {gcf_python-2.5.2 → gcf_python-2.6.0}/src/gcf/generic_delta.py +0 -0
- {gcf_python-2.5.2 → gcf_python-2.6.0}/src/gcf/keyed_map.py +0 -0
- {gcf_python-2.5.2 → gcf_python-2.6.0}/src/gcf/packroot.py +0 -0
- {gcf_python-2.5.2 → gcf_python-2.6.0}/src/gcf/session.py +0 -0
- {gcf_python-2.5.2 → gcf_python-2.6.0}/src/gcf/stream.py +0 -0
- {gcf_python-2.5.2 → gcf_python-2.6.0}/src/gcf/stream_generic.py +0 -0
- {gcf_python-2.5.2 → gcf_python-2.6.0}/src/gcf/types.py +0 -0
- {gcf_python-2.5.2 → gcf_python-2.6.0}/tests/__init__.py +0 -0
- {gcf_python-2.5.2 → gcf_python-2.6.0}/tests/test_decode.py +0 -0
- {gcf_python-2.5.2 → gcf_python-2.6.0}/tests/test_delta.py +0 -0
- {gcf_python-2.5.2 → gcf_python-2.6.0}/tests/test_encode.py +0 -0
- {gcf_python-2.5.2 → gcf_python-2.6.0}/tests/test_generic.py +0 -0
- {gcf_python-2.5.2 → gcf_python-2.6.0}/tests/test_generic_delta.py +0 -0
- {gcf_python-2.5.2 → gcf_python-2.6.0}/tests/test_generic_delta_fuzz.py +0 -0
- {gcf_python-2.5.2 → gcf_python-2.6.0}/tests/test_generic_delta_session.py +0 -0
- {gcf_python-2.5.2 → gcf_python-2.6.0}/tests/test_keyed_map_fuzz.py +0 -0
- {gcf_python-2.5.2 → gcf_python-2.6.0}/tests/test_roundtrip.py +0 -0
- {gcf_python-2.5.2 → gcf_python-2.6.0}/tests/test_roundtrip_v2.py +0 -0
- {gcf_python-2.5.2 → gcf_python-2.6.0}/tests/test_session.py +0 -0
- {gcf_python-2.5.2 → gcf_python-2.6.0}/tests/test_stream.py +0 -0
- {gcf_python-2.5.2 → gcf_python-2.6.0}/tests/test_stream_fielddecl.py +0 -0
- {gcf_python-2.5.2 → gcf_python-2.6.0}/tests/test_stream_generic.py +0 -0
- {gcf_python-2.5.2 → gcf_python-2.6.0}/uv.lock +0 -0
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v2.6.0 (2026-08-14)
|
|
4
|
+
|
|
5
|
+
- **Numeric domain (spec v3.5.3, SPEC 2.3.2).** Specifies the canonical numeric domain as signed `int64` for integers and IEEE-754 double for non-integers. Earlier versions left integers beyond the double-exact range (2^53) to the host numeric type; this version parses integer literals to an exact `int64` on decode and on the JSON-to-value bridge, returns an out-of-range error for a value outside `int64` on both decode and encode, and models larger values (unsigned-64 identifiers, exact decimals) as strings. Canonical number formatting aligns to the domain: a double at or above 2^53 renders in exponent notation. Verified against new `numbers/017-024` and `errors-v2/041-042` conformance fixtures and the cross-SDK differential fuzz. An out-of-range value raises `ValueError`.
|
|
6
|
+
|
|
7
|
+
## v2.5.3 (2026-08-10)
|
|
8
|
+
|
|
9
|
+
- **Losslessness fix (spec v3.5.2, SPEC 2.3/2.4).** The number grammar and numeric-like classification now pin digits to ASCII `[0-9]`. Python's `re` compiles `\d` in Unicode mode (matching `\p{Nd}`), so a value like `1.٥` (ASCII `1`, `.`, U+0665) was treated as number-shaped: quoted on encode, and a bare `1.٥` decoded through `float()` to the number `1.5`. That diverged from the ASCII SDKs and silently turned a string into a number when decoding wire produced by another SDK. `\d` is replaced with `[0-9]`; such tokens stay strings and encode bare. Verified against new `scalar/029-031` and `decode/007` conformance fixtures and the cross-SDK differential fuzz.
|
|
10
|
+
|
|
3
11
|
## v2.5.2 (2026-08-09)
|
|
4
12
|
|
|
5
13
|
- **Spec v3.5.1 conformance (SPEC 5, score-rounding errata).** SPEC 5 now pins the graph `score` two-decimal wire form to round-half-to-even on the exact IEEE-754 double, resolving a midpoint divergence in the JavaScript and Kotlin SDKs. This SDK's `f"{score:.2f}"` formatter already rounds half-to-even, so there is no behavior change; re-verified against the new `graph-encode/004_score_midpoint_rounding` conformance fixture.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: gcf-python
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.6.0
|
|
4
4
|
Summary: The AI-native wire format for structured data. 50-92% fewer tokens than JSON, with multi-turn delta encoding for agent loops. 100% comprehension on every frontier model. Zero dependencies.
|
|
5
5
|
Project-URL: Homepage, https://github.com/blackwell-systems/gcf-python
|
|
6
6
|
Project-URL: Documentation, https://gcformat.com/
|
|
@@ -304,7 +304,7 @@ GCF wins 15/16 datasets on the expanded [token efficiency benchmark](https://git
|
|
|
304
304
|
|
|
305
305
|
**Zero runtime dependencies. Permanently.** All six implementations depend only on their language's standard library. No transitive dependencies. No supply chain risk. This is a permanent commitment: GCF will never take on external runtime dependencies. MIT licensed. All implementations support both generic profile (`encodeGeneric`) and graph profile (`encode`). CLI included in all 6 languages.
|
|
306
306
|
|
|
307
|
-
**Specification:** [SPEC v3.5.
|
|
307
|
+
**Specification:** [SPEC v3.5.2 Stable](https://github.com/blackwell-systems/gcf/blob/main/SPEC.md) with 269 conformance fixtures, 43,000,000,000+ lossless round-trips verified across 5 formats and 6 languages. Current versions: Go v1.6.2, TypeScript v2.5.2, Python v2.5.3, Rust v2.5.3, Swift v2.6.2, Kotlin v2.5.2, .NET v0.1.2. Cross-language conformance verified across all seven SDKs.
|
|
308
308
|
|
|
309
309
|
## Adopted by
|
|
310
310
|
|
|
@@ -279,7 +279,7 @@ GCF wins 15/16 datasets on the expanded [token efficiency benchmark](https://git
|
|
|
279
279
|
|
|
280
280
|
**Zero runtime dependencies. Permanently.** All six implementations depend only on their language's standard library. No transitive dependencies. No supply chain risk. This is a permanent commitment: GCF will never take on external runtime dependencies. MIT licensed. All implementations support both generic profile (`encodeGeneric`) and graph profile (`encode`). CLI included in all 6 languages.
|
|
281
281
|
|
|
282
|
-
**Specification:** [SPEC v3.5.
|
|
282
|
+
**Specification:** [SPEC v3.5.2 Stable](https://github.com/blackwell-systems/gcf/blob/main/SPEC.md) with 269 conformance fixtures, 43,000,000,000+ lossless round-trips verified across 5 formats and 6 languages. Current versions: Go v1.6.2, TypeScript v2.5.2, Python v2.5.3, Rust v2.5.3, Swift v2.6.2, Kotlin v2.5.2, .NET v0.1.2. Cross-language conformance verified across all seven SDKs.
|
|
283
283
|
|
|
284
284
|
## Adopted by
|
|
285
285
|
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "gcf-python"
|
|
7
|
-
version = "2.
|
|
7
|
+
version = "2.6.0"
|
|
8
8
|
description = "The AI-native wire format for structured data. 50-92% fewer tokens than JSON, with multi-turn delta encoding for agent loops. 100% comprehension on every frontier model. Zero dependencies."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {text = "MIT"}
|
|
@@ -9,8 +9,10 @@ from typing import Any
|
|
|
9
9
|
# \Z (end of string), not $, so a trailing newline does not count as the end:
|
|
10
10
|
# in Python $ also matches just before a final \n, which would misclassify a
|
|
11
11
|
# string like "5\n" as a number or "W\n" as a bare key and break round-trip.
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
# Digits are ASCII 0-9 (SPEC 2.3): \d is avoided because in Python's re it also
|
|
13
|
+
# matches Unicode decimal digits (\p{Nd}), which would accept e.g. "1.<U+0665>".
|
|
14
|
+
_JSON_NUMBER_RE = re.compile(r"^-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\Z")
|
|
15
|
+
_NUMERIC_LIKE_RE = re.compile(r"^[+-]\.?[0-9]|^\.[0-9]|^0[0-9]")
|
|
14
16
|
_INLINE_ARRAY_RE = re.compile(r"\[[^\]]*\]\s*:")
|
|
15
17
|
_BARE_KEY_RE = re.compile(r"^[a-zA-Z_][a-zA-Z0-9_]*\Z")
|
|
16
18
|
|
|
@@ -91,6 +93,15 @@ def format_scalar(v: Any, delimiter: str = "") -> str:
|
|
|
91
93
|
if isinstance(v, bool):
|
|
92
94
|
return "true" if v else "false"
|
|
93
95
|
if isinstance(v, int) and not isinstance(v, bool):
|
|
96
|
+
# The encoder enforces the int64 domain (SPEC 2.3.2): a Python int is
|
|
97
|
+
# arbitrary-precision, so a host integer outside int64 is rejected here
|
|
98
|
+
# rather than emitted as a bare token the decoder would reject.
|
|
99
|
+
if v < -(2**63) or v > 2**63 - 1:
|
|
100
|
+
raise ValueError(
|
|
101
|
+
f"out_of_range: integer {v} is outside the canonical int64 "
|
|
102
|
+
"domain [-9223372036854775808, 9223372036854775807]; "
|
|
103
|
+
"model larger values as strings (SPEC 2.3.2)"
|
|
104
|
+
)
|
|
94
105
|
return str(v)
|
|
95
106
|
if isinstance(v, float):
|
|
96
107
|
return format_number(v)
|
|
@@ -109,7 +120,12 @@ def format_number(f: float) -> str:
|
|
|
109
120
|
# Negative zero canonicalizes to 0 (SPEC 2.3.1): -0.0 equals 0.0 by value.
|
|
110
121
|
return "0"
|
|
111
122
|
a = abs(f)
|
|
112
|
-
|
|
123
|
+
# Plain decimal only below 2^53. Every double at or above 2^53 is integer-valued,
|
|
124
|
+
# so a plain rendering would emit a bare-integer token: indistinguishable from an
|
|
125
|
+
# int64 on the wire and beyond the binary64 safe-integer range (2^53-1), so a
|
|
126
|
+
# JavaScript decoder rejects it under its default policy. Exponent shape keeps bare
|
|
127
|
+
# tokens int64 and decimal/exponent tokens doubles (SPEC 2.3.1). Ints format above.
|
|
128
|
+
if 1e-6 <= a < 2**53:
|
|
113
129
|
# Use repr for shortest round-trippable form.
|
|
114
130
|
s = repr(f)
|
|
115
131
|
# If repr chose scientific notation, convert to plain decimal.
|
|
@@ -163,12 +179,20 @@ def parse_scalar(s: str, tabular_context: bool = False) -> Any:
|
|
|
163
179
|
if s == "false":
|
|
164
180
|
return False
|
|
165
181
|
if _JSON_NUMBER_RE.match(s):
|
|
182
|
+
# Token shape follows domain (SPEC 2.3.2): a bare-integer literal (no
|
|
183
|
+
# fraction, no exponent) is an int64-domain integer parsed exactly, not
|
|
184
|
+
# routed through float(); a decimal or exponent literal is a double.
|
|
185
|
+
if "." not in s and "e" not in s and "E" not in s:
|
|
186
|
+
n = int(s)
|
|
187
|
+
if n < -(2**63) or n > 2**63 - 1:
|
|
188
|
+
raise ValueError(
|
|
189
|
+
f"out_of_range: integer {s} is outside the canonical int64 "
|
|
190
|
+
"domain [-9223372036854775808, 9223372036854775807]; "
|
|
191
|
+
"model larger values as strings (SPEC 2.3.2)"
|
|
192
|
+
)
|
|
193
|
+
return n
|
|
166
194
|
try:
|
|
167
|
-
|
|
168
|
-
if "." not in s and "e" not in s and "E" not in s:
|
|
169
|
-
if abs(f) <= 2**53:
|
|
170
|
-
return int(f)
|
|
171
|
-
return f
|
|
195
|
+
return float(s)
|
|
172
196
|
except ValueError:
|
|
173
197
|
pass
|
|
174
198
|
return s
|
|
@@ -183,6 +183,23 @@ def test_conformance(rel_path, data):
|
|
|
183
183
|
with pytest.raises((ValueError, Exception)):
|
|
184
184
|
decode_generic(data["input"])
|
|
185
185
|
|
|
186
|
+
elif op == "roundtrip-wire":
|
|
187
|
+
# Input and expected are wire strings: decode then re-encode and require the
|
|
188
|
+
# result to equal the input wire. The value never becomes a host number, so a
|
|
189
|
+
# value that a JSON parser would float (an integer beyond 2^53) is pinned here.
|
|
190
|
+
decoded = decode_generic(data["input"])
|
|
191
|
+
reencoded = encode_generic(decoded)
|
|
192
|
+
assert reencoded == data["expected"], (
|
|
193
|
+
f"wire idempotence mismatch:\n got: {reencoded!r}\n exp: {data['expected']!r}"
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
elif op == "encode-error":
|
|
197
|
+
# Input is a JSON value (encode-side) out of the numeric domain; encoding it
|
|
198
|
+
# must raise. In Python the JSON parser preserves big integers exactly, so the
|
|
199
|
+
# encoder is the domain-enforcement site.
|
|
200
|
+
with pytest.raises((ValueError, Exception)):
|
|
201
|
+
encode_generic(data["input"])
|
|
202
|
+
|
|
186
203
|
elif op == "generic-pack-root":
|
|
187
204
|
from gcf.generic_delta import GenericSet, generic_pack_root
|
|
188
205
|
|
|
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
|
|
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
|