gitcode-api 1.0.0__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 +23 -0
- gitcode_api/_base_client.py +324 -0
- gitcode_api/_client.py +235 -0
- gitcode_api/_exceptions.py +39 -0
- gitcode_api/_models.py +272 -0
- gitcode_api/resources/__init__.py +77 -0
- gitcode_api/resources/_shared.py +75 -0
- gitcode_api/resources/account.py +766 -0
- gitcode_api/resources/collaboration.py +1451 -0
- gitcode_api/resources/misc.py +301 -0
- gitcode_api/resources/repositories.py +1458 -0
- gitcode_api-1.0.0.dist-info/METADATA +208 -0
- gitcode_api-1.0.0.dist-info/RECORD +16 -0
- gitcode_api-1.0.0.dist-info/WHEEL +5 -0
- gitcode_api-1.0.0.dist-info/licenses/LICENSE +21 -0
- gitcode_api-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,766 @@
|
|
|
1
|
+
"""Account, organization, search, and OAuth resource groups."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, List
|
|
4
|
+
from urllib.parse import urlencode
|
|
5
|
+
|
|
6
|
+
import httpx
|
|
7
|
+
|
|
8
|
+
from .._models import (
|
|
9
|
+
APIObject,
|
|
10
|
+
Email,
|
|
11
|
+
EnterpriseMember,
|
|
12
|
+
Namespace,
|
|
13
|
+
OAuthToken,
|
|
14
|
+
Organization,
|
|
15
|
+
OrganizationMembership,
|
|
16
|
+
Repository,
|
|
17
|
+
SearchResult,
|
|
18
|
+
User,
|
|
19
|
+
)
|
|
20
|
+
from ._shared import AsyncResource, SyncResource
|
|
21
|
+
|
|
22
|
+
OAUTH_BASE_URL = "https://gitcode.com"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class UsersResource(SyncResource):
|
|
26
|
+
"""Synchronous user and account endpoints."""
|
|
27
|
+
|
|
28
|
+
def get(self, *, username: str) -> User:
|
|
29
|
+
"""Get a user profile.
|
|
30
|
+
|
|
31
|
+
:param username: GitCode username or login.
|
|
32
|
+
:returns: User profile details.
|
|
33
|
+
"""
|
|
34
|
+
return self._model("GET", self._client._path("users", username), User)
|
|
35
|
+
|
|
36
|
+
def me(self) -> User:
|
|
37
|
+
"""Get the profile of the authenticated user.
|
|
38
|
+
|
|
39
|
+
:returns: Authorized user profile.
|
|
40
|
+
"""
|
|
41
|
+
return self._model("GET", self._client._path("user"), User)
|
|
42
|
+
|
|
43
|
+
def list_emails(self) -> List[Email]:
|
|
44
|
+
"""List email addresses for the authenticated user.
|
|
45
|
+
|
|
46
|
+
:returns: Email records associated with the current account.
|
|
47
|
+
"""
|
|
48
|
+
return self._models("GET", self._client._path("emails"), Email)
|
|
49
|
+
|
|
50
|
+
def list_events(self, *, username: str, year: str | None = None, next: str | None = None) -> APIObject:
|
|
51
|
+
"""List activity events for a user.
|
|
52
|
+
|
|
53
|
+
:param username: GitCode username or login.
|
|
54
|
+
:param year: Optional start year filter.
|
|
55
|
+
:param next: Optional pagination cursor from a previous response.
|
|
56
|
+
:returns: Event payload grouped by date.
|
|
57
|
+
"""
|
|
58
|
+
return self._model(
|
|
59
|
+
"GET", self._client._path("users", username, "events"), APIObject, params={"year": year, "next": next}
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
def list_repos(self, *, username: str, **params: Any) -> List[Repository]:
|
|
63
|
+
"""List public repositories owned by a user.
|
|
64
|
+
|
|
65
|
+
Supported filters follow the user repository API documentation, such as
|
|
66
|
+
``type``, ``sort``, ``direction``, ``page``, and ``per_page``.
|
|
67
|
+
|
|
68
|
+
:param username: GitCode username or login.
|
|
69
|
+
:param params: Query parameters accepted by the REST endpoint.
|
|
70
|
+
:returns: Matching repositories.
|
|
71
|
+
"""
|
|
72
|
+
return self._models("GET", self._client._path("users", username, "repos"), Repository, params=params)
|
|
73
|
+
|
|
74
|
+
def create_key(self, *, key: str, title: str) -> APIObject:
|
|
75
|
+
"""Add a public SSH key for the authenticated user.
|
|
76
|
+
|
|
77
|
+
:param key: Public key material.
|
|
78
|
+
:param title: Human-readable key name.
|
|
79
|
+
:returns: Created key metadata.
|
|
80
|
+
"""
|
|
81
|
+
return self._model("POST", self._client._path("user", "keys"), APIObject, json={"key": key, "title": title})
|
|
82
|
+
|
|
83
|
+
def list_keys(self, *, page: int | None = None, per_page: int | None = None) -> List[APIObject]:
|
|
84
|
+
"""List public SSH keys for the authenticated user.
|
|
85
|
+
|
|
86
|
+
:param page: Page number.
|
|
87
|
+
:param per_page: Page size.
|
|
88
|
+
:returns: Public key records.
|
|
89
|
+
"""
|
|
90
|
+
data = self._request("GET", self._client._path("user", "keys"), params={"page": page, "per_page": per_page})
|
|
91
|
+
return [APIObject(dict(item)) for item in data]
|
|
92
|
+
|
|
93
|
+
def delete_key(self, *, key_id: int | str) -> None:
|
|
94
|
+
"""Delete a public SSH key.
|
|
95
|
+
|
|
96
|
+
:param key_id: Public key identifier.
|
|
97
|
+
"""
|
|
98
|
+
self._request("DELETE", self._client._path("user", "keys", key_id))
|
|
99
|
+
|
|
100
|
+
def get_key(self, *, key_id: int | str) -> APIObject:
|
|
101
|
+
"""Get a single public SSH key.
|
|
102
|
+
|
|
103
|
+
:param key_id: Public key identifier.
|
|
104
|
+
:returns: Public key metadata.
|
|
105
|
+
"""
|
|
106
|
+
return self._model("GET", self._client._path("user", "keys", key_id), APIObject)
|
|
107
|
+
|
|
108
|
+
def get_namespace(self, *, path: str) -> Namespace:
|
|
109
|
+
"""Resolve namespace information for the authenticated user.
|
|
110
|
+
|
|
111
|
+
:param path: Namespace path to look up.
|
|
112
|
+
:returns: Namespace details.
|
|
113
|
+
"""
|
|
114
|
+
return self._model("GET", self._client._path("user", "namespace"), Namespace, params={"path": path})
|
|
115
|
+
|
|
116
|
+
def list_starred(
|
|
117
|
+
self,
|
|
118
|
+
*,
|
|
119
|
+
sort: str | None = None,
|
|
120
|
+
direction: str | None = None,
|
|
121
|
+
page: int | None = None,
|
|
122
|
+
per_page: int | None = None,
|
|
123
|
+
) -> List[Repository]:
|
|
124
|
+
"""List repositories starred by the authenticated user.
|
|
125
|
+
|
|
126
|
+
:param sort: Sort field such as ``created`` or ``updated``.
|
|
127
|
+
:param direction: Sort direction, usually ``asc`` or ``desc``.
|
|
128
|
+
:param page: Page number.
|
|
129
|
+
:param per_page: Page size.
|
|
130
|
+
:returns: Starred repositories.
|
|
131
|
+
"""
|
|
132
|
+
return self._models(
|
|
133
|
+
"GET",
|
|
134
|
+
self._client._path("user", "starred"),
|
|
135
|
+
Repository,
|
|
136
|
+
params={"sort": sort, "direction": direction, "page": page, "per_page": per_page},
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class OrgsResource(SyncResource):
|
|
141
|
+
"""Synchronous organization and enterprise endpoints."""
|
|
142
|
+
|
|
143
|
+
def list_for_user(
|
|
144
|
+
self, *, username: str, page: int | None = None, per_page: int | None = None
|
|
145
|
+
) -> List[Organization]:
|
|
146
|
+
"""List organizations for a user.
|
|
147
|
+
|
|
148
|
+
:param username: GitCode username or login.
|
|
149
|
+
:param page: Page number.
|
|
150
|
+
:param per_page: Page size.
|
|
151
|
+
:returns: Organizations the user belongs to.
|
|
152
|
+
"""
|
|
153
|
+
return self._models(
|
|
154
|
+
"GET",
|
|
155
|
+
self._client._path("users", username, "orgs"),
|
|
156
|
+
Organization,
|
|
157
|
+
params={"page": page, "per_page": per_page},
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
def list_authenticated(
|
|
161
|
+
self, *, page: int | None = None, per_page: int | None = None, admin: bool | None = None
|
|
162
|
+
) -> List[Organization]:
|
|
163
|
+
"""List organizations for the authenticated user.
|
|
164
|
+
|
|
165
|
+
:param page: Page number.
|
|
166
|
+
:param per_page: Page size.
|
|
167
|
+
:param admin: Optional admin-only filter.
|
|
168
|
+
:returns: Organizations visible to the authorized user.
|
|
169
|
+
"""
|
|
170
|
+
return self._models(
|
|
171
|
+
"GET",
|
|
172
|
+
self._client._path("users", "orgs"),
|
|
173
|
+
Organization,
|
|
174
|
+
params={"page": page, "per_page": per_page, "admin": admin},
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
def get_member(self, *, org: str, username: str) -> OrganizationMembership:
|
|
178
|
+
"""Get an organization member profile.
|
|
179
|
+
|
|
180
|
+
:param org: Organization path or login.
|
|
181
|
+
:param username: Member username or login.
|
|
182
|
+
:returns: Organization membership details.
|
|
183
|
+
"""
|
|
184
|
+
return self._model("GET", self._client._path("orgs", org, "members", username), OrganizationMembership)
|
|
185
|
+
|
|
186
|
+
def get(self, *, org: str) -> Organization:
|
|
187
|
+
"""Get an organization.
|
|
188
|
+
|
|
189
|
+
:param org: Organization path or login.
|
|
190
|
+
:returns: Organization details.
|
|
191
|
+
"""
|
|
192
|
+
return self._model("GET", self._client._path("orgs", org), Organization)
|
|
193
|
+
|
|
194
|
+
def list_repos(
|
|
195
|
+
self, *, org: str, type: str | None = None, page: int | None = None, per_page: int | None = None
|
|
196
|
+
) -> List[Repository]:
|
|
197
|
+
"""List repositories for an organization.
|
|
198
|
+
|
|
199
|
+
:param org: Organization path or login.
|
|
200
|
+
:param type: Repository type filter such as ``all``, ``public``, or ``private``.
|
|
201
|
+
:param page: Page number.
|
|
202
|
+
:param per_page: Page size.
|
|
203
|
+
:returns: Matching repositories.
|
|
204
|
+
"""
|
|
205
|
+
return self._models(
|
|
206
|
+
"GET",
|
|
207
|
+
self._client._path("orgs", org, "repos"),
|
|
208
|
+
Repository,
|
|
209
|
+
params={"type": type, "page": page, "per_page": per_page},
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
def create_repo(self, *, org: str, name: str, **payload: Any) -> Repository:
|
|
213
|
+
"""Create an organization repository.
|
|
214
|
+
|
|
215
|
+
Additional payload fields match the organization repository creation
|
|
216
|
+
endpoint, such as ``description``, ``homepage``, ``private``,
|
|
217
|
+
``public``, ``auto_init``, and ``default_branch``.
|
|
218
|
+
|
|
219
|
+
:param org: Organization path or login.
|
|
220
|
+
:param name: Repository name.
|
|
221
|
+
:param payload: Additional repository creation fields.
|
|
222
|
+
:returns: Created repository metadata.
|
|
223
|
+
"""
|
|
224
|
+
payload["name"] = name
|
|
225
|
+
return self._model("POST", self._client._path("orgs", org, "repos"), Repository, json=payload)
|
|
226
|
+
|
|
227
|
+
def get_enterprise_member(self, *, enterprise: str, username: str) -> EnterpriseMember:
|
|
228
|
+
"""Get a member profile for an enterprise.
|
|
229
|
+
|
|
230
|
+
:param enterprise: Enterprise path or login.
|
|
231
|
+
:param username: Member username or login.
|
|
232
|
+
:returns: Enterprise membership details.
|
|
233
|
+
"""
|
|
234
|
+
return self._model("GET", self._client._path("enterprises", enterprise, "members", username), EnterpriseMember)
|
|
235
|
+
|
|
236
|
+
def get_membership(self, *, org: str) -> OrganizationMembership:
|
|
237
|
+
"""Get the authenticated user's membership in an organization.
|
|
238
|
+
|
|
239
|
+
:param org: Organization path or login.
|
|
240
|
+
:returns: Membership details for the current user.
|
|
241
|
+
"""
|
|
242
|
+
return self._model("GET", self._client._path("user", "memberships", "orgs", org), OrganizationMembership)
|
|
243
|
+
|
|
244
|
+
def list_members(
|
|
245
|
+
self, *, org: str, page: int | None = None, per_page: int | None = None, role: str | None = None
|
|
246
|
+
) -> List[User]:
|
|
247
|
+
"""List members of an organization.
|
|
248
|
+
|
|
249
|
+
:param org: Organization path or login.
|
|
250
|
+
:param page: Page number.
|
|
251
|
+
:param per_page: Page size.
|
|
252
|
+
:param role: Optional role filter such as ``all``, ``admin``, or ``member``.
|
|
253
|
+
:returns: Organization members.
|
|
254
|
+
"""
|
|
255
|
+
return self._models(
|
|
256
|
+
"GET",
|
|
257
|
+
self._client._path("orgs", org, "members"),
|
|
258
|
+
User,
|
|
259
|
+
params={"page": page, "per_page": per_page, "role": role},
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
def list_enterprise_members(
|
|
263
|
+
self, *, enterprise: str, page: int | None = None, per_page: int | None = None, role: str | None = None
|
|
264
|
+
) -> List[EnterpriseMember]:
|
|
265
|
+
"""List members of an enterprise.
|
|
266
|
+
|
|
267
|
+
:param enterprise: Enterprise path or login.
|
|
268
|
+
:param page: Page number.
|
|
269
|
+
:param per_page: Page size.
|
|
270
|
+
:param role: Optional role filter.
|
|
271
|
+
:returns: Enterprise members.
|
|
272
|
+
"""
|
|
273
|
+
return self._models(
|
|
274
|
+
"GET",
|
|
275
|
+
self._client._path("enterprises", enterprise, "members"),
|
|
276
|
+
EnterpriseMember,
|
|
277
|
+
params={"page": page, "per_page": per_page, "role": role},
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
def remove_member(self, *, org: str, username: str) -> APIObject:
|
|
281
|
+
"""Remove a member from an organization.
|
|
282
|
+
|
|
283
|
+
:param org: Organization path or login.
|
|
284
|
+
:param username: Member username or login.
|
|
285
|
+
:returns: API response payload, if any.
|
|
286
|
+
"""
|
|
287
|
+
return self._model("DELETE", self._client._path("orgs", org, "memberships", username), APIObject)
|
|
288
|
+
|
|
289
|
+
def list_followers(self, *, owner: str, page: int | None = None, per_page: int | None = None) -> List[User]:
|
|
290
|
+
"""List followers of an organization.
|
|
291
|
+
|
|
292
|
+
:param owner: Organization path.
|
|
293
|
+
:param page: Page number.
|
|
294
|
+
:param per_page: Page size.
|
|
295
|
+
:returns: Followers of the organization.
|
|
296
|
+
"""
|
|
297
|
+
return self._models(
|
|
298
|
+
"GET",
|
|
299
|
+
self._client._path("orgs", owner, "followers"),
|
|
300
|
+
User,
|
|
301
|
+
params={"page": page, "per_page": per_page},
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
def get_issue_extend_settings(self, *, org: str) -> List[APIObject]:
|
|
305
|
+
"""Get extended issue type and status settings for an organization.
|
|
306
|
+
|
|
307
|
+
:param org: Organization path or login.
|
|
308
|
+
:returns: Extended issue configuration entries.
|
|
309
|
+
"""
|
|
310
|
+
data = self._request("GET", self._client._path("orgs", org, "issue", "extend", "settings"))
|
|
311
|
+
return [APIObject(dict(item)) for item in data]
|
|
312
|
+
|
|
313
|
+
def invite_member(
|
|
314
|
+
self, *, org: str, username: str, permission: str | None = None, role_id: str | None = None
|
|
315
|
+
) -> User:
|
|
316
|
+
"""Invite a user to an organization.
|
|
317
|
+
|
|
318
|
+
:param org: Organization path or login.
|
|
319
|
+
:param username: Member username or login.
|
|
320
|
+
:param permission: Permission level such as ``pull``, ``push``, or ``admin``.
|
|
321
|
+
:param role_id: Custom role identifier when using a customized permission.
|
|
322
|
+
:returns: Invited user information with permissions.
|
|
323
|
+
"""
|
|
324
|
+
return self._model(
|
|
325
|
+
"POST",
|
|
326
|
+
self._client._path("orgs", org, "memberships", username),
|
|
327
|
+
User,
|
|
328
|
+
json={"permission": permission, "role_id": role_id},
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
def update_enterprise_member(self, *, enterprise: str, username: str, role: str) -> EnterpriseMember:
|
|
332
|
+
"""Update the role of an enterprise member.
|
|
333
|
+
|
|
334
|
+
:param enterprise: Enterprise path or login.
|
|
335
|
+
:param username: Member username or login.
|
|
336
|
+
:param role: Enterprise role such as ``viewer``, ``developer``, or ``admin``.
|
|
337
|
+
:returns: Updated enterprise membership.
|
|
338
|
+
"""
|
|
339
|
+
return self._model(
|
|
340
|
+
"PUT",
|
|
341
|
+
self._client._path("enterprises", enterprise, "members", username),
|
|
342
|
+
EnterpriseMember,
|
|
343
|
+
json={"role": role},
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
def update(self, *, org: str, **payload: Any) -> Organization:
|
|
347
|
+
"""Update organization metadata.
|
|
348
|
+
|
|
349
|
+
:param org: Organization path or login.
|
|
350
|
+
:param payload: Updatable fields such as ``name``, ``email``, or ``description``.
|
|
351
|
+
:returns: Updated organization details.
|
|
352
|
+
"""
|
|
353
|
+
return self._model("PATCH", self._client._path("orgs", org), Organization, json=payload)
|
|
354
|
+
|
|
355
|
+
def leave(self, *, org: str) -> None:
|
|
356
|
+
"""Leave an organization as the authenticated user.
|
|
357
|
+
|
|
358
|
+
:param org: Organization path or login.
|
|
359
|
+
"""
|
|
360
|
+
self._request("DELETE", self._client._path("user", "memberships", "orgs", org))
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
class SearchResource(SyncResource):
|
|
364
|
+
"""Synchronous search endpoints."""
|
|
365
|
+
|
|
366
|
+
def users(
|
|
367
|
+
self,
|
|
368
|
+
*,
|
|
369
|
+
q: str,
|
|
370
|
+
page: int | None = None,
|
|
371
|
+
per_page: int | None = None,
|
|
372
|
+
sort: str | None = None,
|
|
373
|
+
order: str | None = None,
|
|
374
|
+
) -> List[SearchResult]:
|
|
375
|
+
"""Search users.
|
|
376
|
+
|
|
377
|
+
:param q: Search keywords.
|
|
378
|
+
:param page: Page number.
|
|
379
|
+
:param per_page: Page size.
|
|
380
|
+
:param sort: Optional sort field such as ``joined_at``.
|
|
381
|
+
:param order: Sort order, usually ``asc`` or ``desc``.
|
|
382
|
+
:returns: Matching user search results.
|
|
383
|
+
"""
|
|
384
|
+
return self._models(
|
|
385
|
+
"GET",
|
|
386
|
+
self._client._path("search", "users"),
|
|
387
|
+
SearchResult,
|
|
388
|
+
params={"q": q, "page": page, "per_page": per_page, "sort": sort, "order": order},
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
def issues(
|
|
392
|
+
self,
|
|
393
|
+
*,
|
|
394
|
+
q: str,
|
|
395
|
+
page: int | None = None,
|
|
396
|
+
per_page: int | None = None,
|
|
397
|
+
sort: str | None = None,
|
|
398
|
+
order: str | None = None,
|
|
399
|
+
repo: str | None = None,
|
|
400
|
+
state: str | None = None,
|
|
401
|
+
) -> List[SearchResult]:
|
|
402
|
+
"""Search issues.
|
|
403
|
+
|
|
404
|
+
:param q: Search keywords.
|
|
405
|
+
:param page: Page number.
|
|
406
|
+
:param per_page: Page size.
|
|
407
|
+
:param sort: Optional sort field.
|
|
408
|
+
:param order: Sort order, usually ``asc`` or ``desc``.
|
|
409
|
+
:param repo: Optional repository path filter.
|
|
410
|
+
:param state: Optional issue state filter.
|
|
411
|
+
:returns: Matching issue search results.
|
|
412
|
+
"""
|
|
413
|
+
return self._models(
|
|
414
|
+
"GET",
|
|
415
|
+
self._client._path("search", "issues"),
|
|
416
|
+
SearchResult,
|
|
417
|
+
params={
|
|
418
|
+
"q": q,
|
|
419
|
+
"page": page,
|
|
420
|
+
"per_page": per_page,
|
|
421
|
+
"sort": sort,
|
|
422
|
+
"order": order,
|
|
423
|
+
"repo": repo,
|
|
424
|
+
"state": state,
|
|
425
|
+
},
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
def repositories(
|
|
429
|
+
self,
|
|
430
|
+
*,
|
|
431
|
+
q: str,
|
|
432
|
+
page: int | None = None,
|
|
433
|
+
per_page: int | None = None,
|
|
434
|
+
sort: str | None = None,
|
|
435
|
+
order: str | None = None,
|
|
436
|
+
owner: str | None = None,
|
|
437
|
+
fork: str | None = None,
|
|
438
|
+
language: str | None = None,
|
|
439
|
+
) -> List[SearchResult]:
|
|
440
|
+
"""Search repositories.
|
|
441
|
+
|
|
442
|
+
:param q: Search keywords.
|
|
443
|
+
:param page: Page number.
|
|
444
|
+
:param per_page: Page size.
|
|
445
|
+
:param sort: Optional sort field such as ``stars_count``.
|
|
446
|
+
:param order: Sort order, usually ``asc`` or ``desc``.
|
|
447
|
+
:param owner: Optional owner path filter.
|
|
448
|
+
:param fork: Optional fork visibility filter.
|
|
449
|
+
:param language: Optional programming language filter.
|
|
450
|
+
:returns: Matching repository search results.
|
|
451
|
+
"""
|
|
452
|
+
return self._models(
|
|
453
|
+
"GET",
|
|
454
|
+
self._client._path("search", "repositories"),
|
|
455
|
+
SearchResult,
|
|
456
|
+
params={
|
|
457
|
+
"q": q,
|
|
458
|
+
"page": page,
|
|
459
|
+
"per_page": per_page,
|
|
460
|
+
"sort": sort,
|
|
461
|
+
"order": order,
|
|
462
|
+
"owner": owner,
|
|
463
|
+
"fork": fork,
|
|
464
|
+
"language": language,
|
|
465
|
+
},
|
|
466
|
+
)
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
class OAuthResource(SyncResource):
|
|
470
|
+
"""Helpers for GitCode OAuth URLs and token exchange."""
|
|
471
|
+
|
|
472
|
+
def build_authorize_url(
|
|
473
|
+
self,
|
|
474
|
+
*,
|
|
475
|
+
client_id: str,
|
|
476
|
+
redirect_uri: str,
|
|
477
|
+
scope: str | None = None,
|
|
478
|
+
state: str | None = None,
|
|
479
|
+
response_type: str = "code",
|
|
480
|
+
) -> str:
|
|
481
|
+
"""Build the GitCode OAuth authorization URL.
|
|
482
|
+
|
|
483
|
+
:param client_id: OAuth application client ID.
|
|
484
|
+
:param redirect_uri: Registered redirect URI.
|
|
485
|
+
:param scope: Optional OAuth scopes.
|
|
486
|
+
:param state: Optional CSRF protection value.
|
|
487
|
+
:param response_type: OAuth response type, defaults to ``"code"``.
|
|
488
|
+
:returns: Browser URL for the authorization step.
|
|
489
|
+
"""
|
|
490
|
+
query = urlencode(
|
|
491
|
+
{
|
|
492
|
+
key: value
|
|
493
|
+
for key, value in {
|
|
494
|
+
"client_id": client_id,
|
|
495
|
+
"redirect_uri": redirect_uri,
|
|
496
|
+
"response_type": response_type,
|
|
497
|
+
"scope": scope,
|
|
498
|
+
"state": state,
|
|
499
|
+
}.items()
|
|
500
|
+
if value is not None
|
|
501
|
+
}
|
|
502
|
+
)
|
|
503
|
+
return f"{OAUTH_BASE_URL}/oauth/authorize?{query}"
|
|
504
|
+
|
|
505
|
+
def exchange_token(self, *, code: str, client_id: str, client_secret: str) -> OAuthToken:
|
|
506
|
+
"""Exchange an authorization code for an OAuth token.
|
|
507
|
+
|
|
508
|
+
:param code: Authorization code returned by GitCode.
|
|
509
|
+
:param client_id: OAuth application client ID.
|
|
510
|
+
:param client_secret: OAuth application client secret.
|
|
511
|
+
:returns: OAuth access token payload.
|
|
512
|
+
"""
|
|
513
|
+
response = httpx.post(
|
|
514
|
+
f"{OAUTH_BASE_URL}/oauth/token",
|
|
515
|
+
params={"grant_type": "authorization_code", "code": code, "client_id": client_id},
|
|
516
|
+
data={"client_secret": client_secret},
|
|
517
|
+
headers={"Accept": "application/json"},
|
|
518
|
+
timeout=self._client.timeout,
|
|
519
|
+
)
|
|
520
|
+
response.raise_for_status()
|
|
521
|
+
return OAuthToken(dict(response.json()))
|
|
522
|
+
|
|
523
|
+
def refresh_token(self, *, refresh_token: str) -> OAuthToken:
|
|
524
|
+
"""Refresh an OAuth token.
|
|
525
|
+
|
|
526
|
+
:param refresh_token: Refresh token previously issued by GitCode.
|
|
527
|
+
:returns: Refreshed OAuth token payload.
|
|
528
|
+
"""
|
|
529
|
+
response = httpx.post(
|
|
530
|
+
f"{OAUTH_BASE_URL}/oauth/token",
|
|
531
|
+
params={"grant_type": "refresh_token", "refresh_token": refresh_token},
|
|
532
|
+
headers={"Accept": "application/json"},
|
|
533
|
+
timeout=self._client.timeout,
|
|
534
|
+
)
|
|
535
|
+
response.raise_for_status()
|
|
536
|
+
return OAuthToken(dict(response.json()))
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
class AsyncUsersResource(AsyncResource):
|
|
540
|
+
"""Asynchronous user and account endpoints."""
|
|
541
|
+
|
|
542
|
+
async def get(self, *, username: str) -> User:
|
|
543
|
+
return await self._model("GET", self._client._path("users", username), User)
|
|
544
|
+
|
|
545
|
+
async def me(self) -> User:
|
|
546
|
+
return await self._model("GET", self._client._path("user"), User)
|
|
547
|
+
|
|
548
|
+
async def list_emails(self) -> List[Email]:
|
|
549
|
+
return await self._models("GET", self._client._path("emails"), Email)
|
|
550
|
+
|
|
551
|
+
async def list_events(self, *, username: str, year: str | None = None, next: str | None = None) -> APIObject:
|
|
552
|
+
return await self._model(
|
|
553
|
+
"GET", self._client._path("users", username, "events"), APIObject, params={"year": year, "next": next}
|
|
554
|
+
)
|
|
555
|
+
|
|
556
|
+
async def list_repos(self, *, username: str, **params: Any) -> List[Repository]:
|
|
557
|
+
return await self._models("GET", self._client._path("users", username, "repos"), Repository, params=params)
|
|
558
|
+
|
|
559
|
+
async def create_key(self, *, key: str, title: str) -> APIObject:
|
|
560
|
+
return await self._model(
|
|
561
|
+
"POST", self._client._path("user", "keys"), APIObject, json={"key": key, "title": title}
|
|
562
|
+
)
|
|
563
|
+
|
|
564
|
+
async def list_keys(self, *, page: int | None = None, per_page: int | None = None) -> List[APIObject]:
|
|
565
|
+
data = await self._request(
|
|
566
|
+
"GET", self._client._path("user", "keys"), params={"page": page, "per_page": per_page}
|
|
567
|
+
)
|
|
568
|
+
return [APIObject(dict(item)) for item in data]
|
|
569
|
+
|
|
570
|
+
async def delete_key(self, *, key_id: int | str) -> None:
|
|
571
|
+
await self._request("DELETE", self._client._path("user", "keys", key_id))
|
|
572
|
+
|
|
573
|
+
async def get_key(self, *, key_id: int | str) -> APIObject:
|
|
574
|
+
return await self._model("GET", self._client._path("user", "keys", key_id), APIObject)
|
|
575
|
+
|
|
576
|
+
async def get_namespace(self, *, path: str) -> Namespace:
|
|
577
|
+
return await self._model("GET", self._client._path("user", "namespace"), Namespace, params={"path": path})
|
|
578
|
+
|
|
579
|
+
async def list_starred(
|
|
580
|
+
self,
|
|
581
|
+
*,
|
|
582
|
+
sort: str | None = None,
|
|
583
|
+
direction: str | None = None,
|
|
584
|
+
page: int | None = None,
|
|
585
|
+
per_page: int | None = None,
|
|
586
|
+
) -> List[Repository]:
|
|
587
|
+
return await self._models(
|
|
588
|
+
"GET",
|
|
589
|
+
self._client._path("user", "starred"),
|
|
590
|
+
Repository,
|
|
591
|
+
params={"sort": sort, "direction": direction, "page": page, "per_page": per_page},
|
|
592
|
+
)
|
|
593
|
+
|
|
594
|
+
|
|
595
|
+
class AsyncOrgsResource(AsyncResource):
|
|
596
|
+
"""Asynchronous organization and enterprise endpoints."""
|
|
597
|
+
|
|
598
|
+
async def list_for_user(
|
|
599
|
+
self, *, username: str, page: int | None = None, per_page: int | None = None
|
|
600
|
+
) -> List[Organization]:
|
|
601
|
+
return await self._models(
|
|
602
|
+
"GET",
|
|
603
|
+
self._client._path("users", username, "orgs"),
|
|
604
|
+
Organization,
|
|
605
|
+
params={"page": page, "per_page": per_page},
|
|
606
|
+
)
|
|
607
|
+
|
|
608
|
+
async def list_authenticated(
|
|
609
|
+
self, *, page: int | None = None, per_page: int | None = None, admin: bool | None = None
|
|
610
|
+
) -> List[Organization]:
|
|
611
|
+
return await self._models(
|
|
612
|
+
"GET",
|
|
613
|
+
self._client._path("users", "orgs"),
|
|
614
|
+
Organization,
|
|
615
|
+
params={"page": page, "per_page": per_page, "admin": admin},
|
|
616
|
+
)
|
|
617
|
+
|
|
618
|
+
async def get_member(self, *, org: str, username: str) -> OrganizationMembership:
|
|
619
|
+
return await self._model("GET", self._client._path("orgs", org, "members", username), OrganizationMembership)
|
|
620
|
+
|
|
621
|
+
async def get(self, *, org: str) -> Organization:
|
|
622
|
+
return await self._model("GET", self._client._path("orgs", org), Organization)
|
|
623
|
+
|
|
624
|
+
async def list_repos(
|
|
625
|
+
self, *, org: str, type: str | None = None, page: int | None = None, per_page: int | None = None
|
|
626
|
+
) -> List[Repository]:
|
|
627
|
+
return await self._models(
|
|
628
|
+
"GET",
|
|
629
|
+
self._client._path("orgs", org, "repos"),
|
|
630
|
+
Repository,
|
|
631
|
+
params={"type": type, "page": page, "per_page": per_page},
|
|
632
|
+
)
|
|
633
|
+
|
|
634
|
+
async def create_repo(self, *, org: str, name: str, **payload: Any) -> Repository:
|
|
635
|
+
payload["name"] = name
|
|
636
|
+
return await self._model("POST", self._client._path("orgs", org, "repos"), Repository, json=payload)
|
|
637
|
+
|
|
638
|
+
async def get_enterprise_member(self, *, enterprise: str, username: str) -> EnterpriseMember:
|
|
639
|
+
return await self._model(
|
|
640
|
+
"GET", self._client._path("enterprises", enterprise, "members", username), EnterpriseMember
|
|
641
|
+
)
|
|
642
|
+
|
|
643
|
+
async def get_membership(self, *, org: str) -> OrganizationMembership:
|
|
644
|
+
return await self._model("GET", self._client._path("user", "memberships", "orgs", org), OrganizationMembership)
|
|
645
|
+
|
|
646
|
+
async def list_members(
|
|
647
|
+
self, *, org: str, page: int | None = None, per_page: int | None = None, role: str | None = None
|
|
648
|
+
) -> List[User]:
|
|
649
|
+
return await self._models(
|
|
650
|
+
"GET",
|
|
651
|
+
self._client._path("orgs", org, "members"),
|
|
652
|
+
User,
|
|
653
|
+
params={"page": page, "per_page": per_page, "role": role},
|
|
654
|
+
)
|
|
655
|
+
|
|
656
|
+
async def list_enterprise_members(
|
|
657
|
+
self, *, enterprise: str, page: int | None = None, per_page: int | None = None, role: str | None = None
|
|
658
|
+
) -> List[EnterpriseMember]:
|
|
659
|
+
return await self._models(
|
|
660
|
+
"GET",
|
|
661
|
+
self._client._path("enterprises", enterprise, "members"),
|
|
662
|
+
EnterpriseMember,
|
|
663
|
+
params={"page": page, "per_page": per_page, "role": role},
|
|
664
|
+
)
|
|
665
|
+
|
|
666
|
+
async def remove_member(self, *, org: str, username: str) -> APIObject:
|
|
667
|
+
return await self._model("DELETE", self._client._path("orgs", org, "memberships", username), APIObject)
|
|
668
|
+
|
|
669
|
+
async def list_followers(self, *, owner: str, page: int | None = None, per_page: int | None = None) -> List[User]:
|
|
670
|
+
return await self._models(
|
|
671
|
+
"GET", self._client._path("orgs", owner, "followers"), User, params={"page": page, "per_page": per_page}
|
|
672
|
+
)
|
|
673
|
+
|
|
674
|
+
async def get_issue_extend_settings(self, *, org: str) -> List[APIObject]:
|
|
675
|
+
data = await self._request("GET", self._client._path("orgs", org, "issue", "extend", "settings"))
|
|
676
|
+
return [APIObject(dict(item)) for item in data]
|
|
677
|
+
|
|
678
|
+
async def invite_member(
|
|
679
|
+
self, *, org: str, username: str, permission: str | None = None, role_id: str | None = None
|
|
680
|
+
) -> User:
|
|
681
|
+
return await self._model(
|
|
682
|
+
"POST",
|
|
683
|
+
self._client._path("orgs", org, "memberships", username),
|
|
684
|
+
User,
|
|
685
|
+
json={"permission": permission, "role_id": role_id},
|
|
686
|
+
)
|
|
687
|
+
|
|
688
|
+
async def update_enterprise_member(self, *, enterprise: str, username: str, role: str) -> EnterpriseMember:
|
|
689
|
+
return await self._model(
|
|
690
|
+
"PUT",
|
|
691
|
+
self._client._path("enterprises", enterprise, "members", username),
|
|
692
|
+
EnterpriseMember,
|
|
693
|
+
json={"role": role},
|
|
694
|
+
)
|
|
695
|
+
|
|
696
|
+
async def update(self, *, org: str, **payload: Any) -> Organization:
|
|
697
|
+
return await self._model("PATCH", self._client._path("orgs", org), Organization, json=payload)
|
|
698
|
+
|
|
699
|
+
async def leave(self, *, org: str) -> None:
|
|
700
|
+
await self._request("DELETE", self._client._path("user", "memberships", "orgs", org))
|
|
701
|
+
|
|
702
|
+
|
|
703
|
+
class AsyncSearchResource(AsyncResource):
|
|
704
|
+
"""Asynchronous search endpoints."""
|
|
705
|
+
|
|
706
|
+
async def users(self, *, q: str, **params: Any) -> List[SearchResult]:
|
|
707
|
+
return await self._models("GET", self._client._path("search", "users"), SearchResult, params={"q": q, **params})
|
|
708
|
+
|
|
709
|
+
async def issues(self, *, q: str, **params: Any) -> List[SearchResult]:
|
|
710
|
+
return await self._models(
|
|
711
|
+
"GET", self._client._path("search", "issues"), SearchResult, params={"q": q, **params}
|
|
712
|
+
)
|
|
713
|
+
|
|
714
|
+
async def repositories(self, *, q: str, **params: Any) -> List[SearchResult]:
|
|
715
|
+
return await self._models(
|
|
716
|
+
"GET", self._client._path("search", "repositories"), SearchResult, params={"q": q, **params}
|
|
717
|
+
)
|
|
718
|
+
|
|
719
|
+
|
|
720
|
+
class AsyncOAuthResource(AsyncResource):
|
|
721
|
+
"""Asynchronous helpers for GitCode OAuth flows."""
|
|
722
|
+
|
|
723
|
+
def build_authorize_url(
|
|
724
|
+
self,
|
|
725
|
+
*,
|
|
726
|
+
client_id: str,
|
|
727
|
+
redirect_uri: str,
|
|
728
|
+
scope: str | None = None,
|
|
729
|
+
state: str | None = None,
|
|
730
|
+
response_type: str = "code",
|
|
731
|
+
) -> str:
|
|
732
|
+
query = urlencode(
|
|
733
|
+
{
|
|
734
|
+
key: value
|
|
735
|
+
for key, value in {
|
|
736
|
+
"client_id": client_id,
|
|
737
|
+
"redirect_uri": redirect_uri,
|
|
738
|
+
"response_type": response_type,
|
|
739
|
+
"scope": scope,
|
|
740
|
+
"state": state,
|
|
741
|
+
}.items()
|
|
742
|
+
if value is not None
|
|
743
|
+
}
|
|
744
|
+
)
|
|
745
|
+
return f"{OAUTH_BASE_URL}/oauth/authorize?{query}"
|
|
746
|
+
|
|
747
|
+
async def exchange_token(self, *, code: str, client_id: str, client_secret: str) -> OAuthToken:
|
|
748
|
+
async with httpx.AsyncClient(timeout=self._client.timeout) as client:
|
|
749
|
+
response = await client.post(
|
|
750
|
+
f"{OAUTH_BASE_URL}/oauth/token",
|
|
751
|
+
params={"grant_type": "authorization_code", "code": code, "client_id": client_id},
|
|
752
|
+
data={"client_secret": client_secret},
|
|
753
|
+
headers={"Accept": "application/json"},
|
|
754
|
+
)
|
|
755
|
+
response.raise_for_status()
|
|
756
|
+
return OAuthToken(dict(response.json()))
|
|
757
|
+
|
|
758
|
+
async def refresh_token(self, *, refresh_token: str) -> OAuthToken:
|
|
759
|
+
async with httpx.AsyncClient(timeout=self._client.timeout) as client:
|
|
760
|
+
response = await client.post(
|
|
761
|
+
f"{OAUTH_BASE_URL}/oauth/token",
|
|
762
|
+
params={"grant_type": "refresh_token", "refresh_token": refresh_token},
|
|
763
|
+
headers={"Accept": "application/json"},
|
|
764
|
+
)
|
|
765
|
+
response.raise_for_status()
|
|
766
|
+
return OAuthToken(dict(response.json()))
|