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.
Files changed (30) hide show
  1. {ghapi-1.0.16/ghapi.egg-info → ghapi-1.1.0}/PKG-INFO +1 -1
  2. ghapi-1.1.0/ghapi/__init__.py +1 -0
  3. {ghapi-1.0.16 → ghapi-1.1.0}/ghapi/actions.py +24 -24
  4. {ghapi-1.0.16 → ghapi-1.1.0}/ghapi/auth.py +12 -12
  5. {ghapi-1.0.16 → ghapi-1.1.0}/ghapi/build_lib.py +6 -6
  6. {ghapi-1.0.16 → ghapi-1.1.0}/ghapi/cli.py +10 -10
  7. {ghapi-1.0.16 → ghapi-1.1.0}/ghapi/core.py +41 -40
  8. {ghapi-1.0.16 → ghapi-1.1.0}/ghapi/event.py +20 -20
  9. {ghapi-1.0.16 → ghapi-1.1.0}/ghapi/graphql.py +4 -4
  10. {ghapi-1.0.16 → ghapi-1.1.0}/ghapi/page.py +9 -9
  11. {ghapi-1.0.16 → ghapi-1.1.0}/ghapi/skill.py +3 -3
  12. {ghapi-1.0.16 → ghapi-1.1.0/ghapi.egg-info}/PKG-INFO +1 -1
  13. {ghapi-1.0.16 → ghapi-1.1.0}/pyproject.toml +1 -1
  14. ghapi-1.0.16/ghapi/__init__.py +0 -1
  15. {ghapi-1.0.16 → ghapi-1.1.0}/CONTRIBUTING.md +0 -0
  16. {ghapi-1.0.16 → ghapi-1.1.0}/LICENSE +0 -0
  17. {ghapi-1.0.16 → ghapi-1.1.0}/MANIFEST.in +0 -0
  18. {ghapi-1.0.16 → ghapi-1.1.0}/README.md +0 -0
  19. {ghapi-1.0.16 → ghapi-1.1.0}/ghapi/_modidx.py +0 -0
  20. {ghapi-1.0.16 → ghapi-1.1.0}/ghapi/_nbdev.py +0 -0
  21. {ghapi-1.0.16 → ghapi-1.1.0}/ghapi/all.py +0 -0
  22. {ghapi-1.0.16 → ghapi-1.1.0}/ghapi/metadata.py +0 -0
  23. {ghapi-1.0.16 → ghapi-1.1.0}/ghapi/py.typed +0 -0
  24. {ghapi-1.0.16 → ghapi-1.1.0}/ghapi/templates.py +0 -0
  25. {ghapi-1.0.16 → ghapi-1.1.0}/ghapi.egg-info/SOURCES.txt +0 -0
  26. {ghapi-1.0.16 → ghapi-1.1.0}/ghapi.egg-info/dependency_links.txt +0 -0
  27. {ghapi-1.0.16 → ghapi-1.1.0}/ghapi.egg-info/entry_points.txt +0 -0
  28. {ghapi-1.0.16 → ghapi-1.1.0}/ghapi.egg-info/requires.txt +0 -0
  29. {ghapi-1.0.16 → ghapi-1.1.0}/ghapi.egg-info/top_level.txt +0 -0
  30. {ghapi-1.0.16 → ghapi-1.1.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ghapi
3
- Version: 1.0.16
3
+ Version: 1.1.0
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
@@ -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()/'examples'
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.title, info.body, info.diff, info.comments, info.review_comments, info.reviews
22
-
23
- This exists because GitHub's REST API doesn't treat these uniformly: a PR *is* an issue, so its general comments come from `issues.list_comments`, not `pulls.*`; the diff needs a special `Accept` header; and review comments/reviews are yet another pair of endpoints. `read_issue` also transparently falls back to a linked commit's diff for a plain issue that was closed by a commit reference (e.g. "Fixes #123").
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
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ghapi
3
- Version: 1.0.16
3
+ Version: 1.1.0
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
@@ -44,7 +44,7 @@ include = ["ghapi"]
44
44
  [tool.nbdev]
45
45
  allowed_metadata_keys = ['solveit']
46
46
  allowed_cell_metadata_keys = ['solveit_ai']
47
- nbs_path = '.'
47
+ nbs_path = 'nbs'
48
48
  recursive = false
49
49
  jupyter_hooks = true
50
50
  custom_sidebar = false
@@ -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