ghapi 2.0.2__tar.gz → 2.0.4__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.
- {ghapi-2.0.2/ghapi.egg-info → ghapi-2.0.4}/PKG-INFO +2 -2
- ghapi-2.0.4/ghapi/__init__.py +1 -0
- {ghapi-2.0.2 → ghapi-2.0.4}/ghapi/_modidx.py +24 -20
- {ghapi-2.0.2 → ghapi-2.0.4}/ghapi/core.py +63 -22
- {ghapi-2.0.2 → ghapi-2.0.4}/ghapi/skill.py +20 -3
- {ghapi-2.0.2 → ghapi-2.0.4/ghapi.egg-info}/PKG-INFO +2 -2
- {ghapi-2.0.2 → ghapi-2.0.4}/ghapi.egg-info/requires.txt +1 -1
- {ghapi-2.0.2 → ghapi-2.0.4}/pyproject.toml +1 -1
- ghapi-2.0.2/ghapi/__init__.py +0 -1
- {ghapi-2.0.2 → ghapi-2.0.4}/CONTRIBUTING.md +0 -0
- {ghapi-2.0.2 → ghapi-2.0.4}/LICENSE +0 -0
- {ghapi-2.0.2 → ghapi-2.0.4}/MANIFEST.in +0 -0
- {ghapi-2.0.2 → ghapi-2.0.4}/README.md +0 -0
- {ghapi-2.0.2 → ghapi-2.0.4}/ghapi/actions.py +0 -0
- {ghapi-2.0.2 → ghapi-2.0.4}/ghapi/all.py +0 -0
- {ghapi-2.0.2 → ghapi-2.0.4}/ghapi/auth.py +0 -0
- {ghapi-2.0.2 → ghapi-2.0.4}/ghapi/build_lib.py +0 -0
- {ghapi-2.0.2 → ghapi-2.0.4}/ghapi/cli.py +0 -0
- {ghapi-2.0.2 → ghapi-2.0.4}/ghapi/event.py +0 -0
- {ghapi-2.0.2 → ghapi-2.0.4}/ghapi/gh_spec.py +0 -0
- {ghapi-2.0.2 → ghapi-2.0.4}/ghapi/graphql.py +0 -0
- {ghapi-2.0.2 → ghapi-2.0.4}/ghapi/page.py +0 -0
- {ghapi-2.0.2 → ghapi-2.0.4}/ghapi/py.typed +0 -0
- {ghapi-2.0.2 → ghapi-2.0.4}/ghapi/templates.py +0 -0
- {ghapi-2.0.2 → ghapi-2.0.4}/ghapi.egg-info/SOURCES.txt +0 -0
- {ghapi-2.0.2 → ghapi-2.0.4}/ghapi.egg-info/dependency_links.txt +0 -0
- {ghapi-2.0.2 → ghapi-2.0.4}/ghapi.egg-info/entry_points.txt +0 -0
- {ghapi-2.0.2 → ghapi-2.0.4}/ghapi.egg-info/top_level.txt +0 -0
- {ghapi-2.0.2 → ghapi-2.0.4}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ghapi
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.4
|
|
4
4
|
Summary: A python client for the GitHub API
|
|
5
5
|
Author-email: Jeremy Howard <info@fast.ai>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -16,7 +16,7 @@ Requires-Python: >=3.10
|
|
|
16
16
|
Description-Content-Type: text/markdown
|
|
17
17
|
License-File: LICENSE
|
|
18
18
|
Requires-Dist: fastcore>=1.14.5
|
|
19
|
-
Requires-Dist: fastspec>=0.1.
|
|
19
|
+
Requires-Dist: fastspec>=0.1.2
|
|
20
20
|
Provides-Extra: dev
|
|
21
21
|
Requires-Dist: fastgit; extra == "dev"
|
|
22
22
|
Requires-Dist: matplotlib; extra == "dev"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "2.0.4"
|
|
@@ -48,34 +48,15 @@ d = { 'settings': { 'branch': 'main',
|
|
|
48
48
|
'ghapi.core.GhApi.__init__': ('core.html#ghapi.__init__', 'ghapi/core.py'),
|
|
49
49
|
'ghapi.core.GhApi._get_repo_files': ('core.html#ghapi._get_repo_files', 'ghapi/core.py'),
|
|
50
50
|
'ghapi.core.GhApi._repr_markdown_': ('core.html#ghapi._repr_markdown_', 'ghapi/core.py'),
|
|
51
|
-
'ghapi.core.GhApi.check_status': ('core.html#ghapi.check_status', 'ghapi/core.py'),
|
|
52
|
-
'ghapi.core.GhApi.create_branch_empty': ('core.html#ghapi.create_branch_empty', 'ghapi/core.py'),
|
|
53
|
-
'ghapi.core.GhApi.create_file': ('core.html#ghapi.create_file', 'ghapi/core.py'),
|
|
54
51
|
'ghapi.core.GhApi.create_gist': ('core.html#ghapi.create_gist', 'ghapi/core.py'),
|
|
55
|
-
'ghapi.core.GhApi.create_or_update_file': ('core.html#ghapi.create_or_update_file', 'ghapi/core.py'),
|
|
56
|
-
'ghapi.core.GhApi.create_release': ('core.html#ghapi.create_release', 'ghapi/core.py'),
|
|
57
52
|
'ghapi.core.GhApi.debug': ('core.html#ghapi.debug', 'ghapi/core.py'),
|
|
58
|
-
'ghapi.core.GhApi.delete_branch': ('core.html#ghapi.delete_branch', 'ghapi/core.py'),
|
|
59
|
-
'ghapi.core.GhApi.delete_file': ('core.html#ghapi.delete_file', 'ghapi/core.py'),
|
|
60
|
-
'ghapi.core.GhApi.delete_release': ('core.html#ghapi.delete_release', 'ghapi/core.py'),
|
|
61
|
-
'ghapi.core.GhApi.delete_tag': ('core.html#ghapi.delete_tag', 'ghapi/core.py'),
|
|
62
|
-
'ghapi.core.GhApi.enable_pages': ('core.html#ghapi.enable_pages', 'ghapi/core.py'),
|
|
63
|
-
'ghapi.core.GhApi.get_branch': ('core.html#ghapi.get_branch', 'ghapi/core.py'),
|
|
64
|
-
'ghapi.core.GhApi.get_content': ('core.html#ghapi.get_content', 'ghapi/core.py'),
|
|
65
53
|
'ghapi.core.GhApi.get_file_content': ('core.html#ghapi.get_file_content', 'ghapi/core.py'),
|
|
66
54
|
'ghapi.core.GhApi.get_repo_contents': ('core.html#ghapi.get_repo_contents', 'ghapi/core.py'),
|
|
67
55
|
'ghapi.core.GhApi.get_repo_files': ('core.html#ghapi.get_repo_files', 'ghapi/core.py'),
|
|
68
56
|
'ghapi.core.GhApi.gist_file': ('core.html#ghapi.gist_file', 'ghapi/core.py'),
|
|
69
|
-
'ghapi.core.GhApi.issue_template': ('core.html#ghapi.issue_template', 'ghapi/core.py'),
|
|
70
57
|
'ghapi.core.GhApi.limit_rem': ('core.html#ghapi.limit_rem', 'ghapi/core.py'),
|
|
71
|
-
'ghapi.core.GhApi.list_branches': ('core.html#ghapi.list_branches', 'ghapi/core.py'),
|
|
72
|
-
'ghapi.core.GhApi.list_files': ('core.html#ghapi.list_files', 'ghapi/core.py'),
|
|
73
|
-
'ghapi.core.GhApi.list_tags': ('core.html#ghapi.list_tags', 'ghapi/core.py'),
|
|
74
58
|
'ghapi.core.GhApi.load_gist': ('core.html#ghapi.load_gist', 'ghapi/core.py'),
|
|
75
|
-
'ghapi.core.GhApi.pr_status': ('core.html#ghapi.pr_status', 'ghapi/core.py'),
|
|
76
|
-
'ghapi.core.GhApi.read_issue': ('core.html#ghapi.read_issue', 'ghapi/core.py'),
|
|
77
59
|
'ghapi.core.GhApi.recv_hdrs': ('core.html#ghapi.recv_hdrs', 'ghapi/core.py'),
|
|
78
|
-
'ghapi.core.GhApi.update_contents': ('core.html#ghapi.update_contents', 'ghapi/core.py'),
|
|
79
60
|
'ghapi.core.GhApi.update_gist': ('core.html#ghapi.update_gist', 'ghapi/core.py'),
|
|
80
61
|
'ghapi.core.GhApi.upload_file': ('core.html#ghapi.upload_file', 'ghapi/core.py'),
|
|
81
62
|
'ghapi.core.GhSyncTransport': ('core.html#ghsynctransport', 'ghapi/core.py'),
|
|
@@ -90,6 +71,8 @@ d = { 'settings': { 'branch': 'main',
|
|
|
90
71
|
'ghapi.core._CheckStatus._repr_markdown_': ('core.html#_checkstatus._repr_markdown_', 'ghapi/core.py'),
|
|
91
72
|
'ghapi.core._IssueInfo': ('core.html#_issueinfo', 'ghapi/core.py'),
|
|
92
73
|
'ghapi.core._IssueInfo._repr_markdown_': ('core.html#_issueinfo._repr_markdown_', 'ghapi/core.py'),
|
|
74
|
+
'ghapi.core._LiveDefaults': ('core.html#_livedefaults', 'ghapi/core.py'),
|
|
75
|
+
'ghapi.core._LiveDefaults.items': ('core.html#_livedefaults.items', 'ghapi/core.py'),
|
|
93
76
|
'ghapi.core._filter_diff': ('core.html#_filter_diff', 'ghapi/core.py'),
|
|
94
77
|
'ghapi.core._find_matches': ('core.html#_find_matches', 'ghapi/core.py'),
|
|
95
78
|
'ghapi.core._fmt_replies': ('core.html#_fmt_replies', 'ghapi/core.py'),
|
|
@@ -98,12 +81,33 @@ d = { 'settings': { 'branch': 'main',
|
|
|
98
81
|
'ghapi.core._reduce_ctx': ('core.html#_reduce_ctx', 'ghapi/core.py'),
|
|
99
82
|
'ghapi.core._run_subp': ('core.html#_run_subp', 'ghapi/core.py'),
|
|
100
83
|
'ghapi.core.call_gh': ('core.html#call_gh', 'ghapi/core.py'),
|
|
84
|
+
'ghapi.core.check_status': ('core.html#check_status', 'ghapi/core.py'),
|
|
85
|
+
'ghapi.core.commit_tree': ('core.html#commit_tree', 'ghapi/core.py'),
|
|
86
|
+
'ghapi.core.create_branch_empty': ('core.html#create_branch_empty', 'ghapi/core.py'),
|
|
87
|
+
'ghapi.core.create_file': ('core.html#create_file', 'ghapi/core.py'),
|
|
88
|
+
'ghapi.core.create_or_update_file': ('core.html#create_or_update_file', 'ghapi/core.py'),
|
|
89
|
+
'ghapi.core.create_release': ('core.html#create_release', 'ghapi/core.py'),
|
|
101
90
|
'ghapi.core.date2gh': ('core.html#date2gh', 'ghapi/core.py'),
|
|
91
|
+
'ghapi.core.delete_branch': ('core.html#delete_branch', 'ghapi/core.py'),
|
|
92
|
+
'ghapi.core.delete_file': ('core.html#delete_file', 'ghapi/core.py'),
|
|
93
|
+
'ghapi.core.delete_release': ('core.html#delete_release', 'ghapi/core.py'),
|
|
94
|
+
'ghapi.core.delete_tag': ('core.html#delete_tag', 'ghapi/core.py'),
|
|
95
|
+
'ghapi.core.enable_pages': ('core.html#enable_pages', 'ghapi/core.py'),
|
|
96
|
+
'ghapi.core.get_branch': ('core.html#get_branch', 'ghapi/core.py'),
|
|
97
|
+
'ghapi.core.get_content': ('core.html#get_content', 'ghapi/core.py'),
|
|
102
98
|
'ghapi.core.gh2date': ('core.html#gh2date', 'ghapi/core.py'),
|
|
99
|
+
'ghapi.core.gh_patch': ('core.html#gh_patch', 'ghapi/core.py'),
|
|
103
100
|
'ghapi.core.issue_body': ('core.html#issue_body', 'ghapi/core.py'),
|
|
101
|
+
'ghapi.core.issue_template': ('core.html#issue_template', 'ghapi/core.py'),
|
|
102
|
+
'ghapi.core.list_branches': ('core.html#list_branches', 'ghapi/core.py'),
|
|
103
|
+
'ghapi.core.list_files': ('core.html#list_files', 'ghapi/core.py'),
|
|
104
|
+
'ghapi.core.list_tags': ('core.html#list_tags', 'ghapi/core.py'),
|
|
104
105
|
'ghapi.core.pr_file_diff': ('core.html#pr_file_diff', 'ghapi/core.py'),
|
|
106
|
+
'ghapi.core.pr_status': ('core.html#pr_status', 'ghapi/core.py'),
|
|
105
107
|
'ghapi.core.print_summary': ('core.html#print_summary', 'ghapi/core.py'),
|
|
106
|
-
'ghapi.core.
|
|
108
|
+
'ghapi.core.read_issue': ('core.html#read_issue', 'ghapi/core.py'),
|
|
109
|
+
'ghapi.core.read_pr': ('core.html#read_pr', 'ghapi/core.py'),
|
|
110
|
+
'ghapi.core.update_contents': ('core.html#update_contents', 'ghapi/core.py')},
|
|
107
111
|
'ghapi.event': { 'ghapi.event.GhApi.fetch_events': ('event.html#ghapi.fetch_events', 'ghapi/event.py'),
|
|
108
112
|
'ghapi.event.GhApi.list_events': ('event.html#ghapi.list_events', 'ghapi/event.py'),
|
|
109
113
|
'ghapi.event.GhApi.list_events_parallel': ('event.html#ghapi.list_events_parallel', 'ghapi/event.py'),
|
|
@@ -5,10 +5,16 @@ Docs: https://ghapi.fast.ai/core.html.md"""
|
|
|
5
5
|
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/00_core.ipynb.
|
|
6
6
|
|
|
7
7
|
# %% auto #0
|
|
8
|
-
__all__ = ['GH_HOST', 'pspec', 'img_md_pat', 'EMPTY_TREE_SHA', 'GhTransport', 'GhSyncTransport', 'print_summary', '
|
|
9
|
-
'call_gh', 'date2gh', 'gh2date', '
|
|
8
|
+
__all__ = ['GH_HOST', 'pspec', 'img_md_pat', 'EMPTY_TREE_SHA', 'GhTransport', 'GhSyncTransport', 'print_summary', 'gh_patch',
|
|
9
|
+
'GhApi', 'call_gh', 'date2gh', 'gh2date', 'delete_release', 'create_release', 'list_tags', 'list_branches',
|
|
10
|
+
'create_branch_empty', 'delete_tag', 'delete_branch', 'get_branch', 'commit_tree', 'list_files',
|
|
11
|
+
'get_content', 'create_or_update_file', 'create_file', 'delete_file', 'update_contents', 'enable_pages',
|
|
12
|
+
'read_issue', 'read_pr', 'pr_file_diff', 'issue_template', 'issue_body', 'check_status', 'pr_status',
|
|
13
|
+
'APIError']
|
|
10
14
|
|
|
11
15
|
# %% ../nbs/00_core.ipynb #5b5cba7b
|
|
16
|
+
from contextvars import ContextVar
|
|
17
|
+
|
|
12
18
|
from fastcore.all import *
|
|
13
19
|
from fastspec.spec import SpecParser
|
|
14
20
|
from fastspec.oapi import OpenAPIClient, OpFunc, SyncOpFunc, _build_groups, UNSET
|
|
@@ -85,6 +91,23 @@ def print_summary(method, url, kwargs):
|
|
|
85
91
|
hdrs = {k:v for k,v in (kwargs.get('headers') or {}).items() if k.lower()!='authorization'}
|
|
86
92
|
print(method, url, {**kwargs, 'headers':hdrs})
|
|
87
93
|
|
|
94
|
+
# %% ../nbs/00_core.ipynb #cecd0177
|
|
95
|
+
_gh_override = ContextVar('_gh_override', default={})
|
|
96
|
+
|
|
97
|
+
class _LiveDefaults(dict):
|
|
98
|
+
"Endpoint defaults, superseded at call time by any active `gh_patch` override"
|
|
99
|
+
def items(self): return {**self, **_gh_override.get()}.items()
|
|
100
|
+
|
|
101
|
+
def gh_patch(fn):
|
|
102
|
+
"`patch` `fn` into `GhApi`, adding `owner`/`repo` params that override the client defaults for this call and its internal calls"
|
|
103
|
+
async def _f(self, *args, owner=UNSET, repo=UNSET, **kwargs):
|
|
104
|
+
over = {k:v for k,v in zip(('owner','repo'),(owner,repo)) if v is not UNSET}
|
|
105
|
+
if not over: return await fn(self, *args, **kwargs)
|
|
106
|
+
token = _gh_override.set({**_gh_override.get(), **over})
|
|
107
|
+
try: return await fn(self, *args, **kwargs)
|
|
108
|
+
finally: _gh_override.reset(token)
|
|
109
|
+
return patch(splice_sig(_f, fn, 'self'))
|
|
110
|
+
|
|
88
111
|
# %% ../nbs/00_core.ipynb #83e8a9ce
|
|
89
112
|
class GhApi(OpenAPIClient):
|
|
90
113
|
"GitHub API client. Endpoint groups (`issues`, `pulls`, ...) are generated per-instance from GitHub's OpenAPI metadata, so the class shows only convenience methods -- inspect a live instance, e.g. `doc(GhApi())`, to see the full API."
|
|
@@ -102,7 +125,7 @@ class GhApi(OpenAPIClient):
|
|
|
102
125
|
self.token,self.gh_host = token,gh_host or GH_HOST
|
|
103
126
|
tcls,fcls = (GhSyncTransport,SyncOpFunc) if sync else (GhTransport,OpFunc)
|
|
104
127
|
self.transport = tcls(debug=debug, limit_cb=limit_cb, timeout=timeout, base_headers=self.headers)
|
|
105
|
-
self.ops = [fcls(o, self.transport, self.gh_host, defaults=kwargs) for o in pspec.ops]
|
|
128
|
+
self.ops = [fcls(o, self.transport, self.gh_host, defaults=_LiveDefaults(kwargs)) for o in pspec.ops]
|
|
106
129
|
self.func_dict = {f'{o.path}:{o.verb.upper()}':o for o in self.ops}
|
|
107
130
|
self.groups = _build_groups(self.ops)
|
|
108
131
|
for k,v in self.groups.items(): setattr(self, k, v)
|
|
@@ -195,7 +218,7 @@ async def update_gist(self:GhApi, gist_id:str, content:str):
|
|
|
195
218
|
return (await self.gists.update(gist_id, files={fname: {'content': content}})).html_url
|
|
196
219
|
|
|
197
220
|
# %% ../nbs/00_core.ipynb #4b7a278c
|
|
198
|
-
@
|
|
221
|
+
@gh_patch
|
|
199
222
|
async def delete_release(self:GhApi, release):
|
|
200
223
|
"Delete a release and its associated tag"
|
|
201
224
|
await self.repos.delete_release(release.id)
|
|
@@ -211,7 +234,7 @@ async def upload_file(self:GhApi, rel, fn):
|
|
|
211
234
|
return await self(url, 'POST', headers={'Content-Type':mime}, query = {'name':fn.name}, data=fn.read_bytes())
|
|
212
235
|
|
|
213
236
|
# %% ../nbs/00_core.ipynb #3cad71a4
|
|
214
|
-
@
|
|
237
|
+
@gh_patch
|
|
215
238
|
async def create_release(self:GhApi, tag_name, branch='master', name=None, body='',
|
|
216
239
|
draft=False, prerelease=False, files=None, make_latest=UNSET):
|
|
217
240
|
"Wrapper for `GhApi.repos.create_release` which also uploads `files`"
|
|
@@ -222,13 +245,13 @@ async def create_release(self:GhApi, tag_name, branch='master', name=None, body=
|
|
|
222
245
|
return rel
|
|
223
246
|
|
|
224
247
|
# %% ../nbs/00_core.ipynb #2be73ae0
|
|
225
|
-
@
|
|
248
|
+
@gh_patch
|
|
226
249
|
async def list_tags(self:GhApi, prefix:str=''):
|
|
227
250
|
"List all tags, optionally filtered to those starting with `prefix`"
|
|
228
251
|
return await self.git.list_matching_refs(f'tags/{prefix}')
|
|
229
252
|
|
|
230
253
|
# %% ../nbs/00_core.ipynb #303eeec6
|
|
231
|
-
@
|
|
254
|
+
@gh_patch
|
|
232
255
|
async def list_branches(self:GhApi, prefix:str=''):
|
|
233
256
|
"List all branches, optionally filtered to those starting with `prefix`"
|
|
234
257
|
return await self.git.list_matching_refs(f'heads/{prefix}')
|
|
@@ -238,7 +261,7 @@ async def list_branches(self:GhApi, prefix:str=''):
|
|
|
238
261
|
EMPTY_TREE_SHA = '4b825dc642cb6eb9a060e54bf8d69288fbee4904'
|
|
239
262
|
|
|
240
263
|
# %% ../nbs/00_core.ipynb #ba6ab941
|
|
241
|
-
@
|
|
264
|
+
@gh_patch
|
|
242
265
|
async def create_branch_empty(self:GhApi, branch):
|
|
243
266
|
t = await self.git.create_tree(base_tree=EMPTY_TREE_SHA, tree = [dict(
|
|
244
267
|
path='.dummy', content='ignore me', mode='100644', type='blob')])
|
|
@@ -246,38 +269,56 @@ async def create_branch_empty(self:GhApi, branch):
|
|
|
246
269
|
return await self.git.create_ref(f'refs/heads/{branch}', c.sha)
|
|
247
270
|
|
|
248
271
|
# %% ../nbs/00_core.ipynb #68b150fc
|
|
249
|
-
@
|
|
272
|
+
@gh_patch
|
|
250
273
|
async def delete_tag(self:GhApi, tag:str):
|
|
251
274
|
"Delete a tag"
|
|
252
275
|
return await self.git.delete_ref(f'tags/{tag}')
|
|
253
276
|
|
|
254
277
|
# %% ../nbs/00_core.ipynb #75c168a1
|
|
255
|
-
@
|
|
278
|
+
@gh_patch
|
|
256
279
|
async def delete_branch(self:GhApi, branch:str):
|
|
257
280
|
"Delete a branch"
|
|
258
281
|
return await self.git.delete_ref(f'heads/{branch}')
|
|
259
282
|
|
|
260
283
|
# %% ../nbs/00_core.ipynb #96053795
|
|
261
|
-
@
|
|
284
|
+
@gh_patch
|
|
262
285
|
async def get_branch(self:GhApi, branch=None):
|
|
263
286
|
branch = branch or (await self.repos.get()).default_branch
|
|
264
287
|
return (await self.list_branches(branch))[0]
|
|
265
288
|
|
|
289
|
+
# %% ../nbs/00_core.ipynb #9f70ac44
|
|
290
|
+
@gh_patch
|
|
291
|
+
async def commit_tree(self:GhApi, branch, message, tree, base=None):
|
|
292
|
+
"Commit `tree` entries to `branch`, creating it from `base` when needed"
|
|
293
|
+
try:
|
|
294
|
+
ref = await self.git.get_ref(f'heads/{branch}')
|
|
295
|
+
exists = True
|
|
296
|
+
except APIError as e:
|
|
297
|
+
if e.status_code != 404: raise
|
|
298
|
+
base = base or (await self.repos.get()).default_branch
|
|
299
|
+
ref,exists = await self.git.get_ref(f'heads/{base}'),False
|
|
300
|
+
parent = await self.git.get_commit(ref.object.sha)
|
|
301
|
+
new_tree = await self.git.create_tree(tree, parent.tree.sha)
|
|
302
|
+
commit = await self.git.create_commit(message, new_tree.sha, [parent.sha])
|
|
303
|
+
if exists: await self.git.update_ref(f'heads/{branch}', commit.sha)
|
|
304
|
+
else: await self.git.create_ref(f'refs/heads/{branch}', commit.sha)
|
|
305
|
+
return commit
|
|
306
|
+
|
|
266
307
|
# %% ../nbs/00_core.ipynb #93b24881
|
|
267
|
-
@
|
|
308
|
+
@gh_patch
|
|
268
309
|
async def list_files(self:GhApi, branch=None):
|
|
269
310
|
ref = await self.get_branch(branch)
|
|
270
311
|
res = (await self.git.get_tree(ref.object.sha)).tree
|
|
271
312
|
return {o.path:o for o in res}
|
|
272
313
|
|
|
273
314
|
# %% ../nbs/00_core.ipynb #ffc347b2
|
|
274
|
-
@
|
|
315
|
+
@gh_patch
|
|
275
316
|
async def get_content(self:GhApi, path):
|
|
276
317
|
res = await self.repos.get_content(path)
|
|
277
318
|
return base64.b64decode(res.content)
|
|
278
319
|
|
|
279
320
|
# %% ../nbs/00_core.ipynb #9582ee1f
|
|
280
|
-
@
|
|
321
|
+
@gh_patch
|
|
281
322
|
async def create_or_update_file(self:GhApi, path, message, committer, author, content=None, sha=None, branch=''):
|
|
282
323
|
if not branch: branch = (await self.repos.get())['default_branch']
|
|
283
324
|
if not isinstance(content,bytes): content = content.encode()
|
|
@@ -287,20 +328,20 @@ async def create_or_update_file(self:GhApi, path, message, committer, author, co
|
|
|
287
328
|
branch=branch, committer=committer or {}, author=author or {}, **kwargs)
|
|
288
329
|
|
|
289
330
|
# %% ../nbs/00_core.ipynb #5044445d
|
|
290
|
-
@
|
|
331
|
+
@gh_patch
|
|
291
332
|
async def create_file(self:GhApi, path, message, committer, author, content=None, branch=None):
|
|
292
333
|
if not branch: branch = (await self.repos.get())['default_branch']
|
|
293
334
|
return await self.create_or_update_file(path, message, branch=branch, committer=committer, content=content, author=author)
|
|
294
335
|
|
|
295
336
|
# %% ../nbs/00_core.ipynb #4e74c337
|
|
296
|
-
@
|
|
337
|
+
@gh_patch
|
|
297
338
|
async def delete_file(self:GhApi, path, message, committer, author, sha=None, branch=None):
|
|
298
339
|
if not branch: branch = (await self.repos.get())['default_branch']
|
|
299
340
|
if sha is None: sha = (await self.list_files())[path].sha
|
|
300
341
|
return await self.repos.delete_file(path, message=message, sha=sha, branch=branch, committer=committer, author=author)
|
|
301
342
|
|
|
302
343
|
# %% ../nbs/00_core.ipynb #93f6b559
|
|
303
|
-
@
|
|
344
|
+
@gh_patch
|
|
304
345
|
async def update_contents(self:GhApi, path, message, committer, author, content, sha=None, branch=None):
|
|
305
346
|
if not branch: branch = (await self.repos.get())['default_branch']
|
|
306
347
|
if sha is None: sha = (await self.list_files())[path].sha
|
|
@@ -348,7 +389,7 @@ async def get_repo_contents(self:GhApi, owner, repo, branch='main',
|
|
|
348
389
|
return L(await parallel_async(self.get_file_content, paths, owner=owner, repo=repo, branch=branch, n_workers=n_workers))
|
|
349
390
|
|
|
350
391
|
# %% ../nbs/00_core.ipynb #ac4ab4e0
|
|
351
|
-
@
|
|
392
|
+
@gh_patch
|
|
352
393
|
async def enable_pages(self:GhApi, branch=None, path="/"):
|
|
353
394
|
"Enable or update pages for a repo to point to a `branch` and `path`."
|
|
354
395
|
if path not in ('/docs','/'): raise Exception("path not in ('/docs','/')")
|
|
@@ -370,7 +411,7 @@ class _IssueInfo(AttrDict):
|
|
|
370
411
|
return '\n\n'.join(res)
|
|
371
412
|
__repr__ = _repr_markdown_
|
|
372
413
|
|
|
373
|
-
@
|
|
414
|
+
@gh_patch
|
|
374
415
|
async def read_issue(self:GhApi, issue_number:int):
|
|
375
416
|
"Fetch an issue or PR: title, body, comments, and (for PRs) diff, review comments, and reviews"
|
|
376
417
|
try: pr = await self.pulls.get(issue_number)
|
|
@@ -463,7 +504,7 @@ def _parse_tmpl(name, txt):
|
|
|
463
504
|
for b in d.get('body', []) if b['type']!='markdown']
|
|
464
505
|
return dict2obj(dict(name=name, title=d.get('name'), description=d.get('description'), sections=secs))
|
|
465
506
|
|
|
466
|
-
@
|
|
507
|
+
@gh_patch
|
|
467
508
|
async def issue_template(self:GhApi):
|
|
468
509
|
"Parsed issue templates for the repo (or the owner's `.github` repo as fallback); pass one to `issue_body`"
|
|
469
510
|
for repo,path in ((None,'.github/ISSUE_TEMPLATE'), ('.github','ISSUE_TEMPLATE'), ('.github','.github/ISSUE_TEMPLATE')):
|
|
@@ -501,14 +542,14 @@ class _CheckStatus(AttrDict):
|
|
|
501
542
|
return '\n'.join([f'**{verdict}**', ''] + [f"- {r.name}: {r.conclusion or r.status}" for r in runs])
|
|
502
543
|
__repr__ = _repr_markdown_
|
|
503
544
|
|
|
504
|
-
@
|
|
545
|
+
@gh_patch
|
|
505
546
|
async def check_status(self:GhApi, ref:str):
|
|
506
547
|
"Combined commit status and check-run results for `ref` (a SHA, branch, or tag)"
|
|
507
548
|
combined = await self.repos.get_combined_status_for_ref(ref)
|
|
508
549
|
checks = await self.checks.list_for_ref(ref)
|
|
509
550
|
return _CheckStatus(dict2obj(dict(state=combined.state, statuses=combined.statuses, check_runs=checks.check_runs)))
|
|
510
551
|
|
|
511
|
-
@
|
|
552
|
+
@gh_patch
|
|
512
553
|
async def pr_status(self:GhApi, pull_number:int):
|
|
513
554
|
"Combined status and check-run results for a PR's head commit"
|
|
514
555
|
return await self.check_status((await self.pulls.get(pull_number)).head.sha)
|
|
@@ -9,9 +9,17 @@
|
|
|
9
9
|
api = GhApi(owner='fastai', repo='ghapi')
|
|
10
10
|
await api.issues.list_for_repo(state='open') # owner/repo already bound
|
|
11
11
|
|
|
12
|
+
These are only defaults: any endpoint or convenience method accepts `owner=`/`repo=` to override per call (e.g. `await api.check_status('v1.0', repo='other')`), so one client can serve a whole org; convenience methods propagate the override to everything they call internally.
|
|
13
|
+
|
|
12
14
|
# Discovering the API
|
|
13
15
|
|
|
14
|
-
`api
|
|
16
|
+
Displaying `api` lists every available group with a docs link. Endpoint names follow `<verb>_<noun>`, e.g. `issues.list_for_repo`, `issues.create`, `pulls.merge`. Groups can contain hundreds of endpoints, so search their names with `pyskills.xdir()` instead of rendering the whole group, then inspect the matching endpoint:
|
|
17
|
+
|
|
18
|
+
import pyskills
|
|
19
|
+
pyskills.xdir(api.actions, 'log')
|
|
20
|
+
doc(api.actions.download_job_logs_for_workflow_run)
|
|
21
|
+
|
|
22
|
+
The optional `pyskills.xdir` query is a case-insensitive regex. `doc()` works at every level of a live instance, but `doc(api.actions)` renders that group's complete endpoint list; use it only when the group is small enough to read. The instance must be live because the API is generated at construction time: inspecting the `GhApi` *class* shows only convenience methods, not generated groups. `api['/path/{owner}/{repo}', 'GET']` looks up an endpoint directly by path and verb.
|
|
15
23
|
|
|
16
24
|
# Reading an issue or PR (including comments and reviews)
|
|
17
25
|
|
|
@@ -60,6 +68,16 @@ For anything that's about the local repo/working tree rather than GitHub itself
|
|
|
60
68
|
|
|
61
69
|
`Git.__call__` prints (not raises) on a `CalledProcessError` unless you pass `mute_errors=True` -- check the return value, don't assume no exception means success.
|
|
62
70
|
|
|
71
|
+
# External PRs
|
|
72
|
+
|
|
73
|
+
For a PR to another owner's repo, use normal local Git through the commit, ensure your fork exists, then use one client scoped to the fork. `commit_tree` uploads GitHub tree entries as one commit without Git transport authentication; override `owner` only when creating the upstream PR:
|
|
74
|
+
|
|
75
|
+
api = GhApi('me', repo)
|
|
76
|
+
await api.commit_tree(branch, message, entries)
|
|
77
|
+
await api.pulls.create(owner='upstream', head=f'me:{branch}', base='main', title=title, body=body)
|
|
78
|
+
|
|
79
|
+
Once the PR exists, the fork branch is the source of truth, and the local clone is residue: reset it (`git restore .` and switch back to the main branch) rather than leaving the edits behind, where a later session will find an unexplained diff. In particular, never build a second PR's edits on a tree still dirty from the first: each PR gets its own branch from main with a clean tree, since layered uncommitted edits ship a merged diff to `commit_tree` and block `git switch` once shared files diverge.
|
|
80
|
+
|
|
63
81
|
# Gotchas
|
|
64
82
|
|
|
65
83
|
- A PR *is* an issue for general comments (`issues.list_comments`, not `pulls.*`) -- inline code-review comments are the separate `pulls.list_review_comments`.
|
|
@@ -85,8 +103,7 @@ class ReadOnlyGhPolicy(AllowPolicy):
|
|
|
85
103
|
allow({OpFunc: [('__call__', ReadOnlyGhPolicy())]})
|
|
86
104
|
class GitPolicy(AllowPolicy):
|
|
87
105
|
"Allow only safecmd-default git subcommands"
|
|
88
|
-
cmds = {
|
|
89
|
-
'blame','branch','cat-file','config','describe','diff','log','ls-files','ls-tree','merge-base',
|
|
106
|
+
cmds = {'blame','branch','cat-file','config','describe','diff','log','ls-files','ls-tree','merge-base',
|
|
90
107
|
'remote','rev-parse','shortlog','show','stash','status','tag','fetch','add','commit','switch','checkout'}
|
|
91
108
|
|
|
92
109
|
def __call__(self, obj, args, kwargs, data):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ghapi
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.4
|
|
4
4
|
Summary: A python client for the GitHub API
|
|
5
5
|
Author-email: Jeremy Howard <info@fast.ai>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -16,7 +16,7 @@ Requires-Python: >=3.10
|
|
|
16
16
|
Description-Content-Type: text/markdown
|
|
17
17
|
License-File: LICENSE
|
|
18
18
|
Requires-Dist: fastcore>=1.14.5
|
|
19
|
-
Requires-Dist: fastspec>=0.1.
|
|
19
|
+
Requires-Dist: fastspec>=0.1.2
|
|
20
20
|
Provides-Extra: dev
|
|
21
21
|
Requires-Dist: fastgit; extra == "dev"
|
|
22
22
|
Requires-Dist: matplotlib; extra == "dev"
|
|
@@ -12,7 +12,7 @@ license = {text = "Apache-2.0"}
|
|
|
12
12
|
authors = [{name = "Jeremy Howard", email = "info@fast.ai"}]
|
|
13
13
|
keywords = ['github', 'api']
|
|
14
14
|
classifiers = ["Natural Language :: English", "Intended Audience :: Developers", "Development Status :: 5 - Production/Stable", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only"]
|
|
15
|
-
dependencies = ['fastcore>=1.14.5', 'fastspec>=0.1.
|
|
15
|
+
dependencies = ['fastcore>=1.14.5', 'fastspec>=0.1.2']
|
|
16
16
|
|
|
17
17
|
[project.urls]
|
|
18
18
|
Repository = "https://github.com/fastai/ghapi"
|
ghapi-2.0.2/ghapi/__init__.py
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "2.0.2"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|