ghapi 2.0.3__tar.gz → 2.0.5__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 (29) hide show
  1. {ghapi-2.0.3/ghapi.egg-info → ghapi-2.0.5}/PKG-INFO +3 -3
  2. ghapi-2.0.5/ghapi/__init__.py +7 -0
  3. {ghapi-2.0.3 → ghapi-2.0.5}/ghapi/_modidx.py +24 -21
  4. {ghapi-2.0.3 → ghapi-2.0.5}/ghapi/core.py +48 -25
  5. {ghapi-2.0.3 → ghapi-2.0.5}/ghapi/skill.py +12 -3
  6. {ghapi-2.0.3 → ghapi-2.0.5/ghapi.egg-info}/PKG-INFO +3 -3
  7. {ghapi-2.0.3 → ghapi-2.0.5}/ghapi.egg-info/requires.txt +2 -2
  8. {ghapi-2.0.3 → ghapi-2.0.5}/pyproject.toml +1 -1
  9. ghapi-2.0.3/ghapi/__init__.py +0 -1
  10. {ghapi-2.0.3 → ghapi-2.0.5}/CONTRIBUTING.md +0 -0
  11. {ghapi-2.0.3 → ghapi-2.0.5}/LICENSE +0 -0
  12. {ghapi-2.0.3 → ghapi-2.0.5}/MANIFEST.in +0 -0
  13. {ghapi-2.0.3 → ghapi-2.0.5}/README.md +0 -0
  14. {ghapi-2.0.3 → ghapi-2.0.5}/ghapi/actions.py +0 -0
  15. {ghapi-2.0.3 → ghapi-2.0.5}/ghapi/all.py +0 -0
  16. {ghapi-2.0.3 → ghapi-2.0.5}/ghapi/auth.py +0 -0
  17. {ghapi-2.0.3 → ghapi-2.0.5}/ghapi/build_lib.py +0 -0
  18. {ghapi-2.0.3 → ghapi-2.0.5}/ghapi/cli.py +0 -0
  19. {ghapi-2.0.3 → ghapi-2.0.5}/ghapi/event.py +0 -0
  20. {ghapi-2.0.3 → ghapi-2.0.5}/ghapi/gh_spec.py +0 -0
  21. {ghapi-2.0.3 → ghapi-2.0.5}/ghapi/graphql.py +0 -0
  22. {ghapi-2.0.3 → ghapi-2.0.5}/ghapi/page.py +0 -0
  23. {ghapi-2.0.3 → ghapi-2.0.5}/ghapi/py.typed +0 -0
  24. {ghapi-2.0.3 → ghapi-2.0.5}/ghapi/templates.py +0 -0
  25. {ghapi-2.0.3 → ghapi-2.0.5}/ghapi.egg-info/SOURCES.txt +0 -0
  26. {ghapi-2.0.3 → ghapi-2.0.5}/ghapi.egg-info/dependency_links.txt +0 -0
  27. {ghapi-2.0.3 → ghapi-2.0.5}/ghapi.egg-info/entry_points.txt +0 -0
  28. {ghapi-2.0.3 → ghapi-2.0.5}/ghapi.egg-info/top_level.txt +0 -0
  29. {ghapi-2.0.3 → ghapi-2.0.5}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ghapi
3
- Version: 2.0.3
3
+ Version: 2.0.5
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
@@ -15,8 +15,8 @@ Classifier: Programming Language :: Python :: 3 :: Only
15
15
  Requires-Python: >=3.10
16
16
  Description-Content-Type: text/markdown
17
17
  License-File: LICENSE
