gfa-sdk 0.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.
gfa_sdk-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,159 @@
1
+ Metadata-Version: 2.4
2
+ Name: gfa-sdk
3
+ Version: 0.1.0
4
+ Summary: Opinionated Python client for gfa (Git for Agents) — smart routing, session cache, profile-aware hints.
5
+ Author: gfa contributors
6
+ License: MIT
7
+ Project-URL: Homepage, https://gitlab.com/kerusu/gfa
8
+ Project-URL: Repository, https://gitlab.com/kerusu/gfa
9
+ Keywords: gfa,git,agent,storage,sdk
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Topic :: Software Development :: Version Control :: Git
18
+ Requires-Python: >=3.10
19
+ Description-Content-Type: text/markdown
20
+ Requires-Dist: httpx[http2]>=0.27
21
+ Requires-Dist: PyJWT[crypto]>=2.8
22
+ Requires-Dist: cryptography>=42
23
+ Provides-Extra: test
24
+ Requires-Dist: pytest>=8; extra == "test"
25
+ Requires-Dist: pytest-asyncio>=0.23; extra == "test"
26
+ Requires-Dist: pyyaml>=6; extra == "test"
27
+
28
+ # gfa-sdk — Python SDK for gfa (Git for Agents)
29
+
30
+ Opinionated Python client for [gfa](https://gitlab.com/kerusu/gfa). Picks the
31
+ right endpoint for the access pattern, caches what is safe to cache, and emits
32
+ hints when a strategy change would help. Designed for AI agents reading,
33
+ mutating, and forking gfa-hosted repos.
34
+
35
+ ## Install
36
+
37
+ ```bash
38
+ pip install gfa-sdk
39
+ ```
40
+
41
+ Python >= 3.10 required.
42
+
43
+ ## Quickstart
44
+
45
+ ```python
46
+ import os
47
+ import gfa
48
+
49
+ client = gfa.Client(
50
+ endpoint=os.environ["GFA_ENDPOINT"],
51
+ token=os.environ["GFA_JWT"],
52
+ )
53
+
54
+ # Profile a repo (cheap, cached for the session)
55
+ profile = client.profile_repo("myrepo")
56
+ print(f"{profile.file_count_at_head} files, {profile.storage_bytes} bytes")
57
+
58
+ # Read one file
59
+ src = client.read_file("myrepo", "src/main.go")
60
+
61
+ # Read many files — SDK picks /file vs /files/batch vs partial-clone
62
+ files = client.read_files("myrepo", ["a.go", "b.go", "c.go"])
63
+
64
+ # Create a commit
65
+ sha = client.create_commit(
66
+ "myrepo",
67
+ branch="main",
68
+ message="agent: refactor",
69
+ files=[gfa.FileChange(path="a.go", content=b"// rewritten\n")],
70
+ )
71
+
72
+ # Workspace for isolated changes
73
+ with client.create_workspace("myrepo", base_ref="main", ttl_hours=2) as ws:
74
+ ws.create_commit(branch="main", message="WIP", files=[...])
75
+ ws.merge()
76
+ ```
77
+
78
+ ## Smart routing
79
+
80
+ `client.read_files(repo, paths)` auto-routes by `len(paths)`:
81
+
82
+ | Count | Surface |
83
+ |---|---|
84
+ | 1 | `GET /file` |
85
+ | 2–200 | `POST /files/batch` |
86
+ | > 200 (default) | `SuggestPartialCloneError` — opt into `auto_clone=True` for automatic partial clone |
87
+
88
+ Threshold tuning:
89
+
90
+ ```python
91
+ from gfa import Client, ClientConfig
92
+ client = Client(
93
+ endpoint,
94
+ token,
95
+ config=ClientConfig(batch_read_threshold=500),
96
+ )
97
+ ```
98
+
99
+ ## Hints
100
+
101
+ The SDK emits structured hints when access patterns suggest a strategy change.
102
+ The default handler writes to stderr; override with a logging hook:
103
+
104
+ ```python
105
+ def my_handler(hint):
106
+ logger.info("gfa hint", extra={"code": hint.code, "repo": hint.repo})
107
+
108
+ client = gfa.Client(endpoint, token, hint_handler=my_handler)
109
+ ```
110
+
111
+ Silence entirely:
112
+
113
+ ```python
114
+ client = gfa.Client(endpoint, token, hint_handler=lambda _: None)
115
+ ```
116
+
117
+ ## Auth
118
+
119
+ Either a pre-minted JWT string, or a `TokenProvider` that mints per-call:
120
+
121
+ ```python
122
+ provider = gfa.FileKeyTokenProvider("/path/to/preview-jwt-priv.pem", ttl_hours=1)
123
+ client = gfa.Client(endpoint, provider)
124
+ ```
125
+
126
+ `FileKeyTokenProvider` mints ES256 JWTs with `sub=<repo>`, `iat=now`,
127
+ `exp=now + ttl_hours`. Mirrors the Go `tools/mint-token` binary.
128
+
129
+ ## Diagnostics
130
+
131
+ ```python
132
+ client.read_file("myrepo", "x.go")
133
+ client.read_files("myrepo", ["a.go", "b.go"])
134
+ print(client.stats)
135
+ # ClientStats(request_count=2, cache_hit_rate=0.0, ...)
136
+ ```
137
+
138
+ ## Partial clone
139
+
140
+ ```python
141
+ with client.partial_clone("myrepo", filter="blob:none", depth=1) as repo:
142
+ content = repo.read("src/foo.go")
143
+ ```
144
+
145
+ Requires `git` binary on PATH.
146
+
147
+ ## Troubleshooting
148
+
149
+ | Symptom | Likely cause |
150
+ |---|---|
151
+ | `UnauthorizedError` | Token missing, malformed, or expired |
152
+ | `ForbiddenError` | JWT `sub` does not match the repo being accessed |
153
+ | `SuggestPartialCloneError` | Asked for too many files; pass `auto_clone=True` or use `partial_clone` |
154
+ | `GitBinaryMissingError` | `partial_clone` needs `git` on PATH |
155
+ | `ConflictError` on merge | Workspace and source diverged — inspect via `conflict_surface()` |
156
+
157
+ ## License
158
+
159
+ MIT.
@@ -0,0 +1,132 @@
1
+ # gfa-sdk — Python SDK for gfa (Git for Agents)
2
+
3
+ Opinionated Python client for [gfa](https://gitlab.com/kerusu/gfa). Picks the
4
+ right endpoint for the access pattern, caches what is safe to cache, and emits
5
+ hints when a strategy change would help. Designed for AI agents reading,
6
+ mutating, and forking gfa-hosted repos.
7
+
8
+ ## Install
9
+
10
+ ```bash
11
+ pip install gfa-sdk
12
+ ```
13
+
14
+ Python >= 3.10 required.
15
+
16
+ ## Quickstart
17
+
18
+ ```python
19
+ import os
20
+ import gfa
21
+
22
+ client = gfa.Client(
23
+ endpoint=os.environ["GFA_ENDPOINT"],
24
+ token=os.environ["GFA_JWT"],
25
+ )
26
+
27
+ # Profile a repo (cheap, cached for the session)
28
+ profile = client.profile_repo("myrepo")
29
+ print(f"{profile.file_count_at_head} files, {profile.storage_bytes} bytes")
30
+
31
+ # Read one file
32
+ src = client.read_file("myrepo", "src/main.go")
33
+
34
+ # Read many files — SDK picks /file vs /files/batch vs partial-clone
35
+ files = client.read_files("myrepo", ["a.go", "b.go", "c.go"])
36
+
37
+ # Create a commit
38
+ sha = client.create_commit(
39
+ "myrepo",
40
+ branch="main",
41
+ message="agent: refactor",
42
+ files=[gfa.FileChange(path="a.go", content=b"// rewritten\n")],
43
+ )
44
+
45
+ # Workspace for isolated changes
46
+ with client.create_workspace("myrepo", base_ref="main", ttl_hours=2) as ws:
47
+ ws.create_commit(branch="main", message="WIP", files=[...])
48
+ ws.merge()
49
+ ```
50
+
51
+ ## Smart routing
52
+
53
+ `client.read_files(repo, paths)` auto-routes by `len(paths)`:
54
+
55
+ | Count | Surface |
56
+ |---|---|
57
+ | 1 | `GET /file` |
58
+ | 2–200 | `POST /files/batch` |
59
+ | > 200 (default) | `SuggestPartialCloneError` — opt into `auto_clone=True` for automatic partial clone |
60
+
61
+ Threshold tuning:
62
+
63
+ ```python
64
+ from gfa import Client, ClientConfig
65
+ client = Client(
66
+ endpoint,
67
+ token,
68
+ config=ClientConfig(batch_read_threshold=500),
69
+ )
70
+ ```
71
+
72
+ ## Hints
73
+
74
+ The SDK emits structured hints when access patterns suggest a strategy change.
75
+ The default handler writes to stderr; override with a logging hook:
76
+
77
+ ```python
78
+ def my_handler(hint):
79
+ logger.info("gfa hint", extra={"code": hint.code, "repo": hint.repo})
80
+
81
+ client = gfa.Client(endpoint, token, hint_handler=my_handler)
82
+ ```
83
+
84
+ Silence entirely:
85
+
86
+ ```python
87
+ client = gfa.Client(endpoint, token, hint_handler=lambda _: None)
88
+ ```
89
+
90
+ ## Auth
91
+
92
+ Either a pre-minted JWT string, or a `TokenProvider` that mints per-call:
93
+
94
+ ```python
95
+ provider = gfa.FileKeyTokenProvider("/path/to/preview-jwt-priv.pem", ttl_hours=1)
96
+ client = gfa.Client(endpoint, provider)
97
+ ```
98
+
99
+ `FileKeyTokenProvider` mints ES256 JWTs with `sub=<repo>`, `iat=now`,
100
+ `exp=now + ttl_hours`. Mirrors the Go `tools/mint-token` binary.
101
+
102
+ ## Diagnostics
103
+
104
+ ```python
105
+ client.read_file("myrepo", "x.go")
106
+ client.read_files("myrepo", ["a.go", "b.go"])
107
+ print(client.stats)
108
+ # ClientStats(request_count=2, cache_hit_rate=0.0, ...)
109
+ ```
110
+
111
+ ## Partial clone
112
+
113
+ ```python
114
+ with client.partial_clone("myrepo", filter="blob:none", depth=1) as repo:
115
+ content = repo.read("src/foo.go")
116
+ ```
117
+
118
+ Requires `git` binary on PATH.
119
+
120
+ ## Troubleshooting
121
+
122
+ | Symptom | Likely cause |
123
+ |---|---|
124
+ | `UnauthorizedError` | Token missing, malformed, or expired |
125
+ | `ForbiddenError` | JWT `sub` does not match the repo being accessed |
126
+ | `SuggestPartialCloneError` | Asked for too many files; pass `auto_clone=True` or use `partial_clone` |
127
+ | `GitBinaryMissingError` | `partial_clone` needs `git` on PATH |
128
+ | `ConflictError` on merge | Workspace and source diverged — inspect via `conflict_surface()` |
129
+
130
+ ## License
131
+
132
+ MIT.
@@ -0,0 +1,82 @@
1
+ """gfa — Opinionated Python SDK for gfa (Git for Agents).
2
+
3
+ Public surface re-exports. Power users can reach into submodules
4
+ (``gfa.routing``, ``gfa.cache``) to inspect internals.
5
+
6
+ See ``design/smart-client-sdk.md`` in the gfa repo for the design spec.
7
+ """
8
+
9
+ from gfa._version import __version__
10
+ from gfa.async_client import AsyncClient
11
+ from gfa.cache import BlobCache, ProfileCache, RefCache
12
+ from gfa.client import Client
13
+ from gfa.defaults import ClientConfig
14
+ from gfa.errors import (
15
+ BadRequestError,
16
+ ConflictError,
17
+ FileNotFoundError,
18
+ ForbiddenError,
19
+ GfaError,
20
+ GitBinaryMissingError,
21
+ RefNotFoundError,
22
+ RepoNotFoundError,
23
+ ServerError,
24
+ SuggestPartialCloneError,
25
+ TransportError,
26
+ UnauthorizedError,
27
+ UnavailableError,
28
+ UnprocessableError,
29
+ )
30
+ from gfa.hints import Hint, HintHandler, default_hint_handler
31
+ from gfa.mint_token import FileKeyTokenProvider, TokenProvider
32
+ from gfa.models import (
33
+ Author,
34
+ ClientStats,
35
+ CommitInfo,
36
+ ConflictSurface,
37
+ DivergeResult,
38
+ FileChange,
39
+ RepoProfile,
40
+ TreeEntry,
41
+ )
42
+ from gfa.partial_clone import PartialClone
43
+ from gfa.workspace import Workspace
44
+
45
+ __all__ = [
46
+ "__version__",
47
+ "AsyncClient",
48
+ "Author",
49
+ "BadRequestError",
50
+ "BlobCache",
51
+ "Client",
52
+ "ClientConfig",
53
+ "ClientStats",
54
+ "CommitInfo",
55
+ "ConflictError",
56
+ "ConflictSurface",
57
+ "DivergeResult",
58
+ "FileChange",
59
+ "FileKeyTokenProvider",
60
+ "FileNotFoundError",
61
+ "ForbiddenError",
62
+ "GfaError",
63
+ "GitBinaryMissingError",
64
+ "Hint",
65
+ "HintHandler",
66
+ "PartialClone",
67
+ "ProfileCache",
68
+ "RefCache",
69
+ "RefNotFoundError",
70
+ "RepoNotFoundError",
71
+ "RepoProfile",
72
+ "ServerError",
73
+ "SuggestPartialCloneError",
74
+ "TokenProvider",
75
+ "TransportError",
76
+ "TreeEntry",
77
+ "UnauthorizedError",
78
+ "UnavailableError",
79
+ "UnprocessableError",
80
+ "Workspace",
81
+ "default_hint_handler",
82
+ ]