gcf-python 2.2.2__tar.gz → 2.3.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 (39) hide show
  1. {gcf_python-2.2.2 → gcf_python-2.3.0}/.github/workflows/ci.yml +9 -1
  2. {gcf_python-2.2.2 → gcf_python-2.3.0}/CHANGELOG.md +19 -0
  3. gcf_python-2.2.2/README.md → gcf_python-2.3.0/PKG-INFO +71 -2
  4. gcf_python-2.2.2/PKG-INFO → gcf_python-2.3.0/README.md +46 -27
  5. {gcf_python-2.2.2 → gcf_python-2.3.0}/pyproject.toml +5 -5
  6. {gcf_python-2.2.2 → gcf_python-2.3.0}/src/gcf/__init__.py +33 -1
  7. gcf_python-2.3.0/src/gcf/generic_delta.py +487 -0
  8. {gcf_python-2.2.2 → gcf_python-2.3.0}/tests/test_conformance_v2.py +85 -0
  9. gcf_python-2.3.0/tests/test_generic_delta.py +141 -0
  10. gcf_python-2.3.0/tests/test_generic_delta_session.py +187 -0
  11. {gcf_python-2.2.2 → gcf_python-2.3.0}/.github/FUNDING.yml +0 -0
  12. {gcf_python-2.2.2 → gcf_python-2.3.0}/.github/workflows/publish.yml +0 -0
  13. {gcf_python-2.2.2 → gcf_python-2.3.0}/.gitignore +0 -0
  14. {gcf_python-2.2.2 → gcf_python-2.3.0}/LICENSE +0 -0
  15. {gcf_python-2.2.2 → gcf_python-2.3.0}/assets/gcf-python-diagram.png +0 -0
  16. {gcf_python-2.2.2 → gcf_python-2.3.0}/src/gcf/__main__.py +0 -0
  17. {gcf_python-2.2.2 → gcf_python-2.3.0}/src/gcf/cli.py +0 -0
  18. {gcf_python-2.2.2 → gcf_python-2.3.0}/src/gcf/constants.py +0 -0
  19. {gcf_python-2.2.2 → gcf_python-2.3.0}/src/gcf/decode.py +0 -0
  20. {gcf_python-2.2.2 → gcf_python-2.3.0}/src/gcf/decode_generic.py +0 -0
  21. {gcf_python-2.2.2 → gcf_python-2.3.0}/src/gcf/delta.py +0 -0
  22. {gcf_python-2.2.2 → gcf_python-2.3.0}/src/gcf/encode.py +0 -0
  23. {gcf_python-2.2.2 → gcf_python-2.3.0}/src/gcf/generic.py +0 -0
  24. {gcf_python-2.2.2 → gcf_python-2.3.0}/src/gcf/scalar.py +0 -0
  25. {gcf_python-2.2.2 → gcf_python-2.3.0}/src/gcf/session.py +0 -0
  26. {gcf_python-2.2.2 → gcf_python-2.3.0}/src/gcf/stream.py +0 -0
  27. {gcf_python-2.2.2 → gcf_python-2.3.0}/src/gcf/stream_generic.py +0 -0
  28. {gcf_python-2.2.2 → gcf_python-2.3.0}/src/gcf/types.py +0 -0
  29. {gcf_python-2.2.2 → gcf_python-2.3.0}/tests/__init__.py +0 -0
  30. {gcf_python-2.2.2 → gcf_python-2.3.0}/tests/test_decode.py +0 -0
  31. {gcf_python-2.2.2 → gcf_python-2.3.0}/tests/test_delta.py +0 -0
  32. {gcf_python-2.2.2 → gcf_python-2.3.0}/tests/test_encode.py +0 -0
  33. {gcf_python-2.2.2 → gcf_python-2.3.0}/tests/test_generic.py +0 -0
  34. {gcf_python-2.2.2 → gcf_python-2.3.0}/tests/test_roundtrip.py +0 -0
  35. {gcf_python-2.2.2 → gcf_python-2.3.0}/tests/test_roundtrip_v2.py +0 -0
  36. {gcf_python-2.2.2 → gcf_python-2.3.0}/tests/test_session.py +0 -0
  37. {gcf_python-2.2.2 → gcf_python-2.3.0}/tests/test_stream.py +0 -0
  38. {gcf_python-2.2.2 → gcf_python-2.3.0}/tests/test_stream_generic.py +0 -0
  39. {gcf_python-2.2.2 → gcf_python-2.3.0}/uv.lock +0 -0
