Ben Chuanlong Du's Blog

It is never too late to learn.

Hands on ghapi

Things on this page are fragmentary and immature notes/thoughts of the author. Please read with your own judgement!

Comments

There seem to be issues with the library. It is suggested that you call GitHub REST APIs directly, which is very straightforwad. For more discussions on GitHub APIs, please refer to GitHub API .

In [1]:
!pip3 install ghapi
Defaulting to user installation because normal site-packages is not writeable
Collecting ghapi
  Downloading ghapi-0.1.20-py3-none-any.whl (53 kB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 54.0/54.0 KB 1.2 MB/s eta 0:00:00a 0:00:01
Collecting fastcore
  Downloading fastcore-1.4.2-py3-none-any.whl (60 kB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 60.1/60.1 KB 1.7 MB/s eta 0:00:00
Requirement already satisfied: pip in /usr/local/lib/python3.8/dist-packages (from ghapi) (22.0.4)
Requirement already satisfied: packaging in /usr/local/lib/python3.8/dist-packages (from ghapi) (21.3)
Requirement already satisfied: pyparsing!=3.0.5,>=2.0.2 in /usr/local/lib/python3.8/dist-packages (from packaging->ghapi) (3.0.8)
Installing collected packages: fastcore, ghapi
Successfully installed fastcore-1.4.2 ghapi-0.1.20
In [47]:
from ghapi.all import GhApi

api = GhApi(owner="dclong", token="ghp_xxxxxxxxxxxxxxxxxxxxxxxxx")

Methods of GhApi

In [4]:
[m for m in dir(api) if not m.startswith("_")]
Out[4]:
['actions',
 'activity',
 'apps',
 'billing',
 'checks',
 'code_scanning',
 'codes_of_conduct',
 'codespaces',
 'create_branch_empty',
 'create_gist',
 'create_release',
 'debug',
 'delete_branch',
 'delete_release',
 'delete_tag',
 'dependabot',
 'dependency_graph',
 'emojis',
 'enable_pages',
 'enterprise_admin',
 'fetch_events',
 'full_docs',
 'func_dict',
 'get_branch',
 'get_content',
 'gh_host',
 'gists',
 'git',
 'gitignore',
 'groups',
 'headers',
 'interactions',
 'issues',
 'last_page',
 'licenses',
 'limit_cb',
 'limit_rem',
 'list_branches',
 'list_events',
 'list_events_parallel',
 'list_files',
 'list_tags',
 'markdown',
 'meta',
 'migrations',
 'oauth_authorizations',
 'orgs',
 'packages',
 'projects',
 'pulls',
 'rate_limit',
 'reactions',
 'repos',
 'scim',
 'search',
 'secret_scanning',
 'teams',
 'update_contents',
 'upload_file',
 'users']

GhApi.repos

In [13]:
api.repos
Out[13]:
In [138]:
repos = api.repos.list_for_org("legendu-net", per_page=100)
In [139]:
len(repos)
Out[139]:
61

Get all Docker related repositories in the org legendu-net.

In [140]:
repos_docker = [repo.name for repo in repos.items if repo.name.startswith("docker-")]
repos_docker
Out[140]:
['docker-rstudio',
 'docker-ubuntu_cn',
 'docker-jupyterlab',
 'docker-jupyterhub-rb',
 'docker-r-base',
 'docker-r-pop',
 'docker-jupyterhub-rp',
 'docker-jupyterhub-toree',
 'docker-python',
 'docker-jdk',
 'docker-dryscrape',
 'docker-nodejs',
 'docker-typescript',
 'docker-jupyterhub-ts',
 'docker-jupyterhub-ds',
 'docker-base',
 'docker-xubuntu-py',
 'docker-samba',
 'docker-conda',
 'docker-conda-yarn',
 'docker-jupyter',
 'docker-jupyterhub',
 'docker-jupyterhub-jdk',
 'docker-jupyterhub-antlr4',
 'docker-python-jdk',
 'docker-nfs',
 'docker-conda-build',
 'docker-mlflow',
 'docker-python-nodejs',
 'docker-jupyterhub-sagemath',
 'docker-lubuntu',
 'docker-lubuntu-jdk',
 'docker-jupyterhub-julia',
 'docker-jupyterhub-almond',
 'docker-jupyterlab-quickopen',
 'docker-deepin',
 'docker-deepin_b',
 'docker-deepin_cn',
 'docker-ldeepin',
 'docker-lubuntu-pyside2',
 'docker-jupyterhub-selenium-firefox',
 'docker-jupyterhub-selenium-chrome',
 'docker-vscode-server',
 'docker-gitpod',
 'docker-jupyterhub-cuda',
 'docker-jupyterhub-cuda-dev',
 'docker-jupyterhub-pytorch',
 'docker-jupyterhub-more',
 'docker-rustpython',
 'docker-rust',
 'docker-python-portable',
 'docker-pypy',
 'docker-rust-utils',
 'docker-tensorboard',
 'docker-evcxr_jupyter',
 'docker-jupyterhub-golang',
 'docker-jupyterhub-kotlin']

GhApi.Actions

In [38]:
api.actions
Out[38]:
In [77]:
[m for m in dir(api.actions) if "secret" in m]
Out[77]:
['add_selected_repo_to_org_secret',
 'create_or_update_environment_secret',
 'create_or_update_org_secret',
 'create_or_update_repo_secret',
 'delete_environment_secret',
 'delete_org_secret',
 'delete_repo_secret',
 'get_environment_secret',
 'get_org_secret',
 'get_repo_secret',
 'list_environment_secrets',
 'list_org_secrets',
 'list_repo_secrets',
 'list_selected_repos_for_org_secret',
 'remove_selected_repo_from_org_secret',
 'set_selected_repos_for_org_secret']

List secrets in the repository dclong/blog.

In [48]:
api.actions.list_repo_secrets(owner="dclong", repo="blog")
Out[48]:
{'secrets': [], 'total_count': 0}
In [71]:
api.actions.list_repo_secrets(owner="legendu-net", repo="xinstall")
Out[71]:
{ 'secrets': [{'name': 'PYPI_XINSTALL', 'created_at': '2022-04-29T19:39:43Z', 'updated_at': '2022-04-29T19:39:43Z'}],
  'total_count': 1}
In [68]:
api.actions.get_repo_secret("xinstall", "PYPI_XINSTALL")
Out[68]:
{ 'created_at': '2022-04-29T19:39:43Z',
  'name': 'PYPI_XINSTALL',
  'updated_at': '2022-04-29T19:39:43Z'}
In [72]:
api.actions.get_repo_secret("docker_image_builder", "DOCKERHUB_USERNAME")
Out[72]:
{ 'created_at': '2020-11-14T05:26:26Z',
  'name': 'DOCKERHUB_USERNAME',
  'updated_at': '2020-11-14T05:26:26Z'}
In [71]:
api.actions.list_repo_secrets(owner="legendu-net", repo="xinstall")
Out[71]:
{ 'secrets': [{'name': 'PYPI_XINSTALL', 'created_at': '2022-04-29T19:39:43Z', 'updated_at': '2022-04-29T19:39:43Z'}],
  'total_count': 1}
In [82]:
api.actions.list_repo_secrets(owner="legendu-net", repo="docker-jupyterhub-cuda")
Out[82]:
{ 'secrets': [{'name': 'GITHUBACTIONS', 'created_at': '2021-11-28T21:46:41Z', 'updated_at': '2021-11-28T21:46:41Z'}],
  'total_count': 1}

List secrets in the repository legendu-net/xinstall.

In [142]:
?api.actions.delete_repo_secret
Signature:   api.actions.delete_repo_secret(repo, secret_name)
Type:        _GhVerb
String form:
actions.delete_repo_secret(repo, secret_name)
https://docs.github.com/rest/reference/actions#delete-a-repository-secret
File:        ~/.local/lib/python3.8/site-packages/ghapi/core.py
Docstring:   Delete a repository secret
In [150]:
api.actions.delete_repo_secret(
    owner="legendu-net", repo="docker-jupyterhub-cuda", secret_name="GITHUBACTIONS"
)
---------------------------------------------------------------------------
HTTPError                                 Traceback (most recent call last)
Input In [150], in <cell line: 1>()
----> 1 api.actions.delete_repo_secret(owner="legendu-net", repo="docker-jupyterhub-cuda", secret_name="GITHUBACTIONS")

File ~/.local/lib/python3.8/site-packages/ghapi/core.py:63, in _GhVerb.__call__(self, headers, *args, **kwargs)
     60 kwargs = {k:v for k,v in kwargs.items() if v is not None}
     61 route_p,query_p,data_p = [{p:kwargs[p] for p in o if p in kwargs}
     62                          for o in (self.route_ps,self.params,d)]
---> 63 return self.client(self.path, self.verb, headers=headers, route=route_p, query=query_p, data=data_p)

File ~/.local/lib/python3.8/site-packages/ghapi/core.py:112, in GhApi.__call__(self, path, verb, headers, route, query, data)
    110 if route:
    111     for k,v in route.items(): route[k] = quote(str(route[k]))
--> 112 res,self.recv_hdrs = urlsend(path, verb, headers=headers or None, debug=self.debug, return_headers=True,
    113                              route=route or None, query=query or None, data=data or None)
    114 if 'X-RateLimit-Remaining' in self.recv_hdrs:
    115     newlim = self.recv_hdrs['X-RateLimit-Remaining']

File ~/.local/lib/python3.8/site-packages/fastcore/net.py:212, in urlsend(url, verb, headers, route, query, data, json_data, return_json, return_headers, debug)
    209 if route and route.get('archive_format', None):
    210     return urlread(req, decode=False, return_json=False, return_headers=return_headers)
--> 212 return urlread(req, return_json=return_json, return_headers=return_headers)

File ~/.local/lib/python3.8/site-packages/fastcore/net.py:111, in urlread(url, data, headers, decode, return_json, return_headers, timeout, **kwargs)
    109 "Retrieve `url`, using `data` dict or `kwargs` to `POST` if present"
    110 try:
--> 111     with urlopen(url, data=data, headers=headers, timeout=timeout, **kwargs) as u: res,hdrs = u.read(),u.headers
    112 except HTTPError as e:
    113     if 400 <= e.code < 500: raise ExceptionsHTTP[e.code](e.url, e.hdrs, e.fp) from None

File ~/.local/lib/python3.8/site-packages/fastcore/net.py:105, in urlopen(url, data, headers, timeout, **kwargs)
    103     if not isinstance(data, (str,bytes)): data = urlencode(data)
    104     if not isinstance(data, bytes): data = data.encode('ascii')
--> 105 return _opener.open(urlwrap(url, data=data, headers=headers), timeout=timeout)

File /usr/lib/python3.8/urllib/request.py:531, in OpenerDirector.open(self, fullurl, data, timeout)
    529 for processor in self.process_response.get(protocol, []):
    530     meth = getattr(processor, meth_name)
--> 531     response = meth(req, response)
    533 return response

File /usr/lib/python3.8/urllib/request.py:640, in HTTPErrorProcessor.http_response(self, request, response)
    637 # According to RFC 2616, "2xx" code indicates that the client's
    638 # request was successfully received, understood, and accepted.
    639 if not (200 <= code < 300):
--> 640     response = self.parent.error(
    641         'http', request, response, code, msg, hdrs)
    643 return response

File /usr/lib/python3.8/urllib/request.py:563, in OpenerDirector.error(self, proto, *args)
    561     http_err = 0
    562 args = (dict, proto, meth_name) + args
--> 563 result = self._call_chain(*args)
    564 if result:
    565     return result

File /usr/lib/python3.8/urllib/request.py:502, in OpenerDirector._call_chain(self, chain, kind, meth_name, *args)
    500 for handler in handlers:
    501     func = getattr(handler, meth_name)
--> 502     result = func(*args)
    503     if result is not None:
    504         return result

File /usr/lib/python3.8/urllib/request.py:734, in HTTPRedirectHandler.http_error_302(self, req, fp, code, msg, headers)
    729 newurl = urljoin(req.full_url, newurl)
    731 # XXX Probably want to forget about the state of the current
    732 # request, although that might interact poorly with other
    733 # handlers that also use handler-specific request attributes
--> 734 new = self.redirect_request(req, fp, code, msg, headers, newurl)
    735 if new is None:
    736     return

File /usr/lib/python3.8/urllib/request.py:672, in HTTPRedirectHandler.redirect_request(self, req, fp, code, msg, headers, newurl)
    669 m = req.get_method()
    670 if (not (code in (301, 302, 303, 307) and m in ("GET", "HEAD")
    671     or code in (301, 302, 303) and m == "POST")):
--> 672     raise HTTPError(req.full_url, code, msg, headers, fp)
    674 # Strictly (according to RFC 2616), 301 or 302 in response to
    675 # a POST MUST NOT cause a redirection without confirmation
    676 # from the user (of urllib.request, in this case).  In practice,
   (...)
    681 # redundant with the more complete encoding done in http_error_302(),
    682 # but it is kept for compatibility with other callers.
    683 newurl = newurl.replace(' ', '%20')

HTTPError: HTTP Error 307: Temporary Redirect

GhApi.issues

In [9]:
api.issues.list()
Unexpected exception formatting exception. Falling back to standard exception
Traceback (most recent call last):
  File "/usr/local/lib/python3.8/dist-packages/IPython/core/interactiveshell.py", line 3397, in run_code
    exec(code_obj, self.user_global_ns, self.user_ns)
  File "/tmp/ipykernel_3545/1632110837.py", line 1, in <cell line: 1>
    api.issues.list()
  File "/home/dclong/.local/lib/python3.8/site-packages/ghapi/core.py", line 63, in __call__
    return self.client(self.path, self.verb, headers=headers, route=route_p, query=query_p, data=data_p)
  File "/home/dclong/.local/lib/python3.8/site-packages/ghapi/core.py", line 112, in __call__
    res,self.recv_hdrs = urlsend(path, verb, headers=headers or None, debug=self.debug, return_headers=True,
  File "/home/dclong/.local/lib/python3.8/site-packages/fastcore/net.py", line 212, in urlsend
    return urlread(req, return_json=return_json, return_headers=return_headers)
  File "/home/dclong/.local/lib/python3.8/site-packages/fastcore/net.py", line 113, in urlread
    if 400 <= e.code < 500: raise ExceptionsHTTP[e.code](e.url, e.hdrs, e.fp) from None
fastcore.basics.HTTP404NotFoundError: HTTP Error 404: Not Found

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/local/lib/python3.8/dist-packages/IPython/core/interactiveshell.py", line 1989, in showtraceback
    if hasattr(value, "_render_traceback_"):
  File "/usr/lib/python3.8/tempfile.py", line 606, in __getattr__
    file = self.__dict__['file']
KeyError: 'file'
In [7]:
api.issues
Out[7]:
In [11]:
api.oauth_authorizations
Out[11]:
In [20]:
api.secret_scanning
Out[20]:
In [ ]:
 

Comments