ghapi 2.0.1__tar.gz → 2.0.3__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.1/ghapi.egg-info → ghapi-2.0.3}/PKG-INFO +1 -1
- ghapi-2.0.3/ghapi/__init__.py +1 -0
- {ghapi-2.0.1 → ghapi-2.0.3}/ghapi/_modidx.py +1 -0
- {ghapi-2.0.1 → ghapi-2.0.3}/ghapi/actions.py +7 -10
- {ghapi-2.0.1 → ghapi-2.0.3}/ghapi/auth.py +4 -6
- {ghapi-2.0.1 → ghapi-2.0.3}/ghapi/core.py +30 -15
- {ghapi-2.0.1 → ghapi-2.0.3}/ghapi/event.py +5 -6
- {ghapi-2.0.1 → ghapi-2.0.3}/ghapi/gh_spec.py +43 -6
- {ghapi-2.0.1 → ghapi-2.0.3}/ghapi/graphql.py +1 -4
- {ghapi-2.0.1 → ghapi-2.0.3}/ghapi/skill.py +8 -0
- {ghapi-2.0.1 → ghapi-2.0.3/ghapi.egg-info}/PKG-INFO +1 -1
- ghapi-2.0.1/ghapi/__init__.py +0 -1
- {ghapi-2.0.1 → ghapi-2.0.3}/CONTRIBUTING.md +0 -0
- {ghapi-2.0.1 → ghapi-2.0.3}/LICENSE +0 -0
- {ghapi-2.0.1 → ghapi-2.0.3}/MANIFEST.in +0 -0
- {ghapi-2.0.1 → ghapi-2.0.3}/README.md +0 -0
- {ghapi-2.0.1 → ghapi-2.0.3}/ghapi/all.py +0 -0
- {ghapi-2.0.1 → ghapi-2.0.3}/ghapi/build_lib.py +0 -0
- {ghapi-2.0.1 → ghapi-2.0.3}/ghapi/cli.py +0 -0
- {ghapi-2.0.1 → ghapi-2.0.3}/ghapi/page.py +0 -0
- {ghapi-2.0.1 → ghapi-2.0.3}/ghapi/py.typed +0 -0
- {ghapi-2.0.1 → ghapi-2.0.3}/ghapi/templates.py +0 -0
- {ghapi-2.0.1 → ghapi-2.0.3}/ghapi.egg-info/SOURCES.txt +0 -0
- {ghapi-2.0.1 → ghapi-2.0.3}/ghapi.egg-info/dependency_links.txt +0 -0
- {ghapi-2.0.1 → ghapi-2.0.3}/ghapi.egg-info/entry_points.txt +0 -0
- {ghapi-2.0.1 → ghapi-2.0.3}/ghapi.egg-info/requires.txt +0 -0
- {ghapi-2.0.1 → ghapi-2.0.3}/ghapi.egg-info/top_level.txt +0 -0
- {ghapi-2.0.1 → ghapi-2.0.3}/pyproject.toml +0 -0
- {ghapi-2.0.1 → ghapi-2.0.3}/setup.cfg +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "2.0.3"
|
|
@@ -49,6 +49,7 @@ d = { 'settings': { 'branch': 'main',
|
|
|
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
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'),
|
|
52
53
|
'ghapi.core.GhApi.create_branch_empty': ('core.html#ghapi.create_branch_empty', 'ghapi/core.py'),
|
|
53
54
|
'ghapi.core.GhApi.create_file': ('core.html#ghapi.create_file', 'ghapi/core.py'),
|
|
54
55
|
'ghapi.core.GhApi.create_gist': ('core.html#ghapi.create_gist', 'ghapi/core.py'),
|
|
@@ -18,7 +18,6 @@ from .templates import *
|
|
|
18
18
|
|
|
19
19
|
import textwrap
|
|
20
20
|
from contextlib import contextmanager
|
|
21
|
-
from enum import Enum
|
|
22
21
|
|
|
23
22
|
# %% ../nbs/01_actions.ipynb #fb69ba90
|
|
24
23
|
# So we can run this outside of GitHub actions too, read from file if needed
|
|
@@ -26,8 +25,7 @@ for a,b in (('CONTEXT_GITHUB',context_example), ('CONTEXT_NEEDS',needs_example),
|
|
|
26
25
|
if a not in os.environ: os.environ[a] = b
|
|
27
26
|
|
|
28
27
|
contexts = 'github', 'env', 'job', 'steps', 'runner', 'secrets', 'strategy', 'matrix', 'needs'
|
|
29
|
-
for context in contexts:
|
|
30
|
-
globals()[f'context_{context}'] = dict2obj(loads(os.getenv(f"CONTEXT_{context.upper()}", "{}")))
|
|
28
|
+
for context in contexts: globals()[f'context_{context}'] = dict2obj(loads(os.getenv(f"CONTEXT_{context.upper()}", "{}")))
|
|
31
29
|
|
|
32
30
|
# %% ../nbs/01_actions.ipynb #393990e1
|
|
33
31
|
_all_ = ['context_github', 'context_env', 'context_job', 'context_steps', 'context_runner', 'context_secrets', 'context_strategy', 'context_matrix', 'context_needs']
|
|
@@ -41,7 +39,7 @@ def user_repo():
|
|
|
41
39
|
return env_github.repository.split('/')
|
|
42
40
|
|
|
43
41
|
# %% ../nbs/01_actions.ipynb #deb389d7
|
|
44
|
-
Event = str_enum('Event',
|
|
42
|
+
Event = str_enum('Event', # chkstyle: ignore-node
|
|
45
43
|
'page_build','content_reference','repository_import','create','workflow_run','delete','organization','sponsorship',
|
|
46
44
|
'project_column','push','context','milestone','project_card','project','package','pull_request','repository_dispatch',
|
|
47
45
|
'team_add','workflow_dispatch','member','meta','code_scanning_alert','public','needs','check_run','security_advisory',
|
|
@@ -54,8 +52,7 @@ Event = str_enum('Event',
|
|
|
54
52
|
def _create_file(path:Path, fname:str, contents):
|
|
55
53
|
if contents and not (path/fname).exists(): (path/fname).write_text(contents)
|
|
56
54
|
|
|
57
|
-
def _replace(s:str, find, repl, i:int=0, suf:str=''):
|
|
58
|
-
return s.replace(find, textwrap.indent(repl, ' '*i)+suf)
|
|
55
|
+
def _replace(s:str, find, repl, i:int=0, suf:str=''): return s.replace(find, textwrap.indent(repl, ' '*i)+suf)
|
|
59
56
|
|
|
60
57
|
# %% ../nbs/01_actions.ipynb #26eb64e5
|
|
61
58
|
def create_workflow_files(fname:str, workflow:str, build_script:str, prebuild:bool=False):
|
|
@@ -75,9 +72,9 @@ def fill_workflow_templates(name:str, event, run, context, script, opersys='ubun
|
|
|
75
72
|
c = wf_tmpl
|
|
76
73
|
if event=='workflow_dispatch:': event=''
|
|
77
74
|
needs = ' needs: [prebuild]' if prebuild else ''
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
75
|
+
repls = (('NAME',name,0), ('EVENT',event,2), ('RUN',run,8), ('CONTEXTS',context,8),
|
|
76
|
+
('OPERSYS',f'[{opersys}]',0), ('NEEDS',needs,0), ('PREBUILD',pre_tmpl if prebuild else '',2))
|
|
77
|
+
for find,repl,i in repls: c = _replace(c, f'${find}', str(repl), i)
|
|
81
78
|
create_workflow_files(name, c, script, prebuild=prebuild)
|
|
82
79
|
|
|
83
80
|
# %% ../nbs/01_actions.ipynb #bc2f80a5
|
|
@@ -93,7 +90,7 @@ def_pipinst = 'pip install -Uq ghapi'
|
|
|
93
90
|
def create_workflow(name:str, event:Event, contexts:list=None, opersys='ubuntu', prebuild=False):
|
|
94
91
|
"Function to create a simple Ubuntu workflow that calls a Python `ghapi` script"
|
|
95
92
|
script = "from fastcore.all import *\nfrom ghapi import *"
|
|
96
|
-
fill_workflow_templates(name, f'{event}:', def_pipinst, env_contexts(contexts),
|
|
93
|
+
fill_workflow_templates(name, f'{event}:', def_pipinst, env_contexts(contexts), # chkstyle: ignore-node
|
|
97
94
|
script=script, opersys=opersys, prebuild=prebuild)
|
|
98
95
|
|
|
99
96
|
# %% ../nbs/01_actions.ipynb #e8482286
|
|
@@ -12,10 +12,10 @@ from fastcore.all import *
|
|
|
12
12
|
from .core import *
|
|
13
13
|
|
|
14
14
|
import webbrowser,time
|
|
15
|
-
from urllib.parse import parse_qs
|
|
15
|
+
from urllib.parse import parse_qs
|
|
16
16
|
|
|
17
17
|
# %% ../nbs/02_auth.ipynb #46620407
|
|
18
|
-
_scopes =(
|
|
18
|
+
_scopes =( # chkstyle: ignore-node
|
|
19
19
|
'repo','repo:status','repo_deployment','public_repo','repo:invite','security_events','admin:repo_hook','write:repo_hook',
|
|
20
20
|
'read:repo_hook','admin:org','write:org','read:org','admin:public_key','write:public_key','read:public_key','admin:org_hook',
|
|
21
21
|
'gist','notifications','user','read:user','user:email','user:follow','delete_repo','write:discussion','read:discussion',
|
|
@@ -61,10 +61,8 @@ def open_browser(self:GhDeviceAuth):
|
|
|
61
61
|
@patch
|
|
62
62
|
def auth(self:GhDeviceAuth)->str:
|
|
63
63
|
"Return token if authentication complete, or `None` otherwise"
|
|
64
|
-
resp = parse_qs(urlread(
|
|
65
|
-
'
|
|
66
|
-
client_id=self.client_id, device_code=self.device_code,
|
|
67
|
-
grant_type='urn:ietf:params:oauth:grant-type:device_code'))
|
|
64
|
+
resp = parse_qs(urlread('https://github.com/login/oauth/access_token',
|
|
65
|
+
client_id=self.client_id, device_code=self.device_code, grant_type='urn:ietf:params:oauth:grant-type:device_code'))
|
|
68
66
|
err = nested_idx(resp, 'error', 0)
|
|
69
67
|
if err == 'authorization_pending': return None
|
|
70
68
|
if err: raise Exception(resp['error_description'][0])
|
|
@@ -11,7 +11,7 @@ __all__ = ['GH_HOST', 'pspec', 'img_md_pat', 'EMPTY_TREE_SHA', 'GhTransport', 'G
|
|
|
11
11
|
# %% ../nbs/00_core.ipynb #5b5cba7b
|
|
12
12
|
from fastcore.all import *
|
|
13
13
|
from fastspec.spec import SpecParser
|
|
14
|
-
from fastspec.oapi import OpenAPIClient, OpFunc, SyncOpFunc, _build_groups
|
|
14
|
+
from fastspec.oapi import OpenAPIClient, OpFunc, SyncOpFunc, _build_groups, UNSET
|
|
15
15
|
from fastspec.transport import AsyncTransport, SyncTransport
|
|
16
16
|
from .gh_spec import spec
|
|
17
17
|
from fastspec.errors import APIError
|
|
@@ -19,8 +19,7 @@ from fastspec.errors import APIError
|
|
|
19
19
|
import mimetypes,base64
|
|
20
20
|
from collections import Counter
|
|
21
21
|
from urllib.parse import quote
|
|
22
|
-
from datetime import datetime
|
|
23
|
-
from time import sleep
|
|
22
|
+
from datetime import datetime
|
|
24
23
|
import os, shutil, tempfile, subprocess, fnmatch, html
|
|
25
24
|
|
|
26
25
|
# %% ../nbs/00_core.ipynb #ed04302a
|
|
@@ -60,8 +59,7 @@ class GhTransport(AsyncTransport):
|
|
|
60
59
|
self.recv_hdrs = resp.headers
|
|
61
60
|
if 'X-RateLimit-Remaining' in resp.headers:
|
|
62
61
|
newlim = resp.headers['X-RateLimit-Remaining']
|
|
63
|
-
if self.limit_cb is not None and newlim != self.limit_rem:
|
|
64
|
-
self.limit_cb(int(newlim), int(resp.headers['X-RateLimit-Limit']))
|
|
62
|
+
if self.limit_cb is not None and newlim != self.limit_rem: self.limit_cb(int(newlim), int(resp.headers['X-RateLimit-Limit']))
|
|
65
63
|
self.limit_rem = newlim
|
|
66
64
|
if raw: return resp
|
|
67
65
|
res = self._decode(resp)
|
|
@@ -90,7 +88,7 @@ def print_summary(method, url, kwargs):
|
|
|
90
88
|
# %% ../nbs/00_core.ipynb #83e8a9ce
|
|
91
89
|
class GhApi(OpenAPIClient):
|
|
92
90
|
"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."
|
|
93
|
-
def __init__(self, owner=None, repo=None, token=None, jwt_token=None, debug=None, limit_cb=None, gh_host=None,
|
|
91
|
+
def __init__(self, owner=None, repo=None, token=None, jwt_token=None, debug=None, limit_cb=None, gh_host=None, # chkstyle: ignore-node
|
|
94
92
|
authenticate=True, timeout=60.0, sync=False, **kwargs):
|
|
95
93
|
self.headers = { 'Accept': 'application/vnd.github.v3+json' }
|
|
96
94
|
if authenticate:
|
|
@@ -215,11 +213,11 @@ async def upload_file(self:GhApi, rel, fn):
|
|
|
215
213
|
# %% ../nbs/00_core.ipynb #3cad71a4
|
|
216
214
|
@patch
|
|
217
215
|
async def create_release(self:GhApi, tag_name, branch='master', name=None, body='',
|
|
218
|
-
|
|
216
|
+
draft=False, prerelease=False, files=None, make_latest=UNSET):
|
|
219
217
|
"Wrapper for `GhApi.repos.create_release` which also uploads `files`"
|
|
220
218
|
if name is None: name = 'v'+tag_name
|
|
221
219
|
rel = await self.repos.create_release(tag_name, target_commitish=branch, name=name, body=body,
|
|
222
|
-
|
|
220
|
+
draft=draft, prerelease=prerelease, make_latest=make_latest)
|
|
223
221
|
for file in listify(files): await self.upload_file(rel, file)
|
|
224
222
|
return rel
|
|
225
223
|
|
|
@@ -265,6 +263,24 @@ async def get_branch(self:GhApi, branch=None):
|
|
|
265
263
|
branch = branch or (await self.repos.get()).default_branch
|
|
266
264
|
return (await self.list_branches(branch))[0]
|
|
267
265
|
|
|
266
|
+
# %% ../nbs/00_core.ipynb #9f70ac44
|
|
267
|
+
@patch
|
|
268
|
+
async def commit_tree(self:GhApi, branch, message, tree, base=None):
|
|
269
|
+
"Commit `tree` entries to `branch`, creating it from `base` when needed"
|
|
270
|
+
try:
|
|
271
|
+
ref = await self.git.get_ref(f'heads/{branch}')
|
|
272
|
+
exists = True
|
|
273
|
+
except APIError as e:
|
|
274
|
+
if e.status_code != 404: raise
|
|
275
|
+
base = base or (await self.repos.get()).default_branch
|
|
276
|
+
ref,exists = await self.git.get_ref(f'heads/{base}'),False
|
|
277
|
+
parent = await self.git.get_commit(ref.object.sha)
|
|
278
|
+
new_tree = await self.git.create_tree(tree, parent.tree.sha)
|
|
279
|
+
commit = await self.git.create_commit(message, new_tree.sha, [parent.sha])
|
|
280
|
+
if exists: await self.git.update_ref(f'heads/{branch}', commit.sha)
|
|
281
|
+
else: await self.git.create_ref(f'refs/heads/{branch}', commit.sha)
|
|
282
|
+
return commit
|
|
283
|
+
|
|
268
284
|
# %% ../nbs/00_core.ipynb #93b24881
|
|
269
285
|
@patch
|
|
270
286
|
async def list_files(self:GhApi, branch=None):
|
|
@@ -299,8 +315,7 @@ async def create_file(self:GhApi, path, message, committer, author, content=None
|
|
|
299
315
|
async def delete_file(self:GhApi, path, message, committer, author, sha=None, branch=None):
|
|
300
316
|
if not branch: branch = (await self.repos.get())['default_branch']
|
|
301
317
|
if sha is None: sha = (await self.list_files())[path].sha
|
|
302
|
-
return await self.repos.delete_file(path, message=message, sha=sha,
|
|
303
|
-
branch=branch, committer=committer, author=author)
|
|
318
|
+
return await self.repos.delete_file(path, message=message, sha=sha, branch=branch, committer=committer, author=author)
|
|
304
319
|
|
|
305
320
|
# %% ../nbs/00_core.ipynb #93f6b559
|
|
306
321
|
@patch
|
|
@@ -346,7 +361,7 @@ async def get_repo_contents(self:GhApi, owner, repo, branch='main',
|
|
|
346
361
|
n_workers=16, # Max concurrent downloads
|
|
347
362
|
**kwargs
|
|
348
363
|
):
|
|
349
|
-
repo_files = await self.get_repo_files(owner, repo, **kwargs)
|
|
364
|
+
repo_files = await self.get_repo_files(owner, repo, branch, **kwargs)
|
|
350
365
|
paths = repo_files.attrgot("path")
|
|
351
366
|
return L(await parallel_async(self.get_file_content, paths, owner=owner, repo=repo, branch=branch, n_workers=n_workers))
|
|
352
367
|
|
|
@@ -379,7 +394,7 @@ async def read_issue(self:GhApi, issue_number:int):
|
|
|
379
394
|
try: pr = await self.pulls.get(issue_number)
|
|
380
395
|
except APIError: pr = None
|
|
381
396
|
iss = await self.issues.get(issue_number)
|
|
382
|
-
res = dict(title=iss.title, body=iss.body or '', is_pr=pr is not None,
|
|
397
|
+
res = dict(title=iss.title, body=iss.body or '', is_pr=pr is not None, # chkstyle: ignore-node
|
|
383
398
|
comments=await self.issues.list_comments(issue_number))
|
|
384
399
|
if pr is not None:
|
|
385
400
|
res['diff'] = await self.pulls.get(issue_number, _headers={'Accept': 'application/vnd.github.v3.diff'})
|
|
@@ -395,7 +410,7 @@ async def read_issue(self:GhApi, issue_number:int):
|
|
|
395
410
|
def _filter_diff(diff, folder='', skip_files=('_modidx.py',)):
|
|
396
411
|
"Filter unified diff to only include files under `folder`, skipping `skip_files`"
|
|
397
412
|
sections = re.split(r'(?=^diff --git )', diff, flags=re.MULTILINE)
|
|
398
|
-
return ''.join(s for s in sections
|
|
413
|
+
return ''.join(s for s in sections # chkstyle: ignore-node
|
|
399
414
|
if s.startswith(f'diff --git a/{folder}')
|
|
400
415
|
and not any(f'/{f} ' in s.split('\n')[0] for f in skip_files))
|
|
401
416
|
|
|
@@ -460,7 +475,7 @@ def _parse_tmpl(name, txt):
|
|
|
460
475
|
if not name.endswith(('.yml','.yaml')): return dict2obj(dict(name=name, raw=txt))
|
|
461
476
|
import yaml
|
|
462
477
|
d = yaml.safe_load(txt)
|
|
463
|
-
secs = [dict(label=b['attributes']['label'], type=b['type'],
|
|
478
|
+
secs = [dict(label=b['attributes']['label'], type=b['type'], # chkstyle: ignore-node
|
|
464
479
|
required=bool(nested_idx(b, 'validations', 'required')),
|
|
465
480
|
options=[o['label'] if isinstance(o, dict) else o for o in b['attributes'].get('options', [])])
|
|
466
481
|
for b in d.get('body', []) if b['type']!='markdown']
|
|
@@ -475,7 +490,7 @@ async def issue_template(self:GhApi):
|
|
|
475
490
|
except APIError as e:
|
|
476
491
|
if e.status_code != 404: raise
|
|
477
492
|
continue
|
|
478
|
-
return L([_parse_tmpl(f.name, base64.b64decode((await self.repos.get_content(path=f.path, **kw)).content).decode())
|
|
493
|
+
return L([_parse_tmpl(f.name, base64.b64decode((await self.repos.get_content(path=f.path, **kw)).content).decode()) # chkstyle: ignore-node
|
|
479
494
|
for f in fs if f.name.endswith(('.md','.yml','.yaml')) and f.name != 'config.yml'])
|
|
480
495
|
return L()
|
|
481
496
|
|
|
@@ -129,7 +129,7 @@ def full_type(self:GhEvent):
|
|
|
129
129
|
_all_ = ['PageBuildEvent', 'ContentReferenceEvent', 'RepositoryImportEvent', 'CreateEvent', 'WorkflowRunEvent', 'DeleteEvent', 'OrganizationEvent', 'SponsorshipEvent', 'ProjectColumnEvent', 'PushEvent', 'ContextEvent', 'MilestoneEvent', 'ProjectCardEvent', 'ProjectEvent', 'PackageEvent', 'PullRequestEvent', 'RepositoryDispatchEvent', 'TeamAddEvent', 'WorkflowDispatchEvent', 'MemberEvent', 'MetaEvent', 'CodeScanningAlertEvent', 'PublicEvent', 'NeedsEvent', 'CheckRunEvent', 'SecurityAdvisoryEvent', 'PullRequestReviewCommentEvent', 'OrgBlockEvent', 'CommitCommentEvent', 'WatchEvent', 'MarketplacePurchaseEvent', 'StarEvent', 'InstallationRepositoriesEvent', 'CheckSuiteEvent', 'GithubAppAuthorizationEvent', 'TeamEvent', 'StatusEvent', 'RepositoryVulnerabilityAlertEvent', 'PullRequestReviewEvent', 'LabelEvent', 'InstallationEvent', 'ReleaseEvent', 'IssuesEvent', 'RepositoryEvent', 'GollumEvent', 'MembershipEvent', 'DeploymentEvent', 'DeployKeyEvent', 'IssueCommentEvent', 'PingEvent', 'DeploymentStatusEvent', 'ForkEvent', 'ScheduleEvent']
|
|
130
130
|
|
|
131
131
|
# %% ../nbs/04_event.ipynb #83be1955
|
|
132
|
-
evt_emojis = dict(
|
|
132
|
+
evt_emojis = dict( # chkstyle: ignore-node
|
|
133
133
|
PushEvent= '⭐',
|
|
134
134
|
CreateEvent= '🏭',
|
|
135
135
|
IssueCommentEvent_created= '💬',
|
|
@@ -157,7 +157,7 @@ def _ref_detl(pay): return pay.ref_type + _ref(pay)
|
|
|
157
157
|
|
|
158
158
|
def _action(self):
|
|
159
159
|
pay = self.payload
|
|
160
|
-
det = (f'issue #{pay.issue.number} on' if isinstance(self,IssuesEvent) else
|
|
160
|
+
det = (f'issue #{pay.issue.number} on' if isinstance(self,IssuesEvent) else # chkstyle: ignore-node
|
|
161
161
|
f'PR #{pay.number} on' if isinstance(self,PullRequestEvent) else
|
|
162
162
|
f'member {pay.member.login} in' if isinstance(self,MemberEvent) else
|
|
163
163
|
f'review comment on PR #{pay.pull_request.number} in' if isinstance(self,PullRequestReviewCommentEvent) else
|
|
@@ -173,7 +173,7 @@ def description(self:GhEvent):
|
|
|
173
173
|
"Description of event"
|
|
174
174
|
act,pay,cls,repo = self.actor,self.payload,type(self),self.repo
|
|
175
175
|
res = _action(self)
|
|
176
|
-
return res if res else (
|
|
176
|
+
return res if res else ( # chkstyle: ignore-node
|
|
177
177
|
f'deleted {_ref_detl(pay)} in' if isinstance(self,DeleteEvent) else
|
|
178
178
|
f'created {_ref_detl(pay)} in' if isinstance(self,CreateEvent) else
|
|
179
179
|
f'pushed {len(pay.commits)} commits{_ref(pay," to")} in' if isinstance(self,PushEvent) else
|
|
@@ -184,17 +184,16 @@ def description(self:GhEvent):
|
|
|
184
184
|
remove_suffix(self.type, "Event")
|
|
185
185
|
)
|
|
186
186
|
|
|
187
|
-
#export
|
|
188
187
|
@patch(as_prop=True)
|
|
189
188
|
def emoji(self:GhEvent):
|
|
190
189
|
"Emoji for event from `evt_emojis`"
|
|
191
190
|
return evt_emojis.get(self.full_type, '❌')
|
|
192
191
|
|
|
193
192
|
# %% ../nbs/04_event.ipynb #daad2a54
|
|
194
|
-
described_evts = (PushEvent,CreateEvent,IssueCommentEvent,WatchEvent,PullRequestEvent,PullRequestReviewEvent,PullRequestReviewCommentEvent,
|
|
193
|
+
described_evts = (PushEvent,CreateEvent,IssueCommentEvent,WatchEvent,PullRequestEvent,PullRequestReviewEvent,PullRequestReviewCommentEvent, # chkstyle: ignore-node
|
|
195
194
|
DeleteEvent,ForkEvent,IssuesEvent,ReleaseEvent,MemberEvent,CommitCommentEvent,GollumEvent,PublicEvent)
|
|
196
195
|
|
|
197
|
-
_text_keys = dict(
|
|
196
|
+
_text_keys = dict( # chkstyle: ignore-node
|
|
198
197
|
CreateEvent = "description",
|
|
199
198
|
PullRequestEvent = "pull_request.title",
|
|
200
199
|
PullRequestReviewCommentEvent = "comment.body",
|
|
@@ -6495,7 +6495,7 @@ spec = {'base_url': 'https://api.github.com',
|
|
|
6495
6495
|
'verb': 'GET',
|
|
6496
6496
|
'summary': 'List secret scanning alerts for an organization',
|
|
6497
6497
|
'route_params': ['org'],
|
|
6498
|
-
'query_params': ['state', 'secret_type', 'exclude_secret_types', 'exclude_providers', 'providers', 'resolution', 'assignee', 'sort', 'direction', 'page', 'per_page', 'before', 'after', 'validity', 'is_publicly_leaked', 'is_multi_repo', 'hide_secret', 'is_bypassed'],
|
|
6498
|
+
'query_params': ['state', 'secret_type', 'exclude_secret_types', 'exclude_providers', 'providers', 'resolution', 'assignee', 'sort', 'direction', 'page', 'per_page', 'before', 'after', 'validity', 'is_publicly_leaked', 'is_multi_repo', 'hide_secret', 'is_bypassed', 'included_metadata', 'owner_email_hash'],
|
|
6499
6499
|
'required_params': ['org'],
|
|
6500
6500
|
'param_types': {'org': 'str',
|
|
6501
6501
|
'state': 'str',
|
|
@@ -6515,7 +6515,9 @@ spec = {'base_url': 'https://api.github.com',
|
|
|
6515
6515
|
'is_publicly_leaked': 'bool',
|
|
6516
6516
|
'is_multi_repo': 'bool',
|
|
6517
6517
|
'hide_secret': 'bool',
|
|
6518
|
-
'is_bypassed': 'bool'
|
|
6518
|
+
'is_bypassed': 'bool',
|
|
6519
|
+
'included_metadata': 'str',
|
|
6520
|
+
'owner_email_hash': 'str'},
|
|
6519
6521
|
'param_defaults': {'sort': 'created', 'direction': 'desc', 'page': 1, 'per_page': 30, 'is_publicly_leaked': False, 'is_multi_repo': False, 'hide_secret': False},
|
|
6520
6522
|
'param_docs': {'org': 'The organization name. The name is not case sensitive.',
|
|
6521
6523
|
'state': 'Set to `open` or `resolved` to only list secret scanning alerts in a specific state.',
|
|
@@ -6538,7 +6540,11 @@ spec = {'base_url': 'https://api.github.com',
|
|
|
6538
6540
|
'is_multi_repo': 'A boolean value representing whether or not to filter alerts by the multi-repo tag being present.',
|
|
6539
6541
|
'hide_secret': 'A boolean value representing whether or not to hide literal secrets in the results.',
|
|
6540
6542
|
'is_bypassed': 'A boolean value (`true` or `false`) indicating whether to filter alerts by their push protection bypass status. When set to `true`, only alerts that were created because a push protection rule was bypassed will be returned. When set to `false`, only alerts that were not caused by a push protection bypass will be '
|
|
6541
|
-
'returned.'
|
|
6543
|
+
'returned.',
|
|
6544
|
+
'included_metadata': 'A comma-separated list of metadata fields to filter alerts by. Only alerts that have all of the specified metadata fields attached will be returned. Possible values are: `owner-email`, `owner-id`, `owner-name`, `secret-id`, `secret-name`, `secret-issued-date`, `secret-expiration-date`, `organization-name`, '
|
|
6545
|
+
'`organization-id`, `last-used-date`, and `has-organization-access`.',
|
|
6546
|
+
'owner_email_hash': 'Filters alerts to only those whose attached `owner_email` metadata field matches the provided value. The value must be the lowercase hex-encoded SHA-256 hash of the email address to match (for example, the SHA-256 of `user@example.com`). Only alerts that have an `owner_email` metadata value whose SHA-256 hash '
|
|
6547
|
+
'equals this parameter are returned.'},
|
|
6542
6548
|
'docs_url': 'https://docs.github.com/rest/secret-scanning/secret-scanning#list-secret-scanning-alerts-for-an-organization'},
|
|
6543
6549
|
{'group': 'secret_scanning',
|
|
6544
6550
|
'name': 'list_org_pattern_configs',
|
|
@@ -11396,6 +11402,31 @@ spec = {'base_url': 'https://api.github.com',
|
|
|
11396
11402
|
'param_types': {'owner': 'str', 'repo': 'str', 'users': 'list'},
|
|
11397
11403
|
'param_docs': {'owner': 'The account owner of the repository. The name is not case sensitive.', 'repo': 'The name of the repository without the `.git` extension. The name is not case sensitive.', 'users': 'A list of user logins to add or remove from the bypass list.'},
|
|
11398
11404
|
'docs_url': 'https://docs.github.com/rest/interactions/repos#remove-users-from-the-pull-request-creation-cap-bypass-list-for-a-repository'},
|
|
11405
|
+
{'group': 'interactions',
|
|
11406
|
+
'name': 'get_pull_request_creation_cap_for_repo',
|
|
11407
|
+
'path': '/repos/{owner}/{repo}/interaction-limits/pulls/creation-cap',
|
|
11408
|
+
'verb': 'GET',
|
|
11409
|
+
'summary': 'Get pull request creation cap for a repository',
|
|
11410
|
+
'route_params': ['owner', 'repo'],
|
|
11411
|
+
'required_params': ['owner', 'repo'],
|
|
11412
|
+
'param_types': {'owner': 'str', 'repo': 'str'},
|
|
11413
|
+
'param_docs': {'owner': 'The account owner of the repository. The name is not case sensitive.', 'repo': 'The name of the repository without the `.git` extension. The name is not case sensitive.'},
|
|
11414
|
+
'docs_url': 'https://docs.github.com/rest/interactions/repos#get-pull-request-creation-cap-for-a-repository'},
|
|
11415
|
+
{'group': 'interactions',
|
|
11416
|
+
'name': 'update_pull_request_creation_cap_for_repo',
|
|
11417
|
+
'path': '/repos/{owner}/{repo}/interaction-limits/pulls/creation-cap',
|
|
11418
|
+
'verb': 'PATCH',
|
|
11419
|
+
'summary': 'Update pull request creation cap for a repository',
|
|
11420
|
+
'route_params': ['owner', 'repo'],
|
|
11421
|
+
'body_params': ['enabled', 'max_open_pull_requests'],
|
|
11422
|
+
'request_content_type': 'application/json',
|
|
11423
|
+
'required_params': ['enabled', 'owner', 'repo'],
|
|
11424
|
+
'param_types': {'owner': 'str', 'repo': 'str', 'enabled': 'bool', 'max_open_pull_requests': 'int'},
|
|
11425
|
+
'param_docs': {'owner': 'The account owner of the repository. The name is not case sensitive.',
|
|
11426
|
+
'repo': 'The name of the repository without the `.git` extension. The name is not case sensitive.',
|
|
11427
|
+
'enabled': 'Whether the pull request creation cap is enabled',
|
|
11428
|
+
'max_open_pull_requests': 'The maximum number of open pull requests a user can have at one time'},
|
|
11429
|
+
'docs_url': 'https://docs.github.com/rest/interactions/repos#update-pull-request-creation-cap-for-a-repository'},
|
|
11399
11430
|
{'group': 'repos',
|
|
11400
11431
|
'name': 'list_invitations',
|
|
11401
11432
|
'path': '/repos/{owner}/{repo}/invitations',
|
|
@@ -13469,7 +13500,7 @@ spec = {'base_url': 'https://api.github.com',
|
|
|
13469
13500
|
'verb': 'GET',
|
|
13470
13501
|
'summary': 'List secret scanning alerts for a repository',
|
|
13471
13502
|
'route_params': ['owner', 'repo'],
|
|
13472
|
-
'query_params': ['state', 'secret_type', 'exclude_secret_types', 'exclude_providers', 'providers', 'resolution', 'assignee', 'sort', 'direction', 'page', 'per_page', 'before', 'after', 'validity', 'is_publicly_leaked', 'is_multi_repo', 'hide_secret', 'is_bypassed'],
|
|
13503
|
+
'query_params': ['state', 'secret_type', 'exclude_secret_types', 'exclude_providers', 'providers', 'resolution', 'assignee', 'sort', 'direction', 'page', 'per_page', 'before', 'after', 'validity', 'is_publicly_leaked', 'is_multi_repo', 'hide_secret', 'is_bypassed', 'included_metadata', 'owner_email_hash'],
|
|
13473
13504
|
'required_params': ['owner', 'repo'],
|
|
13474
13505
|
'param_types': {'owner': 'str',
|
|
13475
13506
|
'repo': 'str',
|
|
@@ -13490,7 +13521,9 @@ spec = {'base_url': 'https://api.github.com',
|
|
|
13490
13521
|
'is_publicly_leaked': 'bool',
|
|
13491
13522
|
'is_multi_repo': 'bool',
|
|
13492
13523
|
'hide_secret': 'bool',
|
|
13493
|
-
'is_bypassed': 'bool'
|
|
13524
|
+
'is_bypassed': 'bool',
|
|
13525
|
+
'included_metadata': 'str',
|
|
13526
|
+
'owner_email_hash': 'str'},
|
|
13494
13527
|
'param_defaults': {'sort': 'created', 'direction': 'desc', 'page': 1, 'per_page': 30, 'is_publicly_leaked': False, 'is_multi_repo': False, 'hide_secret': False},
|
|
13495
13528
|
'param_docs': {'owner': 'The account owner of the repository. The name is not case sensitive.',
|
|
13496
13529
|
'repo': 'The name of the repository without the `.git` extension. The name is not case sensitive.',
|
|
@@ -13514,7 +13547,11 @@ spec = {'base_url': 'https://api.github.com',
|
|
|
13514
13547
|
'is_multi_repo': 'A boolean value representing whether or not to filter alerts by the multi-repo tag being present.',
|
|
13515
13548
|
'hide_secret': 'A boolean value representing whether or not to hide literal secrets in the results.',
|
|
13516
13549
|
'is_bypassed': 'A boolean value (`true` or `false`) indicating whether to filter alerts by their push protection bypass status. When set to `true`, only alerts that were created because a push protection rule was bypassed will be returned. When set to `false`, only alerts that were not caused by a push protection bypass will be '
|
|
13517
|
-
'returned.'
|
|
13550
|
+
'returned.',
|
|
13551
|
+
'included_metadata': 'A comma-separated list of metadata fields to filter alerts by. Only alerts that have all of the specified metadata fields attached will be returned. Possible values are: `owner-email`, `owner-id`, `owner-name`, `secret-id`, `secret-name`, `secret-issued-date`, `secret-expiration-date`, `organization-name`, '
|
|
13552
|
+
'`organization-id`, `last-used-date`, and `has-organization-access`.',
|
|
13553
|
+
'owner_email_hash': 'Filters alerts to only those whose attached `owner_email` metadata field matches the provided value. The value must be the lowercase hex-encoded SHA-256 hash of the email address to match (for example, the SHA-256 of `user@example.com`). Only alerts that have an `owner_email` metadata value whose SHA-256 hash '
|
|
13554
|
+
'equals this parameter are returned.'},
|
|
13518
13555
|
'docs_url': 'https://docs.github.com/rest/secret-scanning/secret-scanning#list-secret-scanning-alerts-for-a-repository'},
|
|
13519
13556
|
{'group': 'secret_scanning',
|
|
13520
13557
|
'name': 'get_alert',
|
|
@@ -24,10 +24,7 @@ class GhGql:
|
|
|
24
24
|
"Lazy-load the GraphQL client and schema"
|
|
25
25
|
if self._client is None:
|
|
26
26
|
if not self.token: raise ValueError("GITHUB_TOKEN not set")
|
|
27
|
-
transport = RequestsHTTPTransport(
|
|
28
|
-
url='https://api.github.com/graphql',
|
|
29
|
-
headers={'Authorization': f'bearer {self.token}'}
|
|
30
|
-
)
|
|
27
|
+
transport = RequestsHTTPTransport(url='https://api.github.com/graphql', headers={'Authorization': f'bearer {self.token}'})
|
|
31
28
|
self._client = Client(transport=transport, fetch_schema_from_transport=True)
|
|
32
29
|
self._client.execute(gql('{ __typename }')) # Trigger schema fetch
|
|
33
30
|
return self._client
|
|
@@ -60,6 +60,14 @@ For anything that's about the local repo/working tree rather than GitHub itself
|
|
|
60
60
|
|
|
61
61
|
`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
62
|
|
|
63
|
+
# External PRs
|
|
64
|
+
|
|
65
|
+
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:
|
|
66
|
+
|
|
67
|
+
api = GhApi('me', repo)
|
|
68
|
+
await api.commit_tree(branch, message, entries)
|
|
69
|
+
await api.pulls.create(owner='upstream', head=f'me:{branch}', base='main', title=title, body=body)
|
|
70
|
+
|
|
63
71
|
# Gotchas
|
|
64
72
|
|
|
65
73
|
- A PR *is* an issue for general comments (`issues.list_comments`, not `pulls.*`) -- inline code-review comments are the separate `pulls.list_review_comments`.
|
ghapi-2.0.1/ghapi/__init__.py
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "2.0.1"
|
|
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
|