@@ -13,7 +13,11 @@ jobs:
13
13
  matrix:
14
14
  python-version: ['3.9', '3.10', '3.11', '3.12', '3.13']
15
15
  steps:
16
+ # Check out gcf-python and the shared-fixtures gcf repo as SIBLINGS so the
17
+ # conformance runner's ../gcf/tests/conformance path resolves on CI.
16
18
  - uses: actions/checkout@v4
19
+ with:
20
+ path: gcf-python
17
21
  - uses: actions/checkout@v4
18
22
  with:
19
23
  repository: blackwell-systems/gcf
@@ -22,9 +26,13 @@ jobs:
22
26
  with:
23
27
  python-version: ${{ matrix.python-version }}
24
28
  - run: pip install -e ".[dev]" || pip install -e . && pip install pytest
29
+ working-directory: gcf-python
25
30
  - name: Unit tests
26
31
  run: pytest tests/ -v --ignore=tests/test_conformance_v2.py --ignore=tests/test_roundtrip_v2.py
27
- - name: Conformance tests (133 fixtures)
32
+ working-directory: gcf-python
33
+ - name: Conformance tests (shared fixtures)
28
34
  run: pytest tests/test_conformance_v2.py -v
35
+ working-directory: gcf-python
29
36
  - name: Property-based round-trip (100K random + 100K adversarial)
30
37
  run: pytest tests/test_roundtrip_v2.py -v
38
+ working-directory: gcf-python
@@ -1,5 +1,24 @@
1
1
  # Changelog
2
2
 