18
- Requires-Dist: fastcore>=1.14.5
19
- Requires-Dist: fastspec>=0.1.0
18
+ Requires-Dist: fastcore>=2.1.16
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,7 @@
1
+ """A python client for the GitHub API
2
+
3
+ Modules:
4
+
5
+ - `ghapi.skill`: GitHub REST API access via `GhApi`, plus local git operations via `fastgit.Git`. Use this for day-to-day GitHub work: reading/creating issues and PRs, checking CI status, managing releases/branches/gists, and repo-local git operations -- all from Python, no shelling out to `gh`/`git` needed."""
6
+
7
+ __version__ = "2.0.5"
@@ -48,35 +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.commit_tree': ('core.html#ghapi.commit_tree', 'ghapi/core.py'),
53
- 'ghapi.core.GhApi.create_branch_empty': ('core.html#ghapi.create_branch_empty', 'ghapi/core.py'),
54
- 'ghapi.core.GhApi.create_file': ('core.html#ghapi.create_file', 'ghapi/core.py'),
55
51
  'ghapi.core.GhApi.create_gist': ('core.html#ghapi.create_gist', 'ghapi/core.py'),
56
- 'ghapi.core.GhApi.create_or_update_file': ('core.html#ghapi.create_or_update_file', 'ghapi/core.py'),
57
- 'ghapi.core.GhApi.create_release': ('core.html#ghapi.create_release', 'ghapi/core.py'),
58
52
  'ghapi.core.GhApi.debug': ('core.html#ghapi.debug', 'ghapi/core.py'),
59
- 'ghapi.core.GhApi.delete_branch': ('core.html#ghapi.delete_branch', 'ghapi/core.py'),
60
- 'ghapi.core.GhApi.delete_file': ('core.html#ghapi.delete_file', 'ghapi/core.py'),
61
- 'ghapi.core.GhApi.delete_release': ('core.html#ghapi.delete_release', 'ghapi/core.py'),
62
- 'ghapi.core.GhApi.delete_tag': ('core.html#ghapi.delete_tag', 'ghapi/core.py'),
63
- 'ghapi.core.GhApi.enable_pages': ('core.html#ghapi.enable_pages', 'ghapi/core.py'),
64
- 'ghapi.core.GhApi.get_branch': ('core.html#ghapi.get_branch', 'ghapi/core.py'),
65
- 'ghapi.core.GhApi.get_content': ('core.html#ghapi.get_content', 'ghapi/core.py'),
66
53
  'ghapi.core.GhApi.get_file_content': ('core.html#ghapi.get_file_content', 'ghapi/core.py'),
67
54
  'ghapi.core.GhApi.get_repo_contents': ('core.html#ghapi.get_repo_contents', 'ghapi/core.py'),
68
55
  'ghapi.core.GhApi.get_repo_files': ('core.html#ghapi.get_repo_files', 'ghapi/core.py'),
69
56
  'ghapi.core.GhApi.gist_file': ('core.html#ghapi.gist_file', 'ghapi/core.py'),
70
- 'ghapi.core.GhApi.issue_template': ('core.html#ghapi.issue_template', 'ghapi/core.py'),
71
57
  'ghapi.core.GhApi.limit_rem': ('core.html#ghapi.limit_rem', 'ghapi/core.py'),
72
- 'ghapi.core.GhApi.list_branches': ('core.html#ghapi.list_branches', 'ghapi/core.py'),
73
- 'ghapi.core.GhApi.list_files': ('core.html#ghapi.list_files', 'ghapi/core.py'),
74
- 'ghapi.core.GhApi.list_tags': ('core.html#ghapi.list_tags', 'ghapi/core.py'),
75
58
  'ghapi.core.GhApi.load_gist': ('core.html#ghapi.load_gist', 'ghapi/core.py'),
76
- 'ghapi.core.GhApi.pr_status': ('core.html#ghapi.pr_status', 'ghapi/core.py'),
77
- 'ghapi.core.GhApi.read_issue': ('core.html#ghapi.read_issue', 'ghapi/core.py'),
78
59
  'ghapi.core.GhApi.recv_hdrs': ('core.html#ghapi.recv_hdrs', 'ghapi/core.py'),
79
- 'ghapi.core.GhApi.update_contents': ('core.html#ghapi.update_contents', 'ghapi/core.py'),
80
60
  'ghapi.core.GhApi.update_gist': ('core.html#ghapi.update_gist', 'ghapi/core.py'),
