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 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.0"
62
+ __version__ = "0.1.2"
gcf/decode.py CHANGED
@@ -1,5 +1,7 @@
1
1
  """GCF decoder: parses GCF text back into a Payload."""
2
2
 
3
+ from __future__ import annotations
4
+
3
5
  from .constants import KIND_EXPAND
4
6
  from .types import Edge, Payload, Symbol
5
7
 
gcf/encode.py CHANGED
@@ -1,5 +1,7 @@
1
1
  """GCF encoder: serializes Payload into GCF text format."""
2
2
 
3
+ from __future__ import annotations
4
+
3
5
  from .constants import KIND_ABBREV
4
6
  from .types import Payload, Symbol
5
7
 
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
@@ -1,5 +1,7 @@
1
1
  """Session-based deduplication for GCF encoding."""
2
2
 
3
+ from __future__ import annotations
4
+
3
5
  import threading
4
6
 
5
7
  from .constants import KIND_ABBREV
gcf/types.py CHANGED
@@ -1,5 +1,7 @@
1
1
  """Data types for GCF payloads."""
2
2
 
3
+ from __future__ import annotations
4
+
3
5
  from dataclasses import dataclass, field
4
6
 
5
7
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gcf-python
3
- Version: 0.1.0
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-py
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,,