gcf-python 0.1.0__py3-none-any.whl → 0.1.2__py3-none-any.whl
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/__init__.py +3 -1
- gcf/decode.py +2 -0
- gcf/encode.py +2 -0
- gcf/generic.py +151 -0
- gcf/session.py +2 -0
- gcf/types.py +2 -0
- {gcf_python-0.1.0.dist-info → gcf_python-0.1.2.dist-info}/METADATA +28 -3
- gcf_python-0.1.2.dist-info/RECORD +14 -0
- gcf_python-0.1.0.dist-info/RECORD +0 -13
- {gcf_python-0.1.0.dist-info → gcf_python-0.1.2.dist-info}/WHEEL +0 -0
- {gcf_python-0.1.0.dist-info → gcf_python-0.1.2.dist-info}/entry_points.txt +0 -0
- {gcf_python-0.1.0.dist-info → gcf_python-0.1.2.dist-info}/licenses/LICENSE +0 -0
gcf/__init__.py
CHANGED
|
@@ -38,6 +38,7 @@ from .constants import KIND_ABBREV, KIND_EXPAND
|
|
|
38
38
|
from .decode import DecodeError, decode
|
|
39
39
|
from .delta import encode_delta
|
|
40
40
|
from .encode import encode
|
|
41
|
+
from .generic import encode_generic
|
|
41
42
|
from .session import Session, encode_with_session
|
|
42
43
|
from .types import Components, DeltaPayload, Edge, Payload, Symbol
|
|
43
44
|
|
|
@@ -54,7 +55,8 @@ __all__ = [
|
|
|
54
55
|
"decode",
|
|
55
56
|
"encode",
|
|
56
57
|
"encode_delta",
|
|
58
|
+
"encode_generic",
|
|
57
59
|
"encode_with_session",
|
|
58
60
|
]
|
|
59
61
|
|
|
60
|
-
__version__ = "0.1.
|
|
62
|
+
__version__ = "0.1.2"
|
gcf/decode.py
CHANGED
gcf/encode.py
CHANGED
gcf/generic.py
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"""GCF generic encoder: serializes arbitrary Python values into GCF tabular format."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def encode_generic(data: Any) -> str:
|
|
9
|
+
"""Encode any Python value into GCF tabular format.
|
|
10
|
+
|
|
11
|
+
Unlike encode() which handles the graph Payload type, encode_generic()
|
|
12
|
+
works on arbitrary dicts, lists, and primitives using GCF's tabular
|
|
13
|
+
encoding grammar.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
data: Any Python value (dict, list, primitive, or None).
|
|
17
|
+
|
|
18
|
+
Returns:
|
|
19
|
+
GCF-formatted text string.
|
|
20
|
+
"""
|
|
21
|
+
lines: list[str] = []
|
|
22
|
+
_encode_value(data, lines, depth=0)
|
|
23
|
+
return "\n".join(lines) + "\n" if lines else "\n"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _encode_value(value: Any, lines: list[str], depth: int) -> None:
|
|
27
|
+
"""Dispatch encoding based on value type."""
|
|
28
|
+
if isinstance(value, dict):
|
|
29
|
+
_encode_dict(value, lines, depth)
|
|
30
|
+
elif isinstance(value, list):
|
|
31
|
+
_encode_array(value, "items", lines, depth)
|
|
32
|
+
else:
|
|
33
|
+
lines.append(_indent(depth) + _format_value(value))
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _encode_dict(d: dict, lines: list[str], depth: int) -> None:
|
|
37
|
+
"""Encode a dict into key=value pairs with section headers for nested values."""
|
|
38
|
+
prefix = _indent(depth)
|
|
39
|
+
for key, value in d.items():
|
|
40
|
+
if isinstance(value, list):
|
|
41
|
+
_encode_array(value, key, lines, depth)
|
|
42
|
+
elif isinstance(value, dict):
|
|
43
|
+
lines.append(f"{prefix}## {key}")
|
|
44
|
+
_encode_dict(value, lines, depth + 1)
|
|
45
|
+
else:
|
|
46
|
+
lines.append(f"{prefix}{key}={_format_value(value)}")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _encode_array(items: list, name: str, lines: list[str], depth: int) -> None:
|
|
50
|
+
"""Encode a list, using tabular format for uniform dict lists."""
|
|
51
|
+
prefix = _indent(depth)
|
|
52
|
+
|
|
53
|
+
if not items:
|
|
54
|
+
lines.append(f"{prefix}## {name} [0]")
|
|
55
|
+
return
|
|
56
|
+
|
|
57
|
+
if _is_uniform_dict_list(items):
|
|
58
|
+
_encode_tabular(items, name, lines, depth)
|
|
59
|
+
else:
|
|
60
|
+
lines.append(f"{prefix}## {name} [{len(items)}]")
|
|
61
|
+
for i, item in enumerate(items):
|
|
62
|
+
if isinstance(item, dict):
|
|
63
|
+
lines.append(f"{prefix}@{i}")
|
|
64
|
+
_encode_dict(item, lines, depth + 1)
|
|
65
|
+
else:
|
|
66
|
+
lines.append(f"{prefix}@{i} {_format_value(item)}")
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _encode_tabular(items: list[dict], name: str, lines: list[str], depth: int) -> None:
|
|
70
|
+
"""Encode a uniform list of dicts as a tabular section."""
|
|
71
|
+
prefix = _indent(depth)
|
|
72
|
+
|
|
73
|
+
# Collect all keys from the first item to determine field order.
|
|
74
|
+
all_keys = list(items[0].keys())
|
|
75
|
+
primitive_fields = [k for k in all_keys if not isinstance(items[0][k], (dict, list))]
|
|
76
|
+
nested_fields = [k for k in all_keys if isinstance(items[0][k], (dict, list))]
|
|
77
|
+
|
|
78
|
+
# Header with field names (primitive fields only in the column spec).
|
|
79
|
+
header = f"{prefix}## {name} [{len(items)}]{{{','.join(primitive_fields)}}}"
|
|
80
|
+
lines.append(header)
|
|
81
|
+
|
|
82
|
+
for i, item in enumerate(items):
|
|
83
|
+
row_values = [_format_value(item.get(f)) for f in primitive_fields]
|
|
84
|
+
row_str = "|".join(row_values)
|
|
85
|
+
|
|
86
|
+
if nested_fields:
|
|
87
|
+
lines.append(f"{prefix}@{i} {row_str}")
|
|
88
|
+
inner_prefix = _indent(depth + 1)
|
|
89
|
+
for nk in nested_fields:
|
|
90
|
+
nv = item.get(nk)
|
|
91
|
+
if isinstance(nv, list):
|
|
92
|
+
_encode_array(nv, nk, lines, depth + 1)
|
|
93
|
+
elif isinstance(nv, dict):
|
|
94
|
+
lines.append(f"{inner_prefix}## {nk}")
|
|
95
|
+
_encode_dict(nv, lines, depth + 2)
|
|
96
|
+
else:
|
|
97
|
+
lines.append(f"{prefix}{row_str}")
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _is_uniform_dict_list(items: list) -> bool:
|
|
101
|
+
"""Check whether a list contains uniform dicts (same keys across items).
|
|
102
|
+
|
|
103
|
+
Samples up to the first 5 items. Considers the list uniform if key
|
|
104
|
+
overlap is at least 70% between consecutive items and the first item.
|
|
105
|
+
"""
|
|
106
|
+
if not items or not isinstance(items[0], dict):
|
|
107
|
+
return False
|
|
108
|
+
|
|
109
|
+
sample = items[:5]
|
|
110
|
+
if not all(isinstance(item, dict) for item in sample):
|
|
111
|
+
return False
|
|
112
|
+
|
|
113
|
+
if not sample:
|
|
114
|
+
return False
|
|
115
|
+
|
|
116
|
+
reference_keys = set(sample[0].keys())
|
|
117
|
+
if not reference_keys:
|
|
118
|
+
return False
|
|
119
|
+
|
|
120
|
+
for item in sample[1:]:
|
|
121
|
+
item_keys = set(item.keys())
|
|
122
|
+
union = reference_keys | item_keys
|
|
123
|
+
intersection = reference_keys & item_keys
|
|
124
|
+
if not union or len(intersection) / len(union) < 0.7:
|
|
125
|
+
return False
|
|
126
|
+
|
|
127
|
+
return True
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _format_value(value: Any) -> str:
|
|
131
|
+
"""Format a single value for GCF output.
|
|
132
|
+
|
|
133
|
+
None becomes "-". Booleans are lowercased. Numbers are unquoted.
|
|
134
|
+
Strings containing "|" or newlines are quoted. Everything else is direct.
|
|
135
|
+
"""
|
|
136
|
+
if value is None:
|
|
137
|
+
return "-"
|
|
138
|
+
if isinstance(value, bool):
|
|
139
|
+
return "true" if value else "false"
|
|
140
|
+
if isinstance(value, (int, float)):
|
|
141
|
+
return str(value)
|
|
142
|
+
s = str(value)
|
|
143
|
+
if "|" in s or "\n" in s or s == "":
|
|
144
|
+
escaped = s.replace("\\", "\\\\").replace('"', '\\"')
|
|
145
|
+
return f'"{escaped}"'
|
|
146
|
+
return s
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _indent(depth: int) -> str:
|
|
150
|
+
"""Return indentation string for the given depth (2 spaces per level)."""
|
|
151
|
+
return " " * depth
|
gcf/session.py
CHANGED
gcf/types.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gcf-python
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: Python implementation of GCF (Graph Compact Format): token-optimized wire format for LLM tool responses
|
|
5
5
|
Project-URL: Homepage, https://github.com/blackwell-systems/gcf-python
|
|
6
6
|
Project-URL: Documentation, https://blackwell-systems.github.io/gcf/
|
|
@@ -37,7 +37,7 @@ Python implementation of [GCF (Graph Compact Format)](https://github.com/blackwe
|
|
|
37
37
|
## Install
|
|
38
38
|
|
|
39
39
|
```
|
|
40
|
-
pip install gcf-
|
|
40
|
+
pip install gcf-python
|
|
41
41
|
```
|
|
42
42
|
|
|
43
43
|
Zero dependencies. Pure Python. Python 3.9+. Includes CLI.
|
|
@@ -139,11 +139,36 @@ output = encode_delta(delta)
|
|
|
139
139
|
|
|
140
140
|
81.2% savings on re-queries where the pack changed slightly.
|
|
141
141
|
|
|
142
|
+
## Generic Encoding
|
|
143
|
+
|
|
144
|
+
Encode any Python value (not just graph payloads) into GCF tabular format:
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
from gcf import encode_generic
|
|
148
|
+
|
|
149
|
+
output = encode_generic({
|
|
150
|
+
"employees": [
|
|
151
|
+
{"id": 1, "name": "Alice", "department": "Engineering", "salary": 95000},
|
|
152
|
+
{"id": 2, "name": "Bob", "department": "Sales", "salary": 72000},
|
|
153
|
+
],
|
|
154
|
+
})
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Output:
|
|
158
|
+
```
|
|
159
|
+
## employees [2]{id,name,department,salary}
|
|
160
|
+
1|Alice|Engineering|95000
|
|
161
|
+
2|Bob|Sales|72000
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Works on dicts, lists, and primitives. Lists of uniform dicts get tabular rows. Nested dicts use `## key` section headers.
|
|
165
|
+
|
|
142
166
|
## API
|
|
143
167
|
|
|
144
168
|
| Function | Description |
|
|
145
169
|
|----------|-------------|
|
|
146
|
-
| `encode(p: Payload) -> str` | Encode a payload to GCF text |
|
|
170
|
+
| `encode(p: Payload) -> str` | Encode a graph payload to GCF text |
|
|
171
|
+
| `encode_generic(data: Any) -> str` | Encode any value to GCF tabular format |
|
|
147
172
|
| `decode(input_text: str) -> Payload` | Parse GCF text back to a Payload |
|
|
148
173
|
| `encode_with_session(p: Payload, s: Session) -> str` | Encode with session deduplication |
|
|
149
174
|
| `encode_delta(d: DeltaPayload) -> str` | Encode a delta (added/removed only) |
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
gcf/__init__.py,sha256=bhnoIK9Qk7tMgWX_3q6HFvE3w55yw5rw2jyFHx7Bqx8,1541
|
|
2
|
+
gcf/cli.py,sha256=2hSTBqiYcn1_EgGXuO65MHiEGh0C4DRMvspTd2zUaso,4258
|
|
3
|
+
gcf/constants.py,sha256=cmZ8YJSOB0im_eyfN8v4UvrLpBC6Fuf4cfcKZGbutxY,638
|
|
4
|
+
gcf/decode.py,sha256=gkBW9fmcurQ9bfcDXbaCOWemMmljo_MiZMj3-1rYvsw,5198
|
|
5
|
+
gcf/delta.py,sha256=xU0ujtSq1iF7yU8yk_WNQKh8iove-WUV_nKSuvW1XVk,1656
|
|
6
|
+
gcf/encode.py,sha256=WZCRv1Vj2PDTYvBgv__2P8GHh8FapxfAZmt5zo0k9Jc,2700
|
|
7
|
+
gcf/generic.py,sha256=N4rhf3JAdQHcWA9HEbTPxWnMfvdac6oZzQHQSnZdXdE,5105
|
|
8
|
+
gcf/session.py,sha256=F8OTJCMRMCY7Yzcvd7aU8wlbER0LZAMUcB4W9kHeSq4,4508
|
|
9
|
+
gcf/types.py,sha256=AWm-LQoSqLHAYtEjcAxWQZqJ4JXqNreLUKO2mJFgNMA,1465
|
|
10
|
+
gcf_python-0.1.2.dist-info/METADATA,sha256=FumNV9D2FUZfkUor89SPotIgLTo2QXmUhT9RHZc4DYk,7320
|
|
11
|
+
gcf_python-0.1.2.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
12
|
+
gcf_python-0.1.2.dist-info/entry_points.txt,sha256=aFT6gqlkh8iGfM8cblE-LUMxHH08_v71IIoZtDdRIVA,37
|
|
13
|
+
gcf_python-0.1.2.dist-info/licenses/LICENSE,sha256=txSvg3E4LugiB7MOOTci6WKd6wMOrOJTvaITeFJ2SgU,1074
|
|
14
|
+
gcf_python-0.1.2.dist-info/RECORD,,
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
gcf/__init__.py,sha256=XzxWfa__EBT3GqV6nAC74WX6EpNFO1Qg-YsnCg1ROQQ,1483
|
|
2
|
-
gcf/cli.py,sha256=2hSTBqiYcn1_EgGXuO65MHiEGh0C4DRMvspTd2zUaso,4258
|
|
3
|
-
gcf/constants.py,sha256=cmZ8YJSOB0im_eyfN8v4UvrLpBC6Fuf4cfcKZGbutxY,638
|
|
4
|
-
gcf/decode.py,sha256=kdbYrx0WzozDw-PhPieBv6h_a0B995crCEK-CJoK59c,5162
|
|
5
|
-
gcf/delta.py,sha256=xU0ujtSq1iF7yU8yk_WNQKh8iove-WUV_nKSuvW1XVk,1656
|
|
6
|
-
gcf/encode.py,sha256=KYGxFHy5LJoOF0IQblAm78bLL5uFf5iQMtrnyuuQXCA,2664
|
|
7
|
-
gcf/session.py,sha256=jVfpEK4euCn7apVm-sb0OyycUJrFUPaAUEWCT0d2c14,4472
|
|
8
|
-
gcf/types.py,sha256=yZL2knyFYguh2ex1ZXO1VwD4NEY4jvC-DL6-R-i-x0U,1429
|
|
9
|
-
gcf_python-0.1.0.dist-info/METADATA,sha256=hfE2L2HB1wxrBi8mRYHTD-KiViqVZ-4omjQNdT-ZtNA,6646
|
|
10
|
-
gcf_python-0.1.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
11
|
-
gcf_python-0.1.0.dist-info/entry_points.txt,sha256=aFT6gqlkh8iGfM8cblE-LUMxHH08_v71IIoZtDdRIVA,37
|
|
12
|
-
gcf_python-0.1.0.dist-info/licenses/LICENSE,sha256=txSvg3E4LugiB7MOOTci6WKd6wMOrOJTvaITeFJ2SgU,1074
|
|
13
|
-
gcf_python-0.1.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|