ghapi 1.0.16__tar.gz → 1.1.0__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-1.0.16/ghapi.egg-info → ghapi-1.1.0}/PKG-INFO +1 -1
- ghapi-1.1.0/ghapi/__init__.py +1 -0
- {ghapi-1.0.16 → ghapi-1.1.0}/ghapi/actions.py +24 -24
- {ghapi-1.0.16 → ghapi-1.1.0}/ghapi/auth.py +12 -12
- {ghapi-1.0.16 → ghapi-1.1.0}/ghapi/build_lib.py +6 -6
- {ghapi-1.0.16 → ghapi-1.1.0}/ghapi/cli.py +10 -10
- {ghapi-1.0.16 → ghapi-1.1.0}/ghapi/core.py +41 -40
- {ghapi-1.0.16 → ghapi-1.1.0}/ghapi/event.py +20 -20
- {ghapi-1.0.16 → ghapi-1.1.0}/ghapi/graphql.py +4 -4
- {ghapi-1.0.16 → ghapi-1.1.0}/ghapi/page.py +9 -9
- {ghapi-1.0.16 → ghapi-1.1.0}/ghapi/skill.py +3 -3
- {ghapi-1.0.16 → ghapi-1.1.0/ghapi.egg-info}/PKG-INFO +1 -1
- {ghapi-1.0.16 → ghapi-1.1.0}/pyproject.toml +1 -1
- ghapi-1.0.16/ghapi/__init__.py +0 -1
- {ghapi-1.0.16 → ghapi-1.1.0}/CONTRIBUTING.md +0 -0
- {ghapi-1.0.16 → ghapi-1.1.0}/LICENSE +0 -0
- {ghapi-1.0.16 → ghapi-1.1.0}/MANIFEST.in +0 -0
- {ghapi-1.0.16 → ghapi-1.1.0}/README.md +0 -0
- {ghapi-1.0.16 → ghapi-1.1.0}/ghapi/_modidx.py +0 -0
- {ghapi-1.0.16 → ghapi-1.1.0}/ghapi/_nbdev.py +0 -0
- {ghapi-1.0.16 → ghapi-1.1.0}/ghapi/all.py +0 -0
- {ghapi-1.0.16 → ghapi-1.1.0}/ghapi/metadata.py +0 -0
- {ghapi-1.0.16 → ghapi-1.1.0}/ghapi/py.typed +0 -0
- {ghapi-1.0.16 → ghapi-1.1.0}/ghapi/templates.py +0 -0
- {ghapi-1.0.16 → ghapi-1.1.0}/ghapi.egg-info/SOURCES.txt +0 -0
- {ghapi-1.0.16 → ghapi-1.1.0}/ghapi.egg-info/dependency_links.txt +0 -0
- {ghapi-1.0.16 → ghapi-1.1.0}/ghapi.egg-info/entry_points.txt +0 -0
- {ghapi-1.0.16 → ghapi-1.1.0}/ghapi.egg-info/requires.txt +0 -0
- {ghapi-1.0.16 → ghapi-1.1.0}/ghapi.egg-info/top_level.txt +0 -0
- {ghapi-1.0.16 → ghapi-1.1.0}/setup.cfg +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.1.0"
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Docs: https://ghapi.fast.ai/actions.html.md"""
|
|
4
4
|
|
|
5
|
-
# AUTOGENERATED! DO NOT EDIT! File to edit: ../01_actions.ipynb.
|
|
5
|
+
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/01_actions.ipynb.
|
|
6
6
|
|
|
7
7
|
# %% auto #0
|
|
8
8
|
__all__ = ['contexts', 'env_github', 'Event', 'def_pipinst', 'user_repo', 'create_workflow_files', 'fill_workflow_templates',
|
|
@@ -11,7 +11,7 @@ __all__ = ['contexts', 'env_github', 'Event', 'def_pipinst', 'user_repo', 'creat
|
|
|
11
11
|
'context_github', 'context_env', 'context_job', 'context_steps', 'context_runner', 'context_secrets',
|
|
12
12
|
'context_strategy', 'context_matrix', 'context_needs']
|
|
13
13
|
|
|
14
|
-
# %% ../01_actions.ipynb #e4187aeb
|
|
14
|
+
# %% ../nbs/01_actions.ipynb #e4187aeb
|
|
15
15
|
from fastcore.all import *
|
|
16
16
|
from .core import *
|
|
17
17
|
from .templates import *
|
|
@@ -20,7 +20,7 @@ import textwrap
|
|
|
20
20
|
from contextlib import contextmanager
|
|
21
21
|
from enum import Enum
|
|
22
22
|
|
|
23
|
-
# %% ../01_actions.ipynb #fb69ba90
|
|
23
|
+
# %% ../nbs/01_actions.ipynb #fb69ba90
|
|
24
24
|
# So we can run this outside of GitHub actions too, read from file if needed
|
|
25
25
|
for a,b in (('CONTEXT_GITHUB',context_example), ('CONTEXT_NEEDS',needs_example), ('GITHUB_REPOSITORY','octocat/Hello-World')):
|
|
26
26
|
if a not in os.environ: os.environ[a] = b
|
|
@@ -29,18 +29,18 @@ contexts = 'github', 'env', 'job', 'steps', 'runner', 'secrets', 'strategy', 'ma
|
|
|
29
29
|
for context in contexts:
|
|
30
30
|
globals()[f'context_{context}'] = dict2obj(loads(os.getenv(f"CONTEXT_{context.upper()}", "{}")))
|
|
31
31
|
|
|
32
|
-
# %% ../01_actions.ipynb #393990e1
|
|
32
|
+
# %% ../nbs/01_actions.ipynb #393990e1
|
|
33
33
|
_all_ = ['context_github', 'context_env', 'context_job', 'context_steps', 'context_runner', 'context_secrets', 'context_strategy', 'context_matrix', 'context_needs']
|
|
34
34
|
|
|
35
|
-
# %% ../01_actions.ipynb #02ca0806
|
|
35
|
+
# %% ../nbs/01_actions.ipynb #02ca0806
|
|
36
36
|
env_github = dict2obj({k[7:].lower():v for k,v in os.environ.items() if k.startswith('GITHUB_')})
|
|
37
37
|
|
|
38
|
-
# %% ../01_actions.ipynb #3170b688
|
|
38
|
+
# %% ../nbs/01_actions.ipynb #3170b688
|
|
39
39
|
def user_repo():
|
|
40
40
|
"List of `user,repo` from `env_github.repository"
|
|
41
41
|
return env_github.repository.split('/')
|
|
42
42
|
|
|
43
|
-
# %% ../01_actions.ipynb #deb389d7
|
|
43
|
+
# %% ../nbs/01_actions.ipynb #deb389d7
|
|
44
44
|
Event = str_enum('Event',
|
|
45
45
|
'page_build','content_reference','repository_import','create','workflow_run','delete','organization','sponsorship',
|
|
46
46
|
'project_column','push','context','milestone','project_card','project','package','pull_request','repository_dispatch',
|
|
@@ -50,14 +50,14 @@ Event = str_enum('Event',
|
|
|
50
50
|
'installation','release','issues','repository','gollum','membership','deployment','deploy_key','issue_comment','ping',
|
|
51
51
|
'deployment_status','fork','schedule')
|
|
52
52
|
|
|
53
|
-
# %% ../01_actions.ipynb #bf0b5888
|
|
53
|
+
# %% ../nbs/01_actions.ipynb #bf0b5888
|
|
54
54
|
def _create_file(path:Path, fname:str, contents):
|
|
55
55
|
if contents and not (path/fname).exists(): (path/fname).write_text(contents)
|
|
56
56
|
|
|
57
57
|
def _replace(s:str, find, repl, i:int=0, suf:str=''):
|
|
58
58
|
return s.replace(find, textwrap.indent(repl, ' '*i)+suf)
|
|
59
59
|
|
|
60
|
-
# %% ../01_actions.ipynb #26eb64e5
|
|
60
|
+
# %% ../nbs/01_actions.ipynb #26eb64e5
|
|
61
61
|
def create_workflow_files(fname:str, workflow:str, build_script:str, prebuild:bool=False):
|
|
62
62
|
"Create workflow and script files in suitable places in `github` folder"
|
|
63
63
|
if not os.path.exists('.git'): return print('This does not appear to be the root of a git repo')
|
|
@@ -69,7 +69,7 @@ def create_workflow_files(fname:str, workflow:str, build_script:str, prebuild:bo
|
|
|
69
69
|
_create_file(scr_path, f'build-{fname}.py', build_script)
|
|
70
70
|
if prebuild: _create_file(scr_path, f'prebuild-{fname}.py', build_script)
|
|
71
71
|
|
|
72
|
-
# %% ../01_actions.ipynb #3cf450a0
|
|
72
|
+
# %% ../nbs/01_actions.ipynb #3cf450a0
|
|
73
73
|
def fill_workflow_templates(name:str, event, run, context, script, opersys='ubuntu', prebuild=False):
|
|
74
74
|
"Function to create a simple Ubuntu workflow that calls a Python `ghapi` script"
|
|
75
75
|
c = wf_tmpl
|
|
@@ -80,23 +80,23 @@ def fill_workflow_templates(name:str, event, run, context, script, opersys='ubun
|
|
|
80
80
|
c = _replace(c, f'${find}', str(repl), i)
|
|
81
81
|
create_workflow_files(name, c, script, prebuild=prebuild)
|
|
82
82
|
|
|
83
|
-
# %% ../01_actions.ipynb #bc2f80a5
|
|
83
|
+
# %% ../nbs/01_actions.ipynb #bc2f80a5
|
|
84
84
|
def env_contexts(contexts):
|
|
85
85
|
"Create a suitable `env:` line for a workflow to make a context available in the environment"
|
|
86
86
|
contexts = uniqueify(['github'] + listify(contexts))
|
|
87
87
|
return "\n".join("CONTEXT_" + o.upper() + ": ${{ toJson(" + o.lower() + ") }}" for o in contexts)
|
|
88
88
|
|
|
89
|
-
# %% ../01_actions.ipynb #561125fa
|
|
89
|
+
# %% ../nbs/01_actions.ipynb #561125fa
|
|
90
90
|
def_pipinst = 'pip install -Uq ghapi'
|
|
91
91
|
|
|
92
|
-
# %% ../01_actions.ipynb #39c5d429
|
|
92
|
+
# %% ../nbs/01_actions.ipynb #39c5d429
|
|
93
93
|
def create_workflow(name:str, event:Event, contexts:list=None, opersys='ubuntu', prebuild=False):
|
|
94
94
|
"Function to create a simple Ubuntu workflow that calls a Python `ghapi` script"
|
|
95
95
|
script = "from fastcore.all import *\nfrom ghapi import *"
|
|
96
96
|
fill_workflow_templates(name, f'{event}:', def_pipinst, env_contexts(contexts),
|
|
97
97
|
script=script, opersys=opersys, prebuild=prebuild)
|
|
98
98
|
|
|
99
|
-
# %% ../01_actions.ipynb #e8482286
|
|
99
|
+
# %% ../nbs/01_actions.ipynb #e8482286
|
|
100
100
|
@call_parse
|
|
101
101
|
def gh_create_workflow(
|
|
102
102
|
name:str, # Name of the workflow file
|
|
@@ -106,40 +106,40 @@ def gh_create_workflow(
|
|
|
106
106
|
"Supports `gh-create-workflow`, a CLI wrapper for `create_workflow`."
|
|
107
107
|
create_workflow(name, Event[event], contexts.split())
|
|
108
108
|
|
|
109
|
-
# %% ../01_actions.ipynb #a85ea1f8
|
|
109
|
+
# %% ../nbs/01_actions.ipynb #a85ea1f8
|
|
110
110
|
_example_url = 'https://raw.githubusercontent.com/fastai/ghapi/master/examples/{}.json'
|
|
111
111
|
|
|
112
|
-
# %% ../01_actions.ipynb #47cfc8b9
|
|
112
|
+
# %% ../nbs/01_actions.ipynb #47cfc8b9
|
|
113
113
|
def example_payload(event):
|
|
114
114
|
"Get an example of a JSON payload for `event`"
|
|
115
115
|
return dict2obj(urljson(_example_url.format(event)))
|
|
116
116
|
|
|
117
|
-
# %% ../01_actions.ipynb #778d7cc7
|
|
117
|
+
# %% ../nbs/01_actions.ipynb #778d7cc7
|
|
118
118
|
def github_token():
|
|
119
119
|
"Get GitHub token from `GITHUB_TOKEN` env var if available, or from `github` context"
|
|
120
120
|
return os.getenv('GITHUB_TOKEN', context_github.get('token', None))
|
|
121
121
|
|
|
122
|
-
# %% ../01_actions.ipynb #88716812
|
|
122
|
+
# %% ../nbs/01_actions.ipynb #88716812
|
|
123
123
|
def actions_output(name, value):
|
|
124
124
|
"Print the special GitHub Actions `::set-output` line for `name::value`"
|
|
125
125
|
print(f"::set-output name={name}::{value}")
|
|
126
126
|
|
|
127
|
-
# %% ../01_actions.ipynb #50cad4c2
|
|
127
|
+
# %% ../nbs/01_actions.ipynb #50cad4c2
|
|
128
128
|
def actions_debug(message):
|
|
129
129
|
"Print the special `::debug` line for `message`"
|
|
130
130
|
print(f"::debug::{message}")
|
|
131
131
|
|
|
132
|
-
# %% ../01_actions.ipynb #5b6d15ab
|
|
132
|
+
# %% ../nbs/01_actions.ipynb #5b6d15ab
|
|
133
133
|
def actions_warn(message, details=''):
|
|
134
134
|
"Print the special `::warning` line for `message`"
|
|
135
135
|
print(f"::warning {details}::{message}")
|
|
136
136
|
|
|
137
|
-
# %% ../01_actions.ipynb #e97ba982
|
|
137
|
+
# %% ../nbs/01_actions.ipynb #e97ba982
|
|
138
138
|
def actions_error(message, details=''):
|
|
139
139
|
"Print the special `::error` line for `message`"
|
|
140
140
|
print(f"::error {details}::{message}")
|
|
141
141
|
|
|
142
|
-
# %% ../01_actions.ipynb #a7ff989d
|
|
142
|
+
# %% ../nbs/01_actions.ipynb #a7ff989d
|
|
143
143
|
@contextmanager
|
|
144
144
|
def actions_group(title):
|
|
145
145
|
"Context manager to print the special `::group`/`::endgroup` lines for `title`"
|
|
@@ -147,12 +147,12 @@ def actions_group(title):
|
|
|
147
147
|
yield
|
|
148
148
|
print(f"::endgroup::")
|
|
149
149
|
|
|
150
|
-
# %% ../01_actions.ipynb #3405cb7f
|
|
150
|
+
# %% ../nbs/01_actions.ipynb #3405cb7f
|
|
151
151
|
def actions_mask(value):
|
|
152
152
|
"Print the special `::add-mask` line for `value`"
|
|
153
153
|
print(f"::add-mask::{value}")
|
|
154
154
|
|
|
155
|
-
# %% ../01_actions.ipynb #8682ff46
|
|
155
|
+
# %% ../nbs/01_actions.ipynb #8682ff46
|
|
156
156
|
def set_git_user(api=None):
|
|
157
157
|
"Set git user name/email to authenticated user (if `api`) or GitHub Actions bot (otherwise)"
|
|
158
158
|
if api:
|
|
@@ -2,19 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
Docs: https://ghapi.fast.ai/auth.html.md"""
|
|
4
4
|
|
|
5
|
-
# AUTOGENERATED! DO NOT EDIT! File to edit: ../02_auth.ipynb.
|
|
5
|
+
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/02_auth.ipynb.
|
|
6
6
|
|
|
7
7
|
# %% auto #0
|
|
8
8
|
__all__ = ['Scope', 'scope_str', 'GhDeviceAuth', 'github_auth_device']
|
|
9
9
|
|
|
10
|
-
# %% ../02_auth.ipynb #e1ccb974
|
|
10
|
+
# %% ../nbs/02_auth.ipynb #e1ccb974
|
|
11
11
|
from fastcore.all import *
|
|
12
12
|
from .core import *
|
|
13
13
|
|
|
14
14
|
import webbrowser,time
|
|
15
15
|
from urllib.parse import parse_qs,urlsplit
|
|
16
16
|
|
|
17
|
-
# %% ../02_auth.ipynb #46620407
|
|
17
|
+
# %% ../nbs/02_auth.ipynb #46620407
|
|
18
18
|
_scopes =(
|
|
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',
|
|
@@ -22,18 +22,18 @@ _scopes =(
|
|
|
22
22
|
'write:packages','read:packages','delete:packages','admin:gpg_key','write:gpg_key','read:gpg_key','workflow'
|
|
23
23
|
)
|
|
24
24
|
|
|
25
|
-
# %% ../02_auth.ipynb #0cbc503b
|
|
25
|
+
# %% ../nbs/02_auth.ipynb #0cbc503b
|
|
26
26
|
Scope = AttrDict({o.replace(':','_'):o for o in _scopes})
|
|
27
27
|
|
|
28
|
-
# %% ../02_auth.ipynb #404b7d4f
|
|
28
|
+
# %% ../nbs/02_auth.ipynb #404b7d4f
|
|
29
29
|
def scope_str(*scopes)->str:
|
|
30
30
|
"Convert `scopes` into a comma-separated string"
|
|
31
31
|
return ','.join(str(o) for o in scopes if o)
|
|
32
32
|
|
|
33
|
-
# %% ../02_auth.ipynb #7b224a0a
|
|
33
|
+
# %% ../nbs/02_auth.ipynb #7b224a0a
|
|
34
34
|
_def_clientid = '71604a89b882ab8c8634'
|
|
35
35
|
|
|
36
|
-
# %% ../02_auth.ipynb #2a614e72
|
|
36
|
+
# %% ../nbs/02_auth.ipynb #2a614e72
|
|
37
37
|
class GhDeviceAuth(GetAttrBase):
|
|
38
38
|
"Get an oauth token using the GitHub API device flow"
|
|
39
39
|
_attr="params"
|
|
@@ -44,20 +44,20 @@ class GhDeviceAuth(GetAttrBase):
|
|
|
44
44
|
|
|
45
45
|
def _getattr(self,v): return v[0]
|
|
46
46
|
|
|
47
|
-
# %% ../02_auth.ipynb #d3764621
|
|
47
|
+
# %% ../nbs/02_auth.ipynb #d3764621
|
|
48
48
|
@patch
|
|
49
49
|
def url_docs(self:GhDeviceAuth)->str:
|
|
50
50
|
"Default instructions on how to authenticate"
|
|
51
51
|
return f"""First copy your one-time code: {self.user_code}
|
|
52
52
|
Then visit {self.verification_uri} in your browser, and paste the code when prompted."""
|
|
53
53
|
|
|
54
|
-
# %% ../02_auth.ipynb #255b299c
|
|
54
|
+
# %% ../nbs/02_auth.ipynb #255b299c
|
|
55
55
|
@patch
|
|
56
56
|
def open_browser(self:GhDeviceAuth):
|
|
57
57
|
"Open a web browser with the verification URL"
|
|
58
58
|
webbrowser.open(self.verification_uri)
|
|
59
59
|
|
|
60
|
-
# %% ../02_auth.ipynb #5091b2c8
|
|
60
|
+
# %% ../nbs/02_auth.ipynb #5091b2c8
|
|
61
61
|
@patch
|
|
62
62
|
def auth(self:GhDeviceAuth)->str:
|
|
63
63
|
"Return token if authentication complete, or `None` otherwise"
|
|
@@ -70,7 +70,7 @@ def auth(self:GhDeviceAuth)->str:
|
|
|
70
70
|
if err: raise Exception(resp['error_description'][0])
|
|
71
71
|
return resp['access_token'][0]
|
|
72
72
|
|
|
73
|
-
# %% ../02_auth.ipynb #a7d55794
|
|
73
|
+
# %% ../nbs/02_auth.ipynb #a7d55794
|
|
74
74
|
@patch
|
|
75
75
|
def wait(self:GhDeviceAuth, cb:callable=None, n_polls=9999)->str:
|
|
76
76
|
"Wait up to `n_polls` times for authentication to complete, calling `cb` after each poll, if passed"
|
|
@@ -82,7 +82,7 @@ def wait(self:GhDeviceAuth, cb:callable=None, n_polls=9999)->str:
|
|
|
82
82
|
if cb: cb()
|
|
83
83
|
time.sleep(interval)
|
|
84
84
|
|
|
85
|
-
# %% ../02_auth.ipynb #e329a3d2
|
|
85
|
+
# %% ../nbs/02_auth.ipynb #e329a3d2
|
|
86
86
|
def github_auth_device(wb='', n_polls=9999):
|
|
87
87
|
"Authenticate with GitHub, polling up to `n_polls` times to wait for completion"
|
|
88
88
|
auth = GhDeviceAuth()
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
"""Docs: https://ghapi.fast.ai/build_lib.html.md"""
|
|
2
2
|
|
|
3
|
-
# AUTOGENERATED! DO NOT EDIT! File to edit: ../90_build_lib.ipynb.
|
|
3
|
+
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/90_build_lib.ipynb.
|
|
4
4
|
|
|
5
5
|
# %% auto #0
|
|
6
6
|
__all__ = ['GH_OPENAPI_URL', 'GhMeta', 'build_funcs']
|
|
7
7
|
|
|
8
|
-
# %% ../90_build_lib.ipynb #cdbcf258
|
|
8
|
+
# %% ../nbs/90_build_lib.ipynb #cdbcf258
|
|
9
9
|
from fastcore.all import *
|
|
10
10
|
|
|
11
11
|
import pprint
|
|
@@ -13,11 +13,11 @@ import pprint
|
|
|
13
13
|
from jsonref import loads
|
|
14
14
|
from collections import namedtuple
|
|
15
15
|
|
|
16
|
-
# %% ../90_build_lib.ipynb #c215ed8d
|
|
16
|
+
# %% ../nbs/90_build_lib.ipynb #c215ed8d
|
|
17
17
|
GH_OPENAPI_URL = 'https://github.com/github/rest-api-description/raw/main/descriptions/api.github.com/api.github.com.json?raw=true'
|
|
18
18
|
_DOC_URL = 'https://docs.github.com/'
|
|
19
19
|
|
|
20
|
-
# %% ../90_build_lib.ipynb #ce33036d
|
|
20
|
+
# %% ../nbs/90_build_lib.ipynb #ce33036d
|
|
21
21
|
_lu_type = dict(zip(
|
|
22
22
|
'NA string object array boolean number integer'.split(),
|
|
23
23
|
map(PrettyString,'object str dict list bool int int'.split())
|
|
@@ -36,7 +36,7 @@ def _find_data(d):
|
|
|
36
36
|
if 'properties' in o: return o['properties']
|
|
37
37
|
return {}
|
|
38
38
|
|
|
39
|
-
# %% ../90_build_lib.ipynb #cf7204d9
|
|
39
|
+
# %% ../nbs/90_build_lib.ipynb #cf7204d9
|
|
40
40
|
def build_funcs(nm='ghapi/metadata.py', url=GH_OPENAPI_URL, docurl=_DOC_URL):
|
|
41
41
|
"Build module metadata.py from an Open API spec and optionally filter by a path `pre`"
|
|
42
42
|
def _get_detls(o):
|
|
@@ -55,5 +55,5 @@ def build_funcs(nm='ghapi/metadata.py', url=GH_OPENAPI_URL, docurl=_DOC_URL):
|
|
|
55
55
|
if 'externalDocs' in detls]
|
|
56
56
|
Path(nm).write_text("funcs = " + pprint.pformat(_funcs, width=360))
|
|
57
57
|
|
|
58
|
-
# %% ../90_build_lib.ipynb #9d2307fc
|
|
58
|
+
# %% ../nbs/90_build_lib.ipynb #9d2307fc
|
|
59
59
|
GhMeta = namedtuple('GhMeta', 'path verb oper_id summary doc_url params data preview'.split())
|
|
@@ -2,18 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
Docs: https://ghapi.fast.ai/cli.html.md"""
|
|
4
4
|
|
|
5
|
-
# AUTOGENERATED! DO NOT EDIT! File to edit: ../10_cli.ipynb.
|
|
5
|
+
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/10_cli.ipynb.
|
|
6
6
|
|
|
7
7
|
# %% auto #0
|
|
8
8
|
__all__ = ['ghapi', 'ghpath', 'ghraw', 'completion_ghapi']
|
|
9
9
|
|
|
10
|
-
# %% ../10_cli.ipynb #e10a0628
|
|
10
|
+
# %% ../nbs/10_cli.ipynb #e10a0628
|
|
11
11
|
from fastcore.all import *
|
|
12
12
|
import ghapi.core as gh,inspect
|
|
13
13
|
from .core import *
|
|
14
14
|
from collections import defaultdict
|
|
15
15
|
|
|
16
|
-
# %% ../10_cli.ipynb #44ebb54f
|
|
16
|
+
# %% ../nbs/10_cli.ipynb #44ebb54f
|
|
17
17
|
def _parse_args(a):
|
|
18
18
|
"Extract positional and keyword arguments from `a`=`sys.argv`"
|
|
19
19
|
pos,kw = [],{}
|
|
@@ -45,34 +45,34 @@ def _call_api(f):
|
|
|
45
45
|
call = f(pos, api)
|
|
46
46
|
return call if kw.get('help', None) else call(*pos, **kw)
|
|
47
47
|
|
|
48
|
-
# %% ../10_cli.ipynb #55f9002f
|
|
48
|
+
# %% ../nbs/10_cli.ipynb #55f9002f
|
|
49
49
|
def _ghapi(arg, api):
|
|
50
50
|
for part in arg.pop(0).split('.'): api = getattr(api,part)
|
|
51
51
|
return api
|
|
52
52
|
|
|
53
|
-
# %% ../10_cli.ipynb #7557c309
|
|
53
|
+
# %% ../nbs/10_cli.ipynb #7557c309
|
|
54
54
|
def ghapi():
|
|
55
55
|
"Python backend for the `ghapi` command, which calls an endpoint by operation name"
|
|
56
56
|
res = _call_api(_ghapi)
|
|
57
57
|
if isinstance(res, (gh._GhObj,dict,L)): print(res)
|
|
58
58
|
elif res: print(inspect.signature(res))
|
|
59
59
|
|
|
60
|
-
# %% ../10_cli.ipynb #524623ba
|
|
60
|
+
# %% ../nbs/10_cli.ipynb #524623ba
|
|
61
61
|
def _ghpath(arg, api): return api[arg.pop(0),arg.pop(0)]
|
|
62
62
|
|
|
63
|
-
# %% ../10_cli.ipynb #723624b0
|
|
63
|
+
# %% ../nbs/10_cli.ipynb #723624b0
|
|
64
64
|
def ghpath():
|
|
65
65
|
"Python backend for the `ghpath` command, which calls an endpoint by path"
|
|
66
66
|
print(_call_api(_ghpath) or '')
|
|
67
67
|
|
|
68
|
-
# %% ../10_cli.ipynb #93dbad3e
|
|
68
|
+
# %% ../nbs/10_cli.ipynb #93dbad3e
|
|
69
69
|
def ghraw():
|
|
70
70
|
"Python backend for the `ghraw` command, which calls a fully-specified endpoint"
|
|
71
71
|
cmd,api,pos,kw = _api()
|
|
72
72
|
if not pos: return print(f"Usage: `{cmd}` operation <params>")
|
|
73
73
|
print(api(*pos, **kw))
|
|
74
74
|
|
|
75
|
-
# %% ../10_cli.ipynb #21033494
|
|
75
|
+
# %% ../nbs/10_cli.ipynb #21033494
|
|
76
76
|
_TAB_COMPLETION="""
|
|
77
77
|
_do_ghapi_completions()
|
|
78
78
|
{
|
|
@@ -83,7 +83,7 @@ _do_ghapi_completions()
|
|
|
83
83
|
complete -F _do_ghapi_completions ghapi
|
|
84
84
|
"""
|
|
85
85
|
|
|
86
|
-
# %% ../10_cli.ipynb #9fe362ab
|
|
86
|
+
# %% ../nbs/10_cli.ipynb #9fe362ab
|
|
87
87
|
def completion_ghapi():
|
|
88
88
|
"Python backend for `completion-ghapi` command"
|
|
89
89
|
if len(sys.argv) == 2 and sys.argv[1] == '--install':
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
Docs: https://ghapi.fast.ai/core.html.md"""
|
|
4
4
|
|
|
5
|
-
# AUTOGENERATED! DO NOT EDIT! File to edit: ../00_core.ipynb.
|
|
5
|
+
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/00_core.ipynb.
|
|
6
6
|
|
|
7
7
|
# %% auto #0
|
|
8
8
|
__all__ = ['GH_HOST', 'img_md_pat', 'EMPTY_TREE_SHA', 'print_summary', 'GhApi', 'date2gh', 'gh2date', 'read_pr']
|
|
9
9
|
|
|
10
|
-
# %% ../00_core.ipynb #5b5cba7b
|
|
10
|
+
# %% ../nbs/00_core.ipynb #5b5cba7b
|
|
11
11
|
from fastcore.all import *
|
|
12
12
|
from .metadata import funcs
|
|
13
13
|
|
|
@@ -22,11 +22,11 @@ from pprint import pprint
|
|
|
22
22
|
from time import sleep
|
|
23
23
|
import os, shutil, tempfile, subprocess, fnmatch
|
|
24
24
|
|
|
25
|
-
# %% ../00_core.ipynb #8d2b1a54
|
|
25
|
+
# %% ../nbs/00_core.ipynb #8d2b1a54
|
|
26
26
|
GH_HOST = os.getenv('GH_HOST', "https://api.github.com")
|
|
27
27
|
_DOC_URL = 'https://docs.github.com/'
|
|
28
28
|
|
|
29
|
-
# %% ../00_core.ipynb #ba730c65
|
|
29
|
+
# %% ../nbs/00_core.ipynb #ba730c65
|
|
30
30
|
def _preview_hdr(preview): return {'Accept': f'application/vnd.github.{preview}-preview+json'} if preview else {}
|
|
31
31
|
|
|
32
32
|
def _mk_param(nm, **kwargs): return Parameter(nm, kind=Parameter.POSITIONAL_OR_KEYWORD, **kwargs)
|
|
@@ -43,7 +43,7 @@ def _mk_sig(req_args, opt_args, anno_args):
|
|
|
43
43
|
|
|
44
44
|
class _GhObj: pass
|
|
45
45
|
|
|
46
|
-
# %% ../00_core.ipynb #d4a51c1a
|
|
46
|
+
# %% ../nbs/00_core.ipynb #d4a51c1a
|
|
47
47
|
class _GhVerb(_GhObj):
|
|
48
48
|
__slots__ = 'path,verb,tag,name,summary,url,route_ps,params,data,preview,client,__doc__'.split(',')
|
|
49
49
|
def __init__(self, path, verb, oper, summary, url, params, data, preview, client, kwargs):
|
|
@@ -77,7 +77,7 @@ class _GhVerb(_GhObj):
|
|
|
77
77
|
return f'[{self.tag}.{self.name}]({self.doc_url})({params}): *{self.summary}*'
|
|
78
78
|
__repr__ = _repr_markdown_
|
|
79
79
|
|
|
80
|
-
# %% ../00_core.ipynb #dd7e6b61
|
|
80
|
+
# %% ../nbs/00_core.ipynb #dd7e6b61
|
|
81
81
|
class _GhVerbGroup(_GhObj):
|
|
82
82
|
def __init__(self, name, verbs):
|
|
83
83
|
self.name,self.verbs = name,verbs
|
|
@@ -85,19 +85,20 @@ class _GhVerbGroup(_GhObj):
|
|
|
85
85
|
def __str__(self): return "\n".join(str(v) for v in self.verbs)
|
|
86
86
|
def _repr_markdown_(self): return "\n".join(f'- {v._repr_markdown_()}' for v in self.verbs)
|
|
87
87
|
|
|
88
|
-
# %% ../00_core.ipynb #531aee7d
|
|
88
|
+
# %% ../nbs/00_core.ipynb #531aee7d
|
|
89
89
|
_docroot = 'https://docs.github.com/rest/reference/'
|
|
90
90
|
|
|
91
|
-
# %% ../00_core.ipynb #f361159b
|
|
91
|
+
# %% ../nbs/00_core.ipynb #f361159b
|
|
92
92
|
def print_summary(req:Request):
|
|
93
93
|
"Print `Request.summary` with the token (if any) removed"
|
|
94
94
|
pprint(req.summary('Authorization'))
|
|
95
95
|
|
|
96
|
-
# %% ../00_core.ipynb #dd66fcdd
|
|
96
|
+
# %% ../nbs/00_core.ipynb #dd66fcdd
|
|
97
97
|
_binary_cts = ('octet-stream', 'zip', 'gzip', 'tar', 'image/', 'audio/', 'video/')
|
|
98
98
|
|
|
99
|
-
# %% ../00_core.ipynb #83e8a9ce
|
|
99
|
+
# %% ../nbs/00_core.ipynb #83e8a9ce
|
|
100
100
|
class GhApi(_GhObj):
|
|
101
|
+
"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."
|
|
101
102
|
def __init__(self, owner=None, repo=None, token=None, jwt_token=None, debug=None, limit_cb=None, gh_host=None,
|
|
102
103
|
authenticate=True, **kwargs):
|
|
103
104
|
self.headers = { 'Accept': 'application/vnd.github.v3+json' }
|
|
@@ -149,17 +150,17 @@ class GhApi(_GhObj):
|
|
|
149
150
|
def full_docs(self):
|
|
150
151
|
return '\n'.join(f'## {gn}\n\n{group._repr_markdown_()}\n' for gn,group in sorted(self.groups.items()))
|
|
151
152
|
|
|
152
|
-
# %% ../00_core.ipynb #05cbdf91
|
|
153
|
+
# %% ../nbs/00_core.ipynb #05cbdf91
|
|
153
154
|
def date2gh(dt:datetime)->str:
|
|
154
155
|
"Convert `dt` (which is assumed to be in UTC time zone) to a format suitable for GitHub API operations"
|
|
155
156
|
return f'{dt.replace(microsecond=0).isoformat()}Z'
|
|
156
157
|
|
|
157
|
-
# %% ../00_core.ipynb #3f4c8b27
|
|
158
|
+
# %% ../nbs/00_core.ipynb #3f4c8b27
|
|
158
159
|
def gh2date(dtstr:str)->datetime:
|
|
159
160
|
"Convert date string `dtstr` received from a GitHub API operation to a UTC `datetime`"
|
|
160
161
|
return datetime.fromisoformat(dtstr.replace('Z', ''))
|
|
161
162
|
|
|
162
|
-
# %% ../00_core.ipynb #16068542
|
|
163
|
+
# %% ../nbs/00_core.ipynb #16068542
|
|
163
164
|
img_md_pat = re.compile(r'!\[(?P<alt>.*?)\]\((?P<url>[^\s]+)\)')
|
|
164
165
|
|
|
165
166
|
def _run_subp(cmd):
|
|
@@ -184,7 +185,7 @@ def create_gist(self:GhApi, description, content, filename='gist.txt', public=Fa
|
|
|
184
185
|
content = img_md_pat.sub(lambda m: f"![{m['alt']}]({img_urls.get(m['url'], m['url'])})", content)
|
|
185
186
|
return self.gists.update(gist.id, files={filename:{'content':content}})
|
|
186
187
|
|
|
187
|
-
# %% ../00_core.ipynb #ef04a3ee
|
|
188
|
+
# %% ../nbs/00_core.ipynb #ef04a3ee
|
|
188
189
|
@patch
|
|
189
190
|
def load_gist(self:GhApi, gist_id:str):
|
|
190
191
|
"Retrieve a gist by id, or by `user/id` (as it appears in gist URLs)"
|
|
@@ -205,14 +206,14 @@ def update_gist(self:GhApi, gist_id:str, content:str):
|
|
|
205
206
|
fname = first(gist.files.keys())
|
|
206
207
|
return self.gists.update(gist_id, files={fname: {'content': content}}).html_url
|
|
207
208
|
|
|
208
|
-
# %% ../00_core.ipynb #4b7a278c
|
|
209
|
+
# %% ../nbs/00_core.ipynb #4b7a278c
|
|
209
210
|
@patch
|
|
210
211
|
def delete_release(self:GhApi, release):
|
|
211
212
|
"Delete a release and its associated tag"
|
|
212
213
|
self.repos.delete_release(release.id)
|
|
213
214
|
self.git.delete_ref(f'tags/{release.tag_name}')
|
|
214
215
|
|
|
215
|
-
# %% ../00_core.ipynb #b2bf7e22
|
|
216
|
+
# %% ../nbs/00_core.ipynb #b2bf7e22
|
|
216
217
|
@patch
|
|
217
218
|
def upload_file(self:GhApi, rel, fn):
|
|
218
219
|
"Upload `fn` to endpoint for release `rel`"
|
|
@@ -221,7 +222,7 @@ def upload_file(self:GhApi, rel, fn):
|
|
|
221
222
|
mime = mimetypes.guess_type(fn, False)[0] or 'application/octet-stream'
|
|
222
223
|
return self(url, 'POST', headers={'Content-Type':mime}, query = {'name':fn.name}, data=fn.read_bytes())
|
|
223
224
|
|
|
224
|
-
# %% ../00_core.ipynb #3cad71a4
|
|
225
|
+
# %% ../nbs/00_core.ipynb #3cad71a4
|
|
225
226
|
@patch
|
|
226
227
|
def create_release(self:GhApi, tag_name, branch='master', name=None, body='',
|
|
227
228
|
draft=False, prerelease=False, files=None):
|
|
@@ -232,23 +233,23 @@ def create_release(self:GhApi, tag_name, branch='master', name=None, body='',
|
|
|
232
233
|
for file in listify(files): self.upload_file(rel, file)
|
|
233
234
|
return rel
|
|
234
235
|
|
|
235
|
-
# %% ../00_core.ipynb #2be73ae0
|
|
236
|
+
# %% ../nbs/00_core.ipynb #2be73ae0
|
|
236
237
|
@patch
|
|
237
238
|
def list_tags(self:GhApi, prefix:str=''):
|
|
238
239
|
"List all tags, optionally filtered to those starting with `prefix`"
|
|
239
240
|
return self.git.list_matching_refs(f'tags/{prefix}')
|
|
240
241
|
|
|
241
|
-
# %% ../00_core.ipynb #303eeec6
|
|
242
|
+
# %% ../nbs/00_core.ipynb #303eeec6
|
|
242
243
|
@patch
|
|
243
244
|
def list_branches(self:GhApi, prefix:str=''):
|
|
244
245
|
"List all branches, optionally filtered to those starting with `prefix`"
|
|
245
246
|
return self.git.list_matching_refs(f'heads/{prefix}')
|
|
246
247
|
|
|
247
|
-
# %% ../00_core.ipynb #eb85edd7
|
|
248
|
+
# %% ../nbs/00_core.ipynb #eb85edd7
|
|
248
249
|
# See https://stackoverflow.com/questions/9765453
|
|
249
250
|
EMPTY_TREE_SHA = '4b825dc642cb6eb9a060e54bf8d69288fbee4904'
|
|
250
251
|
|
|
251
|
-
# %% ../00_core.ipynb #ba6ab941
|
|
252
|
+
# %% ../nbs/00_core.ipynb #ba6ab941
|
|
252
253
|
@patch
|
|
253
254
|
def create_branch_empty(self:GhApi, branch):
|
|
254
255
|
t = self.git.create_tree(base_tree=EMPTY_TREE_SHA, tree = [dict(
|
|
@@ -256,38 +257,38 @@ def create_branch_empty(self:GhApi, branch):
|
|
|
256
257
|
c = self.git.create_commit(f'create {branch}', t.sha)
|
|
257
258
|
return self.git.create_ref(f'refs/heads/{branch}', c.sha)
|
|
258
259
|
|
|
259
|
-
# %% ../00_core.ipynb #68b150fc
|
|
260
|
+
# %% ../nbs/00_core.ipynb #68b150fc
|
|
260
261
|
@patch
|
|
261
262
|
def delete_tag(self:GhApi, tag:str):
|
|
262
263
|
"Delete a tag"
|
|
263
264
|
return self.git.delete_ref(f'tags/{tag}')
|
|
264
265
|
|
|
265
|
-
# %% ../00_core.ipynb #75c168a1
|
|
266
|
+
# %% ../nbs/00_core.ipynb #75c168a1
|
|
266
267
|
@patch
|
|
267
268
|
def delete_branch(self:GhApi, branch:str):
|
|
268
269
|
"Delete a branch"
|
|
269
270
|
return self.git.delete_ref(f'heads/{branch}')
|
|
270
271
|
|
|
271
|
-
# %% ../00_core.ipynb #96053795
|
|
272
|
+
# %% ../nbs/00_core.ipynb #96053795
|
|
272
273
|
@patch
|
|
273
274
|
def get_branch(self:GhApi, branch=None):
|
|
274
275
|
branch = branch or self.repos.get().default_branch
|
|
275
276
|
return self.list_branches(branch)[0]
|
|
276
277
|
|
|
277
|
-
# %% ../00_core.ipynb #93b24881
|
|
278
|
+
# %% ../nbs/00_core.ipynb #93b24881
|
|
278
279
|
@patch
|
|
279
280
|
def list_files(self:GhApi, branch=None):
|
|
280
281
|
ref = self.get_branch(branch)
|
|
281
282
|
res = self.git.get_tree(ref.object.sha).tree
|
|
282
283
|
return {o.path:o for o in res}
|
|
283
284
|
|
|
284
|
-
# %% ../00_core.ipynb #ffc347b2
|
|
285
|
+
# %% ../nbs/00_core.ipynb #ffc347b2
|
|
285
286
|
@patch
|
|
286
287
|
def get_content(self:GhApi, path):
|
|
287
288
|
res = self.repos.get_content(path)
|
|
288
289
|
return base64.b64decode(res.content)
|
|
289
290
|
|
|
290
|
-
# %% ../00_core.ipynb #9582ee1f
|
|
291
|
+
# %% ../nbs/00_core.ipynb #9582ee1f
|
|
291
292
|
@patch
|
|
292
293
|
def create_or_update_file(self:GhApi, path, message, committer, author, content=None, sha=None, branch=''):
|
|
293
294
|
if not branch: branch = api.repos.get()['default_branch']
|
|
@@ -297,13 +298,13 @@ def create_or_update_file(self:GhApi, path, message, committer, author, content=
|
|
|
297
298
|
return self.repos.create_or_update_file_contents(path, message, content=content,
|
|
298
299
|
branch=branch, committer=committer or {}, author=author or {}, **kwargs)
|
|
299
300
|
|
|
300
|
-
# %% ../00_core.ipynb #5044445d
|
|
301
|
+
# %% ../nbs/00_core.ipynb #5044445d
|
|
301
302
|
@patch
|
|
302
303
|
def create_file(self:GhApi, path, message, committer, author, content=None, branch=None):
|
|
303
304
|
if not branch: branch = api.repos.get()['default_branch']
|
|
304
305
|
return self.create_or_update_file(path, message, branch=branch, committer=committer, content=content, author=author)
|
|
305
306
|
|
|
306
|
-
# %% ../00_core.ipynb #4e74c337
|
|
307
|
+
# %% ../nbs/00_core.ipynb #4e74c337
|
|
307
308
|
@patch
|
|
308
309
|
def delete_file(self:GhApi, path, message, committer, author, sha=None, branch=None):
|
|
309
310
|
if not branch: branch = api.repos.get()['default_branch']
|
|
@@ -311,26 +312,26 @@ def delete_file(self:GhApi, path, message, committer, author, sha=None, branch=N
|
|
|
311
312
|
return self.repos.delete_file(path, message=message, sha=sha,
|
|
312
313
|
branch=branch, committer=committer, author=author)
|
|
313
314
|
|
|
314
|
-
# %% ../00_core.ipynb #93f6b559
|
|
315
|
+
# %% ../nbs/00_core.ipynb #93f6b559
|
|
315
316
|
@patch
|
|
316
317
|
def update_contents(self:GhApi, path, message, committer, author, content, sha=None, branch=None):
|
|
317
318
|
if not branch: branch = api.repos.get()['default_branch']
|
|
318
319
|
if sha is None: sha = self.list_files()[path].sha
|
|
319
320
|
return self.create_or_update_file(path, message, committer=committer, author=author, content=content, sha=sha, branch=branch)
|
|
320
321
|
|
|
321
|
-
# %% ../00_core.ipynb #444784ce
|
|
322
|
+
# %% ../nbs/00_core.ipynb #444784ce
|
|
322
323
|
def _find_matches(path, pats):
|
|
323
324
|
"Returns matched patterns"
|
|
324
325
|
return L(pats).filter(lambda p: fnmatch.fnmatch(path, p))
|
|
325
326
|
|
|
326
|
-
# %% ../00_core.ipynb #9ffb3b8f
|
|
327
|
+
# %% ../nbs/00_core.ipynb #9ffb3b8f
|
|
327
328
|
def _include(path, include, exclude):
|
|
328
329
|
"Returns True if path matches include patterns (if any) and doesn't match any exclude pattern."
|
|
329
330
|
if include and not any(fnmatch.fnmatch(path, p) for p in listify(include)): return False
|
|
330
331
|
if exclude and any(fnmatch.fnmatch(path, p) for p in listify(exclude)): return False
|
|
331
332
|
return True
|
|
332
333
|
|
|
333
|
-
# %% ../00_core.ipynb #802737b1
|
|
334
|
+
# %% ../nbs/00_core.ipynb #802737b1
|
|
334
335
|
@patch
|
|
335
336
|
def _get_repo_files(self:GhApi, owner, repo, branch="main"):
|
|
336
337
|
return self.git.get_tree(owner=owner, repo=repo, tree_sha=branch, recursive=True)
|
|
@@ -341,14 +342,14 @@ def get_repo_files(self:GhApi, owner, repo, branch="main", inc=None, exc=None):
|
|
|
341
342
|
tree = self._get_repo_files(owner, repo, branch)
|
|
342
343
|
return L(tree['tree']).filter(lambda o: o['type'] == 'blob' and _include(o.path, inc, exc))
|
|
343
344
|
|
|
344
|
-
# %% ../00_core.ipynb #5ef71bf5
|
|
345
|
+
# %% ../nbs/00_core.ipynb #5ef71bf5
|
|
345
346
|
@patch
|
|
346
347
|
def get_file_content(self:GhApi, path, owner, repo, branch="main"):
|
|
347
348
|
o = self.repos.get_content(owner, repo, path, ref=branch)
|
|
348
349
|
o['content_decoded'] = base64.b64decode(o.content).decode('utf-8')
|
|
349
350
|
return o
|
|
350
351
|
|
|
351
|
-
# %% ../00_core.ipynb #d02d1bab
|
|
352
|
+
# %% ../nbs/00_core.ipynb #d02d1bab
|
|
352
353
|
@patch
|
|
353
354
|
@delegates(GhApi.get_repo_files)
|
|
354
355
|
def get_repo_contents(self:GhApi, owner, repo, branch='main', **kwargs):
|
|
@@ -356,7 +357,7 @@ def get_repo_contents(self:GhApi, owner, repo, branch='main', **kwargs):
|
|
|
356
357
|
for s in ('inc','exc',): kwargs.pop(s)
|
|
357
358
|
return parallel(self.get_file_content, repo_files.attrgot("path"), owner=owner, repo=repo, branch=branch, threadpool=True)
|
|
358
359
|
|
|
359
|
-
# %% ../00_core.ipynb #ac4ab4e0
|
|
360
|
+
# %% ../nbs/00_core.ipynb #ac4ab4e0
|
|
360
361
|
@patch
|
|
361
362
|
def enable_pages(self:GhApi, branch=None, path="/"):
|
|
362
363
|
"Enable or update pages for a repo to point to a `branch` and `path`."
|
|
@@ -368,7 +369,7 @@ def enable_pages(self:GhApi, branch=None, path="/"):
|
|
|
368
369
|
if len(self.list_branches(branch))==0: self.create_branch_empty(branch)
|
|
369
370
|
return self.repos.create_pages_site(source=source)
|
|
370
371
|
|
|
371
|
-
# %% ../00_core.ipynb #519ec71c
|
|
372
|
+
# %% ../nbs/00_core.ipynb #519ec71c
|
|
372
373
|
class _IssueInfo(AttrDict):
|
|
373
374
|
def _repr_markdown_(self):
|
|
374
375
|
res = [f"**{self.title}** ({'PR' if self.is_pr else 'issue'})", self.body]
|
|
@@ -397,7 +398,7 @@ def read_issue(self:GhApi, issue_number:int):
|
|
|
397
398
|
if sha: res['diff'] = self.repos.get_commit(sha, headers={'Accept': 'application/vnd.github.v3.diff'})
|
|
398
399
|
return _IssueInfo(dict2obj(res))
|
|
399
400
|
|
|
400
|
-
# %% ../00_core.ipynb #ccf9d458
|
|
401
|
+
# %% ../nbs/00_core.ipynb #ccf9d458
|
|
401
402
|
def _filter_diff(diff, folder='', skip_files=('_modidx.py',)):
|
|
402
403
|
"Filter unified diff to only include files under `folder`, skipping `skip_files`"
|
|
403
404
|
sections = re.split(r'(?=^diff --git )', diff, flags=re.MULTILINE)
|
|
@@ -425,7 +426,7 @@ def _fmt_replies(info):
|
|
|
425
426
|
f"**@{r.user.login}** {r.state} ({(r.submitted_at or '')[:10]}):\n{r.body or ''}" for r in reviews)
|
|
426
427
|
return res
|
|
427
428
|
|
|
428
|
-
# %% ../00_core.ipynb #39dd03b3
|
|
429
|
+
# %% ../nbs/00_core.ipynb #39dd03b3
|
|
429
430
|
def read_pr(
|
|
430
431
|
pr_number:int|str, # Issue/PR number, or GitHub issue/PR URL
|
|
431
432
|
owner:str=None, # Owner (not needed if URL passed)
|
|
@@ -445,7 +446,7 @@ def read_pr(
|
|
|
445
446
|
if replies: res += _fmt_replies(info)
|
|
446
447
|
return res
|
|
447
448
|
|
|
448
|
-
# %% ../00_core.ipynb #97730eae
|
|
449
|
+
# %% ../nbs/00_core.ipynb #97730eae
|
|
449
450
|
class _CheckStatus(AttrDict):
|
|
450
451
|
def _repr_markdown_(self):
|
|
451
452
|
runs = self.check_runs
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Docs: https://ghapi.fast.ai/event.html.md"""
|
|
4
4
|
|
|
5
|
-
# AUTOGENERATED! DO NOT EDIT! File to edit: ../04_event.ipynb.
|
|
5
|
+
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/04_event.ipynb.
|
|
6
6
|
|
|
7
7
|
# %% auto #0
|
|
8
8
|
__all__ = ['evt_emojis', 'described_evts', 'GhEvent', 'load_sample_events', 'save_sample_events', 'PageBuildEvent',
|
|
@@ -17,7 +17,7 @@ __all__ = ['evt_emojis', 'described_evts', 'GhEvent', 'load_sample_events', 'sav
|
|
|
17
17
|
'ReleaseEvent', 'IssuesEvent', 'RepositoryEvent', 'GollumEvent', 'MembershipEvent', 'DeploymentEvent',
|
|
18
18
|
'DeployKeyEvent', 'IssueCommentEvent', 'PingEvent', 'DeploymentStatusEvent', 'ForkEvent', 'ScheduleEvent']
|
|
19
19
|
|
|
20
|
-
# %% ../04_event.ipynb #637bba9f
|
|
20
|
+
# %% ../nbs/04_event.ipynb #637bba9f
|
|
21
21
|
from fastcore.all import *
|
|
22
22
|
from .core import *
|
|
23
23
|
from .page import *
|
|
@@ -26,7 +26,7 @@ from .actions import *
|
|
|
26
26
|
import time,json,gzip
|
|
27
27
|
from itertools import islice
|
|
28
28
|
|
|
29
|
-
# %% ../04_event.ipynb #a19bf236
|
|
29
|
+
# %% ../nbs/04_event.ipynb #a19bf236
|
|
30
30
|
def _list_events(g, username=None, org=None, owner=None, repo=None):
|
|
31
31
|
if (username or org or owner) and \
|
|
32
32
|
not (bool(username) ^ bool(org) ^ bool(owner)): raise Exception('Can not pass more than one of username, org, and owner')
|
|
@@ -36,12 +36,12 @@ def _list_events(g, username=None, org=None, owner=None, repo=None):
|
|
|
36
36
|
if username: return g.list_public_events_for_user,{'username':username}
|
|
37
37
|
return g.list_public_events,{}
|
|
38
38
|
|
|
39
|
-
# %% ../04_event.ipynb #566f4527
|
|
39
|
+
# %% ../nbs/04_event.ipynb #566f4527
|
|
40
40
|
def _id2int(x):
|
|
41
41
|
x.id = int(x.id)
|
|
42
42
|
return x
|
|
43
43
|
|
|
44
|
-
# %% ../04_event.ipynb #c95e27c2
|
|
44
|
+
# %% ../nbs/04_event.ipynb #c95e27c2
|
|
45
45
|
@patch
|
|
46
46
|
@delegates(_list_events)
|
|
47
47
|
def list_events(self:GhApi, per_page=30, page=1, **kwargs):
|
|
@@ -49,7 +49,7 @@ def list_events(self:GhApi, per_page=30, page=1, **kwargs):
|
|
|
49
49
|
oper,kw = _list_events(self.activity, **kwargs)
|
|
50
50
|
return oper(per_page=per_page, page=page, **kw).map(_id2int)
|
|
51
51
|
|
|
52
|
-
# %% ../04_event.ipynb #c7f44fc6
|
|
52
|
+
# %% ../nbs/04_event.ipynb #c7f44fc6
|
|
53
53
|
@patch
|
|
54
54
|
@delegates(_list_events)
|
|
55
55
|
def list_events_parallel(self:GhApi, per_page=30, n_pages=8, **kwargs):
|
|
@@ -57,21 +57,21 @@ def list_events_parallel(self:GhApi, per_page=30, n_pages=8, **kwargs):
|
|
|
57
57
|
oper,kw = _list_events(self.activity, **kwargs)
|
|
58
58
|
return pages(oper, n_pages, per_page=per_page, **kw).concat().map(_id2int)
|
|
59
59
|
|
|
60
|
-
# %% ../04_event.ipynb #129d3fa2
|
|
60
|
+
# %% ../nbs/04_event.ipynb #129d3fa2
|
|
61
61
|
_bot_re = re.compile('b[o0]t')
|
|
62
62
|
def _want_evt(o, types, incl_bot):
|
|
63
63
|
if not incl_bot and _bot_re.search(nested_attr(o, 'actor.login') or ''): return False
|
|
64
64
|
if types and o.type not in types: return False
|
|
65
65
|
return True
|
|
66
66
|
|
|
67
|
-
# %% ../04_event.ipynb #e84331e2
|
|
67
|
+
# %% ../nbs/04_event.ipynb #e84331e2
|
|
68
68
|
class GhEvent(AttrDict):
|
|
69
69
|
"Class for events returned from `fetch_events"
|
|
70
70
|
pass
|
|
71
71
|
|
|
72
72
|
for o in Event: exec(f'class {snake2camel(o)}Event(GhEvent):pass')
|
|
73
73
|
|
|
74
|
-
# %% ../04_event.ipynb #8232d504
|
|
74
|
+
# %% ../nbs/04_event.ipynb #8232d504
|
|
75
75
|
_all_ = [
|
|
76
76
|
'PageBuildEvent', 'ContentReferenceEvent', 'RepositoryImportEvent', 'CreateEvent', 'WorkflowRunEvent', 'DeleteEvent', 'OrganizationEvent',
|
|
77
77
|
'SponsorshipEvent', 'ProjectColumnEvent', 'PushEvent', 'ContextEvent', 'MilestoneEvent', 'ProjectCardEvent', 'ProjectEvent', 'PackageEvent',
|
|
@@ -82,10 +82,10 @@ _all_ = [
|
|
|
82
82
|
'InstallationEvent', 'ReleaseEvent', 'IssuesEvent', 'RepositoryEvent', 'GollumEvent', 'MembershipEvent', 'DeploymentEvent', 'DeployKeyEvent',
|
|
83
83
|
'IssueCommentEvent', 'PingEvent', 'DeploymentStatusEvent', 'ForkEvent', 'ScheduleEvent']
|
|
84
84
|
|
|
85
|
-
# %% ../04_event.ipynb #4de78d50
|
|
85
|
+
# %% ../nbs/04_event.ipynb #4de78d50
|
|
86
86
|
def _cast_evt(o): return globals()[o.type](o)
|
|
87
87
|
|
|
88
|
-
# %% ../04_event.ipynb #e3e61809
|
|
88
|
+
# %% ../nbs/04_event.ipynb #e3e61809
|
|
89
89
|
@patch
|
|
90
90
|
@delegates(_list_events)
|
|
91
91
|
def fetch_events(self:GhApi, n_pages=3, pause=0.4, per_page=30, types=None, incl_bot=False, **kwargs):
|
|
@@ -100,34 +100,34 @@ def fetch_events(self:GhApi, n_pages=3, pause=0.4, per_page=30, types=None, incl
|
|
|
100
100
|
yield from new_evts
|
|
101
101
|
if pause: time.sleep(pause)
|
|
102
102
|
|
|
103
|
-
# %% ../04_event.ipynb #83cc4ca9
|
|
103
|
+
# %% ../nbs/04_event.ipynb #83cc4ca9
|
|
104
104
|
def load_sample_events():
|
|
105
105
|
"Load sample events, downloading if needed"
|
|
106
106
|
name = 'sample_evts.json.gz'
|
|
107
107
|
url = f'https://raw.githubusercontent.com/fastai/ghapi/master/examples/{name}'
|
|
108
108
|
try: path = Path(__file__).parent
|
|
109
|
-
except NameError: path = Path(
|
|
109
|
+
except NameError: path = Path('../examples')
|
|
110
110
|
path = path/name
|
|
111
111
|
if not path.exists():path.write_bytes(urlread(url, decode=False))
|
|
112
112
|
return dict2obj(json.load(open_file(path))).map(_cast_evt)
|
|
113
113
|
|
|
114
|
-
# %% ../04_event.ipynb #4347a0fd
|
|
114
|
+
# %% ../nbs/04_event.ipynb #4347a0fd
|
|
115
115
|
def save_sample_events(n=5000):
|
|
116
116
|
"Save the most recent `n` events as compressed JSON"
|
|
117
117
|
evts = list(islice(api.fetch_events(incl_bot=True), n))
|
|
118
118
|
with gzip.open('sample_evts.json.gz', 'wt') as f: json.dump(obj2dict(evts), f)
|
|
119
119
|
|
|
120
|
-
# %% ../04_event.ipynb #5b935849
|
|
120
|
+
# %% ../nbs/04_event.ipynb #5b935849
|
|
121
121
|
@patch(as_prop=True)
|
|
122
122
|
def full_type(self:GhEvent):
|
|
123
123
|
"Concatenation of `type` and `payload.action` (if available)"
|
|
124
124
|
act = getattr(self.payload, 'action', '')
|
|
125
125
|
return f'{self.type}_{act}' if act else self.type
|
|
126
126
|
|
|
127
|
-
# %% ../04_event.ipynb #b14ba037
|
|
127
|
+
# %% ../nbs/04_event.ipynb #b14ba037
|
|
128
128
|
_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']
|
|
129
129
|
|
|
130
|
-
# %% ../04_event.ipynb #83be1955
|
|
130
|
+
# %% ../nbs/04_event.ipynb #83be1955
|
|
131
131
|
evt_emojis = dict(
|
|
132
132
|
PushEvent= '⭐',
|
|
133
133
|
CreateEvent= '🏭',
|
|
@@ -150,7 +150,7 @@ evt_emojis = dict(
|
|
|
150
150
|
PullRequestEvent_reopened= '🔁'
|
|
151
151
|
)
|
|
152
152
|
|
|
153
|
-
# %% ../04_event.ipynb #281d36b4
|
|
153
|
+
# %% ../nbs/04_event.ipynb #281d36b4
|
|
154
154
|
def _ref(pay, pre=''): return f'{pre} "{pay.ref.split("/")[-1]}"' if pay.ref else ''
|
|
155
155
|
def _ref_detl(pay): return pay.ref_type + _ref(pay)
|
|
156
156
|
|
|
@@ -166,7 +166,7 @@ def _action(self):
|
|
|
166
166
|
'watching' if isinstance(self,WatchEvent) else '')
|
|
167
167
|
if det: return f'{pay.action} {det}'
|
|
168
168
|
|
|
169
|
-
# %% ../04_event.ipynb #e4e71098
|
|
169
|
+
# %% ../nbs/04_event.ipynb #e4e71098
|
|
170
170
|
@patch(as_prop=True)
|
|
171
171
|
def description(self:GhEvent):
|
|
172
172
|
"Description of event"
|
|
@@ -189,7 +189,7 @@ def emoji(self:GhEvent):
|
|
|
189
189
|
"Emoji for event from `evt_emojis`"
|
|
190
190
|
return evt_emojis.get(self.full_type, '❌')
|
|
191
191
|
|
|
192
|
-
# %% ../04_event.ipynb #daad2a54
|
|
192
|
+
# %% ../nbs/04_event.ipynb #daad2a54
|
|
193
193
|
described_evts = (PushEvent,CreateEvent,IssueCommentEvent,WatchEvent,PullRequestEvent,PullRequestReviewEvent,PullRequestReviewCommentEvent,
|
|
194
194
|
DeleteEvent,ForkEvent,IssuesEvent,ReleaseEvent,MemberEvent,CommitCommentEvent,GollumEvent,PublicEvent)
|
|
195
195
|
|
|
@@ -2,17 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
Docs: https://ghapi.fast.ai/graphql.html.md"""
|
|
4
4
|
|
|
5
|
-
# AUTOGENERATED! DO NOT EDIT! File to edit: ../05_graphql.ipynb.
|
|
5
|
+
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/05_graphql.ipynb.
|
|
6
6
|
|
|
7
7
|
# %% auto #0
|
|
8
8
|
__all__ = ['GhGql', 'list_queries', 'query_args', 'type_fields', 'gh_query']
|
|
9
9
|
|
|
10
|
-
# %% ../05_graphql.ipynb #373423dc
|
|
10
|
+
# %% ../nbs/05_graphql.ipynb #373423dc
|
|
11
11
|
import os
|
|
12
12
|
from gql import gql, Client
|
|
13
13
|
from gql.transport.requests import RequestsHTTPTransport
|
|
14
14
|
|
|
15
|
-
# %% ../05_graphql.ipynb #553fc866
|
|
15
|
+
# %% ../nbs/05_graphql.ipynb #553fc866
|
|
16
16
|
class GhGql:
|
|
17
17
|
"GitHub GraphQL client with lazy-loaded schema"
|
|
18
18
|
def __init__(self, token=None):
|
|
@@ -51,7 +51,7 @@ class GhGql:
|
|
|
51
51
|
"List fields on a GraphQL type (use PascalCase, e.g. 'Repository')"
|
|
52
52
|
return list(self.schema.type_map[name].fields.keys())
|
|
53
53
|
|
|
54
|
-
# %% ../05_graphql.ipynb #b1464c99
|
|
54
|
+
# %% ../nbs/05_graphql.ipynb #b1464c99
|
|
55
55
|
_default_client = None
|
|
56
56
|
|
|
57
57
|
def _get_client():
|
|
@@ -2,24 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
Docs: https://ghapi.fast.ai/page.html.md"""
|
|
4
4
|
|
|
5
|
-
# AUTOGENERATED! DO NOT EDIT! File to edit: ../03_page.ipynb.
|
|
5
|
+
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/03_page.ipynb.
|
|
6
6
|
|
|
7
7
|
# %% auto #0
|
|
8
8
|
__all__ = ['paged', 'parse_link_hdr', 'pages']
|
|
9
9
|
|
|
10
|
-
# %% ../03_page.ipynb #6a0bf465
|
|
10
|
+
# %% ../nbs/03_page.ipynb #6a0bf465
|
|
11
11
|
from fastcore.all import *
|
|
12
12
|
from .core import *
|
|
13
13
|
|
|
14
14
|
import re
|
|
15
15
|
from urllib.parse import parse_qs,urlsplit
|
|
16
16
|
|
|
17
|
-
# %% ../03_page.ipynb #cea42302
|
|
17
|
+
# %% ../nbs/03_page.ipynb #cea42302
|
|
18
18
|
def paged(oper, *args, per_page=30, max_pages=9999, **kwargs):
|
|
19
19
|
"Convert operation `oper(*args,**kwargs)` into an iterator"
|
|
20
20
|
yield from itertools.takewhile(noop, (oper(*args, per_page=per_page, page=i, **kwargs) for i in range(1,max_pages+1)))
|
|
21
21
|
|
|
22
|
-
# %% ../03_page.ipynb #56bd053b
|
|
22
|
+
# %% ../nbs/03_page.ipynb #56bd053b
|
|
23
23
|
class _Scanner:
|
|
24
24
|
def __init__(self, buf): self.buf,self.match = buf,None
|
|
25
25
|
def __getitem__(self, key): return self.match.group(key)
|
|
@@ -33,7 +33,7 @@ _TOKEN = r'([^()<>@,;:\"\[\]?={}\s]+)'
|
|
|
33
33
|
_RE_COMMA_HREF = r' *,? *< *([^>]*) *> *'
|
|
34
34
|
_RE_ATTR = rf'{_TOKEN} *(?:= *({_TOKEN}|{_QUOTED}))? *'
|
|
35
35
|
|
|
36
|
-
# %% ../03_page.ipynb #c8de1f90
|
|
36
|
+
# %% ../nbs/03_page.ipynb #c8de1f90
|
|
37
37
|
def _parse_link_hdr(header):
|
|
38
38
|
"Parse an RFC 5988 link header, returning a `list` of `tuple`s of URL and attr `dict`"
|
|
39
39
|
scanner,links = _Scanner(header),[]
|
|
@@ -49,12 +49,12 @@ def _parse_link_hdr(header):
|
|
|
49
49
|
if scanner.buf: raise Exception(f"parse() failed at {scanner.buf!r}")
|
|
50
50
|
return links
|
|
51
51
|
|
|
52
|
-
# %% ../03_page.ipynb #5af9f0d7
|
|
52
|
+
# %% ../nbs/03_page.ipynb #5af9f0d7
|
|
53
53
|
def parse_link_hdr(header):
|
|
54
54
|
"Parse an RFC 5988 link header, returning a `dict` from rels to a `tuple` of URL and attrs `dict`"
|
|
55
55
|
return {a.pop('rel'):(u,a) for u,a in _parse_link_hdr(header)}
|
|
56
56
|
|
|
57
|
-
# %% ../03_page.ipynb #581beb00
|
|
57
|
+
# %% ../nbs/03_page.ipynb #581beb00
|
|
58
58
|
@patch
|
|
59
59
|
def last_page(self:GhApi):
|
|
60
60
|
"Parse RFC 5988 link header from most recent operation, and extract the last page"
|
|
@@ -63,11 +63,11 @@ def last_page(self:GhApi):
|
|
|
63
63
|
qs = parse_qs(urlsplit(last).query)
|
|
64
64
|
return int(nested_idx(qs,'page',0) or 0)
|
|
65
65
|
|
|
66
|
-
# %% ../03_page.ipynb #f34f91bd
|
|
66
|
+
# %% ../nbs/03_page.ipynb #f34f91bd
|
|
67
67
|
def _call_page(i, oper, args, kwargs, per_page):
|
|
68
68
|
return oper(*args, per_page=per_page, page=i, **kwargs)
|
|
69
69
|
|
|
70
|
-
# %% ../03_page.ipynb #75052160
|
|
70
|
+
# %% ../nbs/03_page.ipynb #75052160
|
|
71
71
|
def pages(oper, n_pages, *args, n_workers=None, per_page=100, **kwargs):
|
|
72
72
|
"Get `n_pages` pages from `oper(*args,**kwargs)`"
|
|
73
73
|
return parallel(_call_page, range(1,n_pages+1), oper=oper, per_page=per_page, args=args, kwargs=kwargs,
|
|
@@ -18,9 +18,9 @@
|
|
|
18
18
|
`api.read_issue(number)` is the single call for this -- it fetches title, body, and general comments, and for PRs also the unified diff, inline review comments, and review summaries (approved/changes-requested/commented), returning an `AttrDict` whose repr is a readable summary (title, body, diff size, comment counts). Display it bare instead of printing fields; the full diff stays in `.diff`:
|
|
19
19
|
|
|
20
20
|
info = api.read_issue(205)
|
|
21
|
-
info
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
print(info)
|
|
22
|
+
print(info.diff) # if needed
|
|
23
|
+
# See info.title, info.body, info.comments, info.review_comments, info.reviews for details
|
|
24
24
|
|
|
25
25
|
When you want the whole thing as one LLM-ready markdown string rather than structured data, use the module-level `read_pr(num_or_url, owner, repo, replies=True)` -- it accepts a pasted GitHub URL directly, reduces the diff to just headers and changed lines, and appends formatted comments/review comments/reviews.
|
|
26
26
|
|
ghapi-1.0.16/ghapi/__init__.py
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "1.0.16"
|
|
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
|