81
61
  'ghapi.core.GhApi.upload_file': ('core.html#ghapi.upload_file', 'ghapi/core.py'),
82
62
  'ghapi.core.GhSyncTransport': ('core.html#ghsynctransport', 'ghapi/core.py'),
@@ -91,6 +71,8 @@ d = { 'settings': { 'branch': 'main',
91
71
  'ghapi.core._CheckStatus._repr_markdown_': ('core.html#_checkstatus._repr_markdown_', 'ghapi/core.py'),
92
72
  'ghapi.core._IssueInfo': ('core.html#_issueinfo', 'ghapi/core.py'),
93
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'),
94
76
  'ghapi.core._filter_diff': ('core.html#_filter_diff', 'ghapi/core.py'),
95
77
  'ghapi.core._find_matches': ('core.html#_find_matches', 'ghapi/core.py'),
96
78
  'ghapi.core._fmt_replies': ('core.html#_fmt_replies', 'ghapi/core.py'),
@@ -99,12 +81,33 @@ d = { 'settings': { 'branch': 'main',
99
81
  'ghapi.core._reduce_ctx': ('core.html#_reduce_ctx', 'ghapi/core.py'),
100
82
  'ghapi.core._run_subp': ('core.html#_run_subp', 'ghapi/core.py'),
101
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'),
102
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'),
103
98
  'ghapi.core.gh2date': ('core.html#gh2date', 'ghapi/core.py'),
99
+ 'ghapi.core.gh_patch': ('core.html#gh_patch', 'ghapi/core.py'),
104
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'),
105
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'),
106
107
  'ghapi.core.print_summary': ('core.html#print_summary', 'ghapi/core.py'),
107
- 'ghapi.core.read_pr': ('core.html#read_pr', 'ghapi/core.py')},
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')},
108
111
  'ghapi.event': { 'ghapi.event.GhApi.fetch_events': ('event.html#ghapi.fetch_events', 'ghapi/event.py'),
109
112
  'ghapi.event.GhApi.list_events': ('event.html#ghapi.list_events', 'ghapi/event.py'),
110
113
  'ghapi.event.GhApi.list_events_parallel': ('event.html#ghapi.list_events_parallel', 'ghapi/event.py'),
@@ -5,13 +5,19 @@ 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', 'GhApi',
9
- 'call_gh', 'date2gh', 'gh2date', 'read_pr', 'pr_file_diff', 'issue_body', 'APIError']
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
- from fastspec.oapi import OpenAPIClient, OpFunc, SyncOpFunc, _build_groups, UNSET
20
+ from fastspec.oapi import OpenAPIClient, OpFunc, SyncOpFunc
15
21
  from fastspec.transport import AsyncTransport, SyncTransport
16
22
  from .gh_spec import spec
17
23
  from fastspec.errors import APIError
@@ -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,9 +125,9 @@ 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
- self.groups = _build_groups(self.ops)
130
+ self.groups = mk_groups(self.ops)
108
131
  for k,v in self.groups.items(): setattr(self, k, v)
109
132
 
110
133
  def __call__(self, path:str, verb:str=None, headers:dict=None, route:dict=None, query:dict=None, data=None):
@@ -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
- @patch
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
- @patch
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
- @patch
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
- @patch
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
- @patch
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,25 +269,25 @@ 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
- @patch
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
- @patch
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
- @patch
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
 
266
289
  # %% ../nbs/00_core.ipynb #9f70ac44
267
- @patch
290
+ @gh_patch
268
291
  async def commit_tree(self:GhApi, branch, message, tree, base=None):
269
292
  "Commit `tree` entries to `branch`, creating it from `base` when needed"
270
293
  try:
@@ -282,20 +305,20 @@ async def commit_tree(self:GhApi, branch, message, tree, base=None):
282
305
  return commit
283
306
 
284
307
  # %% ../nbs/00_core.ipynb #93b24881
285
- @patch
308
+ @gh_patch
286
309
  async def list_files(self:GhApi, branch=None):
287
310
  ref = await self.get_branch(branch)
288
311
  res = (await self.git.get_tree(ref.object.sha)).tree
289
312
  return {o.path:o for o in res}
290
313
 
291
314
  # %% ../nbs/00_core.ipynb #ffc347b2
292
- @patch
315
+ @gh_patch
293
316
  async def get_content(self:GhApi, path):
294
317
  res = await self.repos.get_content(path)
295
318
  return base64.b64decode(res.content)
296
319
 
297
320
  # %% ../nbs/00_core.ipynb #9582ee1f
298
- @patch
321
+ @gh_patch
299
322
  async def create_or_update_file(self:GhApi, path, message, committer, author, content=None, sha=None, branch=''):
300
323
  if not branch: branch = (await self.repos.get())['default_branch']
301
324
  if not isinstance(content,bytes): content = content.encode()
@@ -305,20 +328,20 @@ async def create_or_update_file(self:GhApi, path, message, committer, author, co
305
328
  branch=branch, committer=committer or {}, author=author or {}, **kwargs)
306
329
 
307
330
  # %% ../nbs/00_core.ipynb #5044445d
308
- @patch
331
+ @gh_patch
309
332
  async def create_file(self:GhApi, path, message, committer, author, content=None, branch=None):
310
333
  if not branch: branch = (await self.repos.get())['default_branch']
311
334
  return await self.create_or_update_file(path, message, branch=branch, committer=committer, content=content, author=author)
312
335
 
313
336
  # %% ../nbs/00_core.ipynb #4e74c337
314
- @patch
337
+ @gh_patch
315
338
  async def delete_file(self:GhApi, path, message, committer, author, sha=None, branch=None):
316
339
  if not branch: branch = (await self.repos.get())['default_branch']
317
340
  if sha is None: sha = (await self.list_files())[path].sha
318
341
  return await self.repos.delete_file(path, message=message, sha=sha, branch=branch, committer=committer, author=author)
319
342
 
320
343
  # %% ../nbs/00_core.ipynb #93f6b559
321
- @patch
344
+ @gh_patch
322
345
  async def update_contents(self:GhApi, path, message, committer, author, content, sha=None, branch=None):
323
346
  if not branch: branch = (await self.repos.get())['default_branch']
324
347
  if sha is None: sha = (await self.list_files())[path].sha
@@ -366,7 +389,7 @@ async def get_repo_contents(self:GhApi, owner, repo, branch='main',
366
389
  return L(await parallel_async(self.get_file_content, paths, owner=owner, repo=repo, branch=branch, n_workers=n_workers))
367
390
 
368
391
  # %% ../nbs/00_core.ipynb #ac4ab4e0
369
- @patch
392
+ @gh_patch
370
393
  async def enable_pages(self:GhApi, branch=None, path="/"):
371
394
  "Enable or update pages for a repo to point to a `branch` and `path`."
372
395
  if path not in ('/docs','/'): raise Exception("path not in ('/docs','/')")
@@ -388,7 +411,7 @@ class _IssueInfo(AttrDict):
388
411
  return '\n\n'.join(res)
389
412
  __repr__ = _repr_markdown_
390
413
 
391
- @patch
414
+ @gh_patch
392
415
  async def read_issue(self:GhApi, issue_number:int):
393
416
  "Fetch an issue or PR: title, body, comments, and (for PRs) diff, review comments, and reviews"
394
417
  try: pr = await self.pulls.get(issue_number)
@@ -481,7 +504,7 @@ def _parse_tmpl(name, txt):
481
504
  for b in d.get('body', []) if b['type']!='markdown']
482
505
  return dict2obj(dict(name=name, title=d.get('name'), description=d.get('description'), sections=secs))
483
506
 
484
- @patch
507
+ @gh_patch
485
508
  async def issue_template(self:GhApi):
486
509
  "Parsed issue templates for the repo (or the owner's `.github` repo as fallback); pass one to `issue_body`"
487
510
  for repo,path in ((None,'.github/ISSUE_TEMPLATE'), ('.github','ISSUE_TEMPLATE'), ('.github','.github/ISSUE_TEMPLATE')):
@@ -519,14 +542,14 @@ class _CheckStatus(AttrDict):
519
542
  return '\n'.join([f'**{verdict}**', ''] + [f"- {r.name}: {r.conclusion or r.status}" for r in runs])
520
543
  __repr__ = _repr_markdown_
521
544
 
522
- @patch
545
+ @gh_patch
523
546
  async def check_status(self:GhApi, ref:str):
524
547
  "Combined commit status and check-run results for `ref` (a SHA, branch, or tag)"
525
548
  combined = await self.repos.get_combined_status_for_ref(ref)
526
549
  checks = await self.checks.list_for_ref(ref)
527
550
  return _CheckStatus(dict2obj(dict(state=combined.state, statuses=combined.statuses, check_runs=checks.check_runs)))
528
551
 
529
- @patch
552
+ @gh_patch
530
553
  async def pr_status(self:GhApi, pull_number:int):
531
554
  "Combined status and check-run results for a PR's head commit"
532
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.<group>` (displayed bare, or via `doc()`) lists every endpoint in a group (issues, pulls, repos, checks, actions, gists, git, search, ...) with its signature and a link to GitHub's docs. `api['/path/{owner}/{repo}', 'GET']` looks up an endpoint by path+verb if you'd rather not use the grouped names. Endpoint names follow `<verb>_<noun>`, e.g. `issues.list_for_repo`, `issues.create`, `pulls.merge`. Displaying `api` itself lists every available group with a docs link. `doc()` works at every level of a live instance: `doc(api)`, `doc(api.issues)`, `doc(api.issues.create)`. It must be a live instance, since the API is generated at construction time: inspecting the `GhApi` *class* shows only the convenience methods and none of the endpoint groups.
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
 
@@ -68,6 +76,8 @@ For a PR to another owner's repo, use normal local Git through the commit, ensur
68
76
  await api.commit_tree(branch, message, entries)
69
77
  await api.pulls.create(owner='upstream', head=f'me:{branch}', base='main', title=title, body=body)
70
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
+
71
81
  # Gotchas
72
82
 
73
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`.
@@ -93,8 +103,7 @@ class ReadOnlyGhPolicy(AllowPolicy):
93
103
  allow({OpFunc: [('__call__', ReadOnlyGhPolicy())]})
94
104
  class GitPolicy(AllowPolicy):
95
105
  "Allow only safecmd-default git subcommands"
96
- cmds = {
97
- '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',
98
107
  'remote','rev-parse','shortlog','show','stash','status','tag','fetch','add','commit','switch','checkout'}
99
108
 
100
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
3
+ Version: 2.0.5
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
@@ -15,8 +15,8 @@ Classifier: Programming Language :: Python :: 3 :: Only
15
15
  Requires-Python: >=3.10
16
16
  Description-Content-Type: text/markdown
17
17
  License-File: LICENSE
18
- Requires-Dist: fastcore>=1.14.5
19
- Requires-Dist: fastspec>=0.1.0
18
+ Requires-Dist: fastcore>=2.1.16
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"
@@ -1,5 +1,5 @@
1
- fastcore>=1.14.5
2
- fastspec>=0.1.0
1
+ fastcore>=2.1.16
2
+ fastspec>=0.1.2
3
3
 
4
4
  [dev]
5
5
  fastgit
@@ -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.0']
15
+ dependencies = ['fastcore>=2.1.16', 'fastspec>=0.1.2']
16
16
 
17
17
  [project.urls]
18
18
  Repository = "https://github.com/fastai/ghapi"
@@ -1 +0,0 @@
1
- __version__ = "2.0.3"
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