gitcode-api 1.2.12__py3-none-any.whl → 1.2.14__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.
gitcode_api/__init__.py CHANGED
@@ -9,6 +9,7 @@ from ._exceptions import (
9
9
  GitCodeError,
10
10
  GitCodeHTTPStatusError,
11
11
  )
12
+ from .utils import as_dict
12
13
 
13
14
  __version__ = (Path(__file__).parent / "version.txt").read_text().strip()
14
15
 
@@ -20,4 +21,5 @@ __all__ = [
20
21
  "GitCodeConfigurationError",
21
22
  "GitCodeError",
22
23
  "GitCodeHTTPStatusError",
24
+ "as_dict",
23
25
  ]
gitcode_api/_models.py CHANGED
@@ -9,7 +9,6 @@ from typing import (
9
9
  Mapping,
10
10
  MutableMapping,
11
11
  Optional,
12
- TypedDict,
13
12
  TypeVar,
14
13
  Union,
15
14
  get_args,
@@ -136,60 +135,6 @@ def as_model_list(data: List[Mapping[str, Any]], model_type: type[ModelT]) -> Li
136
135
  return [as_model(item, model_type) for item in data]
137
136
 
138
137
 
139
- class RepositoryCreateParams(TypedDict, total=False):
140
- """Typed fields accepted by repository creation endpoints."""
141
-
142
- name: str
143
- description: str
144
- has_issues: bool
145
- has_wiki: bool
146
- auto_init: bool
147
- gitignore_template: str
148
- license_template: str
149
- path: str
150
- private: bool
151
- public: int
152
- default_branch: str
153
- homepage: str
154
- can_comment: bool
155
-
156
-
157
- class IssueCreateParams(TypedDict, total=False):
158
- """Typed fields accepted by issue creation endpoints."""
159
-
160
- title: str
161
- body: str
162
- assignee: str
163
- labels: List[str]
164
- milestone: Union[int, str]
165
-
166
-
167
- class PullRequestCreateParams(TypedDict, total=False):
168
- """Typed fields accepted by pull request creation endpoints."""
169
-
170
- title: str
171
- body: str
172
- head: str
173
- base: str
174
- assignees: List[str]
175
- testers: List[str]
176
- labels: List[str]
177
- draft: bool
178
-
179
-
180
- class WebhookCreateParams(TypedDict, total=False):
181
- """Typed fields accepted by webhook creation endpoints."""
182
-
183
- url: str
184
- encryption_type: int
185
- password: str
186
- push_events: bool
187
- tag_push_events: bool
188
- issues_events: bool
189
- note_events: bool
190
- merge_requests_events: bool
191
-
192
-
193
138
  @dataclass(init=False)
194
139
  class UserRef(APIObject):
195
140
  """Compact user payload embedded in other responses.
@@ -513,6 +458,8 @@ class Repository(APIObject):
513
458
  :vartype relation: Optional[str]
514
459
  :ivar members: Members returned in the repository payload.
515
460
  :vartype members: Optional[List[str]]
461
+ :ivar parent: Upstream source repository information
462
+ :vartype parent: Optional[Dict[str, Any]]
516
463
  :ivar permission: Permission summary for this object.
517
464
  :vartype permission: Optional[RepositoryPermission]
518
465
  :ivar enterprise: Enterprise associated with the repository.
@@ -566,6 +513,7 @@ class Repository(APIObject):
566
513
  status: Optional[str] = None
567
514
  relation: Optional[str] = None
568
515
  members: Optional[List[str]] = None
516
+ parent: Optional[Dict[str, Any]] = None
569
517
  permission: Optional[RepositoryPermission] = None
570
518
  enterprise: Optional[Union[EnterpriseRef, str]] = None
571
519
  issue_template_source: Optional[str] = None
@@ -760,6 +708,31 @@ class ContentObject(APIObject):
760
708
  _links: Optional[ContentLinks] = None
761
709
 
762
710
 
711
+ @dataclass(init=False)
712
+ class RepositoryGitCodeTemplate(APIObject):
713
+ """Metadata for a ``.gitcode`` issue or pull request template file.
714
+
715
+ Returned by :meth:`~gitcode_api.resources.collaboration.IssuesResource.list_templates`
716
+ and :meth:`~gitcode_api.resources.collaboration.PullsResource.list_templates`. Use
717
+ :meth:`~gitcode_api.resources.collaboration.IssuesResource.get_template` or
718
+ :meth:`~gitcode_api.resources.collaboration.PullsResource.get_template` to load the body.
719
+
720
+ :ivar path: Repository-relative path (under ``.gitcode/``).
721
+ :vartype path: Optional[str]
722
+ :ivar sha: Object SHA for the file.
723
+ :vartype sha: Optional[str]
724
+ :ivar template_owner: Owner path of the repository the template was resolved from.
725
+ :vartype template_owner: Optional[str]
726
+ :ivar template_repo: Repository name the template was resolved from.
727
+ :vartype template_repo: Optional[str]
728
+ """
729
+
730
+ path: Optional[str] = None
731
+ sha: Optional[str] = None
732
+ template_owner: Optional[str] = None
733
+ template_repo: Optional[str] = None
734
+
735
+
763
736
  @dataclass(init=False)
764
737
  class CommitIdentity(APIObject):
765
738
  """Commit author/committer identity.
@@ -2171,6 +2144,23 @@ class RepoMemberPermission(APIObject):
2171
2144
  permission: Optional[str] = None
2172
2145
 
2173
2146
 
2147
+ @dataclass(init=False)
2148
+ class ReleaseAsset(APIObject):
2149
+ """Asset metadata embedded in release payloads.
2150
+
2151
+ :ivar browser_download_url: Browser download URL for this asset.
2152
+ :vartype browser_download_url: Optional[str]
2153
+ :ivar name: Asset file name.
2154
+ :vartype name: Optional[str]
2155
+ :ivar type: Asset media type.
2156
+ :vartype type: Optional[str]
2157
+ """
2158
+
2159
+ browser_download_url: Optional[str] = None
2160
+ name: Optional[str] = None
2161
+ type: Optional[str] = None
2162
+
2163
+
2174
2164
  @dataclass(init=False)
2175
2165
  class Release(APIObject):
2176
2166
  """Release payload.
@@ -2179,6 +2169,10 @@ class Release(APIObject):
2179
2169
  :vartype id: Optional[Union[int, str]]
2180
2170
  :ivar tag_name: Tag name.
2181
2171
  :vartype tag_name: Optional[str]
2172
+ :ivar target_commitish: Branch or commit SHA targeted by the release tag.
2173
+ :vartype target_commitish: Optional[str]
2174
+ :ivar prerelease: Whether this release is a pre-release.
2175
+ :vartype prerelease: Optional[bool]
2182
2176
  :ivar name: Display name.
2183
2177
  :vartype name: Optional[str]
2184
2178
  :ivar body: Body text of the object.
@@ -2191,16 +2185,38 @@ class Release(APIObject):
2191
2185
  :vartype published_at: Optional[str]
2192
2186
  :ivar html_url: Web URL for this object.
2193
2187
  :vartype html_url: Optional[str]
2188
+ :ivar assets: Release source packages and uploaded attachments.
2189
+ :vartype assets: Optional[List[ReleaseAsset]]
2190
+ :ivar release_status: Release status, for example ``pre`` or ``latest``.
2191
+ :vartype release_status: Optional[str]
2194
2192
  """
2195
2193
 
2196
2194
  id: Optional[Union[int, str]] = None
2197
2195
  tag_name: Optional[str] = None
2196
+ target_commitish: Optional[str] = None
2197
+ prerelease: Optional[bool] = None
2198
2198
  name: Optional[str] = None
2199
2199
  body: Optional[str] = None
2200
2200
  author: Optional[UserRef] = None
2201
2201
  created_at: Optional[str] = None
2202
2202
  published_at: Optional[str] = None
2203
2203
  html_url: Optional[str] = None
2204
+ assets: Optional[List[ReleaseAsset]] = None
2205
+ release_status: Optional[str] = None
2206
+
2207
+
2208
+ @dataclass(init=False)
2209
+ class ReleaseUploadURL(APIObject):
2210
+ """Pre-signed release attachment upload target.
2211
+
2212
+ :ivar url: Pre-signed object storage URL for uploading with ``PUT``.
2213
+ :vartype url: Optional[str]
2214
+ :ivar headers: Headers to send unchanged with the upload request.
2215
+ :vartype headers: Optional[Dict[str, str]]
2216
+ """
2217
+
2218
+ url: Optional[str] = None
2219
+ headers: Optional[Dict[str, str]] = None
2204
2220
 
2205
2221
 
2206
2222
  @dataclass(init=False)
gitcode_api/llm/openai.py CHANGED
@@ -75,6 +75,7 @@ class GitCodeOpenAITool(GitCodeLLMTool):
75
75
  return self.tool
76
76
 
77
77
  async def __async_call__(self, *args, **kwargs) -> str:
78
+ """Invoke the configured async tool callable."""
78
79
  result = await super().__async_call__(*args, **kwargs)
79
80
  return json.dumps(result, ensure_ascii=False, indent=self.indent)
80
81
 
@@ -1,11 +1,317 @@
1
1
  """Shared resource base classes for the GitCode SDK."""
2
2
 
3
- from typing import Any, Dict, List, Optional, Union
3
+ import re
4
+ from typing import Any, Dict, List, Optional, Pattern, Tuple, Union
4
5
 
5
6
  from .._base_client import AsyncAPIClient, SyncAPIClient
6
7
  from .._base_resource import BaseResource
8
+ from .._exceptions import GitCodeHTTPStatusError
7
9
  from .._models import APIObject, ModelT, as_model, as_model_list
8
10
 
11
+ GITCODE_ISSUE_TEMPLATE_PATH_RE: Pattern[str] = re.compile(
12
+ r"\.gitcode/ISSUE_TEMPLATE.*\.(md|markdown|ya?ml)$", re.IGNORECASE
13
+ )
14
+ GITCODE_PULL_REQUEST_TEMPLATE_PATH_RE: Pattern[str] = re.compile(
15
+ r"\.gitcode/PULL_REQUEST_TEMPLATE.*\.(md|markdown|ya?ml)$", re.IGNORECASE
16
+ )
17
+
18
+
19
+ def _parse_parent_owner_repo(repo_obj: Any) -> Optional[Tuple[str, str]]:
20
+ parent = getattr(repo_obj, "parent", None)
21
+ if parent is None and hasattr(repo_obj, "get"):
22
+ parent = repo_obj.get("parent")
23
+ if not isinstance(parent, dict):
24
+ return None
25
+ full = str(parent.get("full_name") or "").strip()
26
+ if "/" not in full:
27
+ return None
28
+ owner_path, name = full.rsplit("/", 1)
29
+ owner_path, name = owner_path.strip(), name.strip()
30
+ if not owner_path or not name:
31
+ return None
32
+ return owner_path, name
33
+
34
+
35
+ def _resolution_sources_sync(client: SyncAPIClient, owner: str, repo: str) -> List[Tuple[str, str]]:
36
+ """Build ordered repo candidates.
37
+
38
+ Starts with ``(owner, repo)`` and ``(owner, ".gitcode")``. For every candidate that is a
39
+ fork (per ``GET /repos/{owner}/{repo}``), appends that repository's parent main repo and
40
+ ``(parent_owner, ".gitcode")`` when not already listed (deduplicated, breadth-first).
41
+ """
42
+ from .repositories import ReposResource
43
+
44
+ sources: List[Tuple[str, str]] = []
45
+ seen: set[Tuple[str, str]] = set()
46
+
47
+ def add(pair: Tuple[str, str]) -> None:
48
+ if pair not in seen:
49
+ seen.add(pair)
50
+ sources.append(pair)
51
+
52
+ add((owner, repo))
53
+ add((owner, ".gitcode"))
54
+
55
+ max_candidates = 64
56
+ index = 0
57
+ while index < len(sources) and len(sources) < max_candidates:
58
+ so, sr = sources[index]
59
+ index += 1
60
+ try:
61
+ meta = ReposResource(client).get(owner=so, repo=sr)
62
+ except GitCodeHTTPStatusError:
63
+ continue
64
+ if not getattr(meta, "fork", None):
65
+ continue
66
+ parsed = _parse_parent_owner_repo(meta)
67
+ if not parsed:
68
+ continue
69
+ po, pr = parsed
70
+ add((po, pr))
71
+ add((po, ".gitcode"))
72
+
73
+ return sources
74
+
75
+
76
+ async def _resolution_sources_async(client: AsyncAPIClient, owner: str, repo: str) -> List[Tuple[str, str]]:
77
+ """Async counterpart of :func:`_resolution_sources_sync`."""
78
+ from .repositories import AsyncReposResource
79
+
80
+ sources: List[Tuple[str, str]] = []
81
+ seen: set[Tuple[str, str]] = set()
82
+
83
+ def add(pair: Tuple[str, str]) -> None:
84
+ if pair not in seen:
85
+ seen.add(pair)
86
+ sources.append(pair)
87
+
88
+ add((owner, repo))
89
+ add((owner, ".gitcode"))
90
+
91
+ max_candidates = 64
92
+ index = 0
93
+ while index < len(sources) and len(sources) < max_candidates:
94
+ so, sr = sources[index]
95
+ index += 1
96
+ try:
97
+ meta = await AsyncReposResource(client).get(owner=so, repo=sr)
98
+ except GitCodeHTTPStatusError:
99
+ continue
100
+ if not getattr(meta, "fork", None):
101
+ continue
102
+ parsed = _parse_parent_owner_repo(meta)
103
+ if not parsed:
104
+ continue
105
+ po, pr = parsed
106
+ add((po, pr))
107
+ add((po, ".gitcode"))
108
+
109
+ return sources
110
+
111
+
112
+ def _walk_dot_gitcode_contents_sync(
113
+ client: SyncAPIClient,
114
+ template_owner: str,
115
+ template_repo: str,
116
+ path: str,
117
+ acc: List[Tuple[str, str, str, str]],
118
+ ) -> None:
119
+ try:
120
+ data = client.request(
121
+ "GET",
122
+ client._repo_file_path("contents", path, owner=template_owner, repo=template_repo),
123
+ )
124
+ except GitCodeHTTPStatusError as exc:
125
+ if exc.status_code == 404:
126
+ return
127
+ raise
128
+
129
+ if isinstance(data, dict):
130
+ t = (data.get("type") or "").lower()
131
+ if t == "file":
132
+ sha = data.get("sha") or ""
133
+ pth = data.get("path") or path
134
+ if sha and pth:
135
+ acc.append((template_owner, template_repo, str(pth), str(sha)))
136
+ return
137
+ if t in ("dir", "directory", "tree"):
138
+ pth = data.get("path") or path
139
+ _walk_dot_gitcode_contents_sync(client, template_owner, template_repo, pth, acc)
140
+ return
141
+ return
142
+
143
+ if isinstance(data, list):
144
+ for item in data:
145
+ if not isinstance(item, dict):
146
+ continue
147
+ t = (item.get("type") or "").lower()
148
+ pth = item.get("path") or ""
149
+ if t == "file":
150
+ sha = item.get("sha") or ""
151
+ if sha and pth:
152
+ acc.append((template_owner, template_repo, str(pth), str(sha)))
153
+ elif t in ("dir", "directory", "tree") and pth:
154
+ _walk_dot_gitcode_contents_sync(client, template_owner, template_repo, str(pth), acc)
155
+
156
+
157
+ async def _walk_dot_gitcode_contents_async(
158
+ client: AsyncAPIClient,
159
+ template_owner: str,
160
+ template_repo: str,
161
+ path: str,
162
+ acc: List[Tuple[str, str, str, str]],
163
+ ) -> None:
164
+ try:
165
+ data = await client.request(
166
+ "GET",
167
+ client._repo_file_path("contents", path, owner=template_owner, repo=template_repo),
168
+ )
169
+ except GitCodeHTTPStatusError as exc:
170
+ if exc.status_code == 404:
171
+ return
172
+ raise
173
+
174
+ if isinstance(data, dict):
175
+ t = (data.get("type") or "").lower()
176
+ if t == "file":
177
+ sha = data.get("sha") or ""
178
+ pth = data.get("path") or path
179
+ if sha and pth:
180
+ acc.append((template_owner, template_repo, str(pth), str(sha)))
181
+ return
182
+ if t in ("dir", "directory", "tree"):
183
+ pth = data.get("path") or path
184
+ await _walk_dot_gitcode_contents_async(client, template_owner, template_repo, pth, acc)
185
+ return
186
+ return
187
+
188
+ if isinstance(data, list):
189
+ for item in data:
190
+ if not isinstance(item, dict):
191
+ continue
192
+ t = (item.get("type") or "").lower()
193
+ pth = item.get("path") or ""
194
+ if t == "file":
195
+ sha = item.get("sha") or ""
196
+ if sha and pth:
197
+ acc.append((template_owner, template_repo, str(pth), str(sha)))
198
+ elif t in ("dir", "directory", "tree") and pth:
199
+ await _walk_dot_gitcode_contents_async(client, template_owner, template_repo, str(pth), acc)
200
+
201
+
202
+ def list_gitcode_template_rows_sync(
203
+ client: SyncAPIClient,
204
+ owner: str,
205
+ repo: str,
206
+ path_pattern: Pattern[str],
207
+ ) -> List[Tuple[str, str, str, str]]:
208
+ """Return ``(template_owner, template_repo, path, sha)`` from the first resolution source with matches."""
209
+ for so, sr in _resolution_sources_sync(client, owner, repo):
210
+ acc: List[Tuple[str, str, str, str]] = []
211
+ try:
212
+ _walk_dot_gitcode_contents_sync(client, so, sr, ".gitcode", acc)
213
+ except GitCodeHTTPStatusError:
214
+ continue
215
+ rows = [(t_o, t_r, p, s) for t_o, t_r, p, s in acc if path_pattern.match(p)]
216
+ if rows:
217
+ return rows
218
+ return []
219
+
220
+
221
+ async def list_gitcode_template_rows_async(
222
+ client: AsyncAPIClient,
223
+ owner: str,
224
+ repo: str,
225
+ path_pattern: Pattern[str],
226
+ ) -> List[Tuple[str, str, str, str]]:
227
+ for so, sr in await _resolution_sources_async(client, owner, repo):
228
+ acc: List[Tuple[str, str, str, str]] = []
229
+ try:
230
+ await _walk_dot_gitcode_contents_async(client, so, sr, ".gitcode", acc)
231
+ except GitCodeHTTPStatusError:
232
+ continue
233
+ rows = [(t_o, t_r, p, s) for t_o, t_r, p, s in acc if path_pattern.match(p)]
234
+ if rows:
235
+ return rows
236
+ return []
237
+
238
+
239
+ def get_gitcode_template_body_sync(
240
+ client: SyncAPIClient,
241
+ path: str,
242
+ path_pattern: Pattern[str],
243
+ owner: str,
244
+ repo: str,
245
+ ) -> str:
246
+ """Fetch raw template text from the first resolution source that serves ``path``."""
247
+ if not path_pattern.match(path):
248
+ raise GitCodeHTTPStatusError(
249
+ "Path does not match the expected GitCode template pattern for this resource.",
250
+ status_code=404,
251
+ payload=None,
252
+ )
253
+ last_error: Optional[GitCodeHTTPStatusError] = None
254
+ for so, sr in _resolution_sources_sync(client, owner, repo):
255
+ try:
256
+ raw = client.request(
257
+ "GET",
258
+ client._repo_file_path("raw", path, owner=so, repo=sr),
259
+ raw=True,
260
+ )
261
+ except GitCodeHTTPStatusError as exc:
262
+ last_error = exc
263
+ continue
264
+ if isinstance(raw, bytes):
265
+ return raw.decode("utf-8")
266
+ if isinstance(raw, str):
267
+ return raw
268
+ return str(raw)
269
+ if last_error is not None:
270
+ raise last_error
271
+ raise GitCodeHTTPStatusError(
272
+ "No template found for the given path.",
273
+ status_code=404,
274
+ payload=None,
275
+ )
276
+
277
+
278
+ async def get_gitcode_template_body_async(
279
+ client: AsyncAPIClient,
280
+ path: str,
281
+ path_pattern: Pattern[str],
282
+ owner: str,
283
+ repo: str,
284
+ ) -> str:
285
+ if not path_pattern.match(path):
286
+ raise GitCodeHTTPStatusError(
287
+ "Path does not match the expected GitCode template pattern for this resource.",
288
+ status_code=404,
289
+ payload=None,
290
+ )
291
+ last_error: Optional[GitCodeHTTPStatusError] = None
292
+ for so, sr in await _resolution_sources_async(client, owner, repo):
293
+ try:
294
+ raw = await client.request(
295
+ "GET",
296
+ client._repo_file_path("raw", path, owner=so, repo=sr),
297
+ raw=True,
298
+ )
299
+ except GitCodeHTTPStatusError as exc:
300
+ last_error = exc
301
+ continue
302
+ if isinstance(raw, bytes):
303
+ return raw.decode("utf-8")
304
+ if isinstance(raw, str):
305
+ return raw
306
+ return str(raw)
307
+ if last_error is not None:
308
+ raise last_error
309
+ raise GitCodeHTTPStatusError(
310
+ "No template found for the given path.",
311
+ status_code=404,
312
+ payload=None,
313
+ )
314
+
9
315
 
10
316
  class SyncResource(BaseResource):
11
317
  """Base class for synchronous resource groups."""