3
+ ## v2.3.0 (2026-07-12)
4
+
5
+ ### Generic-profile delta encoding (SPEC §10a)
6
+
7
+ - Full producer + consumer implementation of generic-profile delta, byte-for-byte interoperable with `gcf-go`:
8
+ - `GenericSet` (keyed record set), `GenericDeltaPayload`
9
+ - `generic_pack_root` (`gcf-pack-root-v1`, generic profile) with a purpose-built cell canonicalization decoupled from the wire cell encoder: collision-free (null/bool/number bare, strings always quoted) and record-safe. Fields and records sort by UTF-8 byte order to match Go's `sort.Strings`.
10
+ - `diff_generic_sets` (the blessed producer path; centralizes the keyed-diff invariants), `encode_generic_full`, `encode_generic_delta`
11
+ - `decode_generic_full`, `decode_generic_delta` (consumer wire parsing)
12
+ - `verify_generic_delta` (atomic apply + `new_root` verification)
13
+ - Re-anchor session helper (SPEC §10a.8): `GenericDeltaSession` (`current_full`, `next`) with `ReanchorPolicy` / `fixed_n(n)` / `size_guard()` cadence policies and `DEFAULT_REANCHOR_N = 15`. Producer-side sugar over the primitives; introduces no new wire syntax (every emission is exactly `encode_generic_full` / `encode_generic_delta` output). Re-anchor cadence is byte-for-byte identical to `gcf-go` (size guard uses UTF-8 byte length to match Go's `len(string)`), verified by the shared `generic-delta-session` conformance fixtures.
14
+ - Delta is opt-in and bilateral; the existing `encode_generic` path is unchanged (backward compatible).
15
+
16
+ ### Tests
17
+
18
+ - Unit suite mirroring `gcf-go`: self-proving round-trip (diff -> encode -> apply -> recomputed root), determinism / row-order invariance, no-type-collision canonicalization, every invariant/error path, full-payload wire round-trip, the complete server -> wire -> consumer end-to-end loop, and malformed-wire-fails-closed.
19
+ - Conformance runner support for `generic-pack-root`, `generic-delta`, `generic-delta-verify`, `generic-delta-decode` (12 shared fixtures); verified to produce identical pack roots and delta wire to `gcf-go`.
20
+ - Session helper suite (`test_generic_delta_session.py`) mirroring `gcf-go`: FixedN cadence pattern, size-guard triggering, schema-change forced full, FixedN(15)-over-30-turns count, and the load-bearing consumer-stays-in-sync check under both policies. Conformance runner support for `generic-delta-session` (3 shared fixtures: fixed-N, size-guard, schema-change).
21
+
3
22
  ## v2.2.2 (2026-07-10)
4
23
 
5
24
  ### Fixes
@@ -1,3 +1,28 @@
1
+ Metadata-Version: 2.4
2
+ Name: gcf-python
3
+ Version: 2.3.0
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
+ Project-URL: Homepage, https://github.com/blackwell-systems/gcf-python
6
+ Project-URL: Documentation, https://gcformat.com/
7
+ Project-URL: Specification, https://github.com/blackwell-systems/gcf
8
+ Author: Blackwell Systems
9
+ License: MIT
10
+ License-File: LICENSE
11
+ Keywords: agents,delta,gcf,graph,llm,mcp,multi-turn,token-efficient,wire-format
12
+ Classifier: Development Status :: 5 - Production/Stable
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Software Development :: Libraries
22
+ Classifier: Typing :: Typed
23
+ Requires-Python: >=3.9
24
+ Description-Content-Type: text/markdown
25
+
1
26
  <p align="center">
2
27
  <a href="https://github.com/blackwell-systems"><img src="https://raw.githubusercontent.com/blackwell-systems/blackwell-docs-theme/main/badge-trademark.svg" alt="Blackwell Systems"></a>
3
28
  <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License"></a>
@@ -157,6 +182,44 @@ Output:
157
182
 
158
183
  Works on dicts, lists, and primitives. Lists of uniform dicts get tabular rows. Nested dicts use `## key` section headers.
159
184
 
185
+ ## Generic-Profile Delta (multi-turn)
186
+
187
+ In an agent loop the same keyed table gets re-queried turn after turn. Instead of re-sending the whole table each time, send only the changed rows (SPEC §10a):
188
+
189
+ ```python
190
+ from gcf import GenericSet, diff_generic_sets, encode_generic_delta, verify_generic_delta
191
+
192
+ base = GenericSet(key="id", fields=["id", "status"], rows=[
193
+ {"id": 1001, "status": "pending"},
194
+ {"id": 1002, "status": "shipped"},
195
+ ])
196
+ nxt = GenericSet(key="id", fields=["id", "status"], rows=[
197
+ {"id": 1001, "status": "shipped"}, # changed
198
+ {"id": 1003, "status": "pending"}, # added (1002 removed)
199
+ ])
200
+
201
+ d = diff_generic_sets(base, nxt)
202
+ wire = encode_generic_delta(d) # ## added / ## changed / ## removed
203
+ held = verify_generic_delta(base, d, d.new_root) # atomic apply + new_root verification
204
+ ```
205
+
206
+ Opt-in and bilateral, keyed on content-addressed pack roots. By the 5th overlapping call, ~97% fewer tokens than re-sending JSON.
207
+
208
+ ### Re-anchor session helper
209
+
210
+ `GenericDeltaSession` manages the delta/re-anchor cadence for you: each `next()` returns either a compact delta or, on its cadence, a full re-anchor (which re-grounds the consumer), updating its held base.
211
+
212
+ ```python
213
+ from gcf import GenericDeltaSession, fixed_n, size_guard
214
+
215
+ sess = GenericDeltaSession(base, tool="orders", policy=size_guard())
216
+ wire = sess.current_full() # transmit the base once to establish it
217
+ for snapshot in stream: # each turn's current GenericSet
218
+ wire, is_full = sess.next(snapshot) # a compact delta, or a periodic full re-anchor
219
+ ```
220
+
221
+ `fixed_n(15)` re-anchors every N turns; `size_guard()` (recommended) re-anchors once the cumulative delta reaches a full payload's size. It introduces no new wire syntax and the decoder stays cadence-agnostic, so a re-anchor is just the protocol's "full" outcome on a schedule.
222
+
160
223
  ## API
161
224
 
162
225
  | Function | Description |
@@ -165,7 +228,11 @@ Works on dicts, lists, and primitives. Lists of uniform dicts get tabular rows.
165
228
  | `encode_generic(data: Any) -> str` | Encode any value to GCF tabular format |
166
229
  | `decode(input_text: str) -> Payload` | Parse GCF text back to a Payload |
167
230
  | `encode_with_session(p: Payload, s: Session) -> str` | Encode with session deduplication |
168
- | `encode_delta(d: DeltaPayload) -> str` | Encode a delta (added/removed only) |
231
+ | `encode_delta(d: DeltaPayload) -> str` | Encode a graph delta (added/removed only) |
232
+ | `diff_generic_sets(base, next) -> GenericDeltaPayload` | Diff two keyed record sets (generic profile) |
233
+ | `encode_generic_delta(d) -> str` / `decode_generic_delta(s)` | Generic-profile delta wire (§10a) |
234
+ | `verify_generic_delta(base, d, root) -> GenericSet` | Atomic apply + `new_root` verification |
235
+ | `GenericDeltaSession(base, tool, policy)` | Producer-side re-anchor cadence helper (§10a.8) |
169
236
  | `Session()` | Create a new session tracker (thread-safe) |
170
237
 
171
238
  ## Types
@@ -175,7 +242,9 @@ Works on dicts, lists, and primitives. Lists of uniform dicts get tabular rows.
175
242
  | `Payload` | Full GCF payload: tool, budget, symbols, edges, pack root |
176
243
  | `Symbol` | Graph node: qualified name, kind, score, provenance, distance |
177
244
  | `Edge` | Directed relationship: source, target, edge type |
178
- | `DeltaPayload` | Diff between two packs: added/removed symbols and edges |
245
+ | `DeltaPayload` | Diff between two graph packs: added/removed symbols and edges |
246
+ | `GenericSet` / `GenericDeltaPayload` | Keyed record set and its generic-profile diff (§10a) |
247
+ | `GenericDeltaSession` | Stateful producer that schedules delta vs full re-anchor (§10a.8) |
179
248
  | `Session` | Thread-safe tracker for multi-call deduplication |
180
249
  | `KIND_ABBREV` / `KIND_EXPAND` | Bidirectional kind abbreviation dicts |
181
250
 
@@ -1,28 +1,3 @@
1
- Metadata-Version: 2.4
2
- Name: gcf-python
3
- Version: 2.2.2
4
- Summary: The AI-native wire format for structured data. 50-92% fewer tokens than JSON. 100% comprehension on every frontier model. Zero dependencies.
5
- Project-URL: Homepage, https://github.com/blackwell-systems/gcf-python
6
- Project-URL: Documentation, https://blackwell-systems.github.io/gcf/
7
- Project-URL: Specification, https://github.com/blackwell-systems/gcf
8
- Author: Blackwell Systems
9
- License: MIT
10
- License-File: LICENSE
11
- Keywords: gcf,graph,llm,mcp,token-efficient,wire-format
12
- Classifier: Development Status :: 4 - Beta
13
- Classifier: Intended Audience :: Developers
14
- Classifier: License :: OSI Approved :: MIT License
15
- Classifier: Programming Language :: Python :: 3
16
- Classifier: Programming Language :: Python :: 3.9
17
- Classifier: Programming Language :: Python :: 3.10
18
- Classifier: Programming Language :: Python :: 3.11
19
- Classifier: Programming Language :: Python :: 3.12
20
- Classifier: Programming Language :: Python :: 3.13
21
- Classifier: Topic :: Software Development :: Libraries
22
- Classifier: Typing :: Typed
23
- Requires-Python: >=3.9
24
- Description-Content-Type: text/markdown
25
-
26
1
  <p align="center">
27
2
  <a href="https://github.com/blackwell-systems"><img src="https://raw.githubusercontent.com/blackwell-systems/blackwell-docs-theme/main/badge-trademark.svg" alt="Blackwell Systems"></a>
28
3
  <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License"></a>
@@ -182,6 +157,44 @@ Output:
182
157
 
183
158
  Works on dicts, lists, and primitives. Lists of uniform dicts get tabular rows. Nested dicts use `## key` section headers.
184
159
 
160
+ ## Generic-Profile Delta (multi-turn)
161
+
162
+ In an agent loop the same keyed table gets re-queried turn after turn. Instead of re-sending the whole table each time, send only the changed rows (SPEC §10a):
163
+
164
+ ```python
165
+ from gcf import GenericSet, diff_generic_sets, encode_generic_delta, verify_generic_delta
166
+
167
+ base = GenericSet(key="id", fields=["id", "status"], rows=[
168
+ {"id": 1001, "status": "pending"},
169
+ {"id": 1002, "status": "shipped"},
170
+ ])
171
+ nxt = GenericSet(key="id", fields=["id", "status"], rows=[
172
+ {"id": 1001, "status": "shipped"}, # changed
173
+ {"id": 1003, "status": "pending"}, # added (1002 removed)
174
+ ])
175
+
176
+ d = diff_generic_sets(base, nxt)
177
+ wire = encode_generic_delta(d) # ## added / ## changed / ## removed
178
+ held = verify_generic_delta(base, d, d.new_root) # atomic apply + new_root verification
179
+ ```
180
+
181
+ Opt-in and bilateral, keyed on content-addressed pack roots. By the 5th overlapping call, ~97% fewer tokens than re-sending JSON.
182
+
183
+ ### Re-anchor session helper
184
+
185
+ `GenericDeltaSession` manages the delta/re-anchor cadence for you: each `next()` returns either a compact delta or, on its cadence, a full re-anchor (which re-grounds the consumer), updating its held base.
186
+
187
+ ```python
188
+ from gcf import GenericDeltaSession, fixed_n, size_guard
189
+
190
+ sess = GenericDeltaSession(base, tool="orders", policy=size_guard())
191
+ wire = sess.current_full() # transmit the base once to establish it
192
+ for snapshot in stream: # each turn's current GenericSet
193
+ wire, is_full = sess.next(snapshot) # a compact delta, or a periodic full re-anchor
194
+ ```
195
+
196
+ `fixed_n(15)` re-anchors every N turns; `size_guard()` (recommended) re-anchors once the cumulative delta reaches a full payload's size. It introduces no new wire syntax and the decoder stays cadence-agnostic, so a re-anchor is just the protocol's "full" outcome on a schedule.
197
+
185
198
  ## API
186
199
 
187
200
  | Function | Description |
@@ -190,7 +203,11 @@ Works on dicts, lists, and primitives. Lists of uniform dicts get tabular rows.
190
203
  | `encode_generic(data: Any) -> str` | Encode any value to GCF tabular format |
191
204
  | `decode(input_text: str) -> Payload` | Parse GCF text back to a Payload |
192
205
  | `encode_with_session(p: Payload, s: Session) -> str` | Encode with session deduplication |
193
- | `encode_delta(d: DeltaPayload) -> str` | Encode a delta (added/removed only) |
206
+ | `encode_delta(d: DeltaPayload) -> str` | Encode a graph delta (added/removed only) |
207
+ | `diff_generic_sets(base, next) -> GenericDeltaPayload` | Diff two keyed record sets (generic profile) |
208
+ | `encode_generic_delta(d) -> str` / `decode_generic_delta(s)` | Generic-profile delta wire (§10a) |
209
+ | `verify_generic_delta(base, d, root) -> GenericSet` | Atomic apply + `new_root` verification |
210
+ | `GenericDeltaSession(base, tool, policy)` | Producer-side re-anchor cadence helper (§10a.8) |
194
211
  | `Session()` | Create a new session tracker (thread-safe) |
195
212
 
196
213
  ## Types
@@ -200,7 +217,9 @@ Works on dicts, lists, and primitives. Lists of uniform dicts get tabular rows.
200
217
  | `Payload` | Full GCF payload: tool, budget, symbols, edges, pack root |
201
218
  | `Symbol` | Graph node: qualified name, kind, score, provenance, distance |
202
219
  | `Edge` | Directed relationship: source, target, edge type |
203
- | `DeltaPayload` | Diff between two packs: added/removed symbols and edges |
220
+ | `DeltaPayload` | Diff between two graph packs: added/removed symbols and edges |
221
+ | `GenericSet` / `GenericDeltaPayload` | Keyed record set and its generic-profile diff (§10a) |
222
+ | `GenericDeltaSession` | Stateful producer that schedules delta vs full re-anchor (§10a.8) |
204
223
  | `Session` | Thread-safe tracker for multi-call deduplication |
205
224
  | `KIND_ABBREV` / `KIND_EXPAND` | Bidirectional kind abbreviation dicts |
206
225
 
@@ -4,17 +4,17 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "gcf-python"
7
- version = "2.2.2"
8
- description = "The AI-native wire format for structured data. 50-92% fewer tokens than JSON. 100% comprehension on every frontier model. Zero dependencies."
7
+ version = "2.3.0"
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"}
11
11
  requires-python = ">=3.9"
12
12
  authors = [
13
13
  { name = "Blackwell Systems" },
14
14
  ]
15
- keywords = ["gcf", "llm", "mcp", "token-efficient", "graph", "wire-format"]
15
+ keywords = ["gcf", "llm", "mcp", "token-efficient", "graph", "wire-format", "delta", "multi-turn", "agents"]
16
16
  classifiers = [
17
- "Development Status :: 4 - Beta",
17
+ "Development Status :: 5 - Production/Stable",
18
18
  "Intended Audience :: Developers",
19
19
  "License :: OSI Approved :: MIT License",
20
20
  "Programming Language :: Python :: 3",
@@ -32,7 +32,7 @@ gcf = "gcf.cli:main"
32
32
 
33
33
  [project.urls]
34
34
  Homepage = "https://github.com/blackwell-systems/gcf-python"
35
- Documentation = "https://blackwell-systems.github.io/gcf/"
35
+ Documentation = "https://gcformat.com/"
36
36
  Specification = "https://github.com/blackwell-systems/gcf"
37
37
 
38
38
  [tool.hatch.build.targets.wheel]
@@ -39,6 +39,23 @@ from .decode import DecodeError, decode
39
39
  from .delta import encode_delta
40
40
  from .encode import encode
41
41
  from .generic import encode_generic, GenericOptions
42
+ from .generic_delta import (
43
+ GenericSet,
44
+ GenericDeltaPayload,
45
+ generic_pack_root,
46
+ diff_generic_sets,
47
+ encode_generic_full,
48
+ encode_generic_delta,
49
+ decode_generic_full,
50
+ decode_generic_delta,
51
+ verify_generic_delta,
52
+ GenericDeltaSession,
53
+ ReanchorPolicy,
54
+ ReanchorMode,
55
+ fixed_n,
56
+ size_guard,
57
+ DEFAULT_REANCHOR_N,
58
+ )
42
59
  from .session import Session, encode_with_session
43
60
  from .decode_generic import decode_generic
44
61
  from .stream import StreamEncoder
@@ -64,6 +81,21 @@ __all__ = [
64
81
  "encode_generic",
65
82
  "GenericOptions",
66
83
  "encode_with_session",
84
+ "GenericSet",
85
+ "GenericDeltaPayload",
86
+ "generic_pack_root",
87
+ "diff_generic_sets",
88
+ "encode_generic_full",
89
+ "encode_generic_delta",
90
+ "decode_generic_full",
91
+ "decode_generic_delta",
92
+ "verify_generic_delta",
93
+ "GenericDeltaSession",
94
+ "ReanchorPolicy",
95
+ "ReanchorMode",
96
+ "fixed_n",
97
+ "size_guard",
98
+ "DEFAULT_REANCHOR_N",
67
99
  ]
68
100
 
69
- __version__ = "2.2.2"
101
+ __version__ = "2.3.0"