{ "cells": [ { "cell_type": "raw", "metadata": {}, "source": [ "---\n", "description: API details\n", "output-file: ghtop.html\n", "title: ghtop API\n", "\n", "---\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#| default_exp ghtop" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#| export\n", "import sys, signal, shutil, os, json\n", "# from dashing import *\n", "from collections import defaultdict\n", "from warnings import warn\n", "from itertools import islice\n", "from fastcore.utils import *\n", "from fastcore.foundation import *\n", "from fastcore.script import *\n", "from ghapi.all import *\n", "from ghtop.richext import *\n", "from ghtop.all_rich import (Console, Color, FixedPanel, box, Segments, Live,\n", " grid, ConsoleOptions, Progress, BarColumn, Spinner, Table)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "evts = load_sample_events()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#| export\n", "\n", "ETYPES=PushEvent,PullRequestEvent,IssuesEvent,ReleaseEvent\n", "\n", "def get_sparklines():\n", " s1 = ESpark('Push', 'magenta', [PushEvent], mx=30)\n", " s2 = ESpark('PR', 'yellow', [PullRequestEvent, PullRequestReviewCommentEvent, PullRequestReviewEvent], mx=8)\n", " s3 = ESpark('Issues', 'green', [IssueCommentEvent,IssuesEvent], mx=6)\n", " s4 = ESpark('Releases', 'blue', [ReleaseEvent], mx=0.4)\n", " s5 = ESpark('All Events', 'orange', mx=45)\n", "\n", " return Stats([s1,s2,s3,s4,s5], store=5, span=5, spn_lbl='5/s', show_freq=True)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#| export\n", "term = Console()\n", "\n", "tdim = L(os.popen('stty size', 'r').read().split())\n", "if not tdim: theight,twidth = 15,15\n", "else: theight,twidth = tdim.map(lambda x: max(int(x)-4, 15))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#| export\n", "def _exit(msg):\n", " print(msg, file=sys.stderr)\n", " sys.exit()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "When creating `GhApi` we can pass a callback which will be called after each API operation. In this case, we use it to warn the user when their quota is getting low." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#| exports\n", "def limit_cb(rem,quota):\n", " \"Callback to warn user when close to using up hourly quota\"\n", " w='WARNING '*7\n", " if rem < 1000: print(f\"{w}\\nRemaining calls: {rem} out of {quota}\\n{w}\", file=sys.stderr)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#| export\n", "def pct_comp(api): return int(((5000-int(api.limit_rem)) / 5000) * 100)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#| export\n", "def tail_events(evt, api):\n", " \"Print events from `fetch_events` along with a counter of push events\"\n", " p = FixedPanel(theight, box=box.HORIZONTALS, title='ghtop')\n", " s = get_sparklines()\n", " g = grid([[s], [p]])\n", " with Live(g):\n", " for e in evt:\n", " s.add_events(e)\n", " s.update_prog(pct_comp(api))\n", " p.append(e)\n", " g = grid([[s], [p]])" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#| export\n", "def _user_grid():\n", " g = Table.grid(expand=True)\n", " g.add_column(justify=\"left\")\n", " for i in range(4): g.add_column(justify=\"center\")\n", " g.add_row(\"\", \"\", \"\", \"\", \"\")\n", " g.add_row(\"User\", \"Events\", \"PRs\", \"Issues\", \"Pushes\")\n", " return g" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#| export\n", "def watch_users(evts, api):\n", " \"Print a table of the users with the most events\"\n", " users,users_events = defaultdict(int),defaultdict(lambda: defaultdict(int))\n", "\n", " with Live() as live:\n", " s = get_sparklines()\n", " while True:\n", " for x in islice(evts, 10):\n", " users[x.actor.login] += 1\n", " users_events[x.actor.login][x.type] += 1\n", " s.add_events(x)\n", " \n", " ig = _user_grid()\n", " sorted_users = sorted(users.items(), key=lambda o: (o[1],o[0]), reverse=True)\n", " for u in sorted_users[:theight]:\n", " data = (*u, *itemgetter('PullRequestEvent','IssuesEvent','PushEvent')(users_events[u[0]]))\n", " ig.add_row(*L(data).map(str))\n", " \n", " s.update_prog(pct_comp(api))\n", " g = grid([[s], [ig]])\n", " live.update(g)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#| export\n", "def _panelDict2Grid(pd):\n", " ispush,ispr,isiss,isrel = pd.values()\n", " return grid([[ispush,ispr],[isiss,isrel]], width=twidth)\n", "\n", "\n", "def quad_logs(evts, api):\n", " \"Print 4 panels, showing most recent issues, commits, PRs, and releases\"\n", " pd = {o:FixedPanel(height=(theight//2)-1,\n", " width=(twidth//2)-1, \n", " box=box.HORIZONTALS, \n", " title=camel2words(remove_suffix(o.__name__,'Event'))) for o in ETYPES}\n", " p = _panelDict2Grid(pd)\n", " s = get_sparklines()\n", " g = grid([[s], [p]])\n", " with Live(g):\n", " for e in evts:\n", " s.add_events(e)\n", " s.update_prog(pct_comp(api))\n", " typ = type(e)\n", " if typ in pd: pd[typ].append(e)\n", " p = _panelDict2Grid(pd)\n", " g = grid([[s], [p]])" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#| export\n", "def simple(evts, api):\n", " for ev in evts: print(f\"{ev.actor.login} {ev.type} {ev.repo.name}\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#| export\n", "def _get_token():\n", " path = Path.home()/\".ghtop_token\"\n", " if path.is_file():\n", " try: return path.read_text().strip()\n", " except: _exit(\"Error reading token\")\n", " else: token = github_auth_device()\n", " path.write_text(token)\n", " return token" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#| export\n", "def _signal_handler(sig, frame):\n", " if sig != signal.SIGINT: return\n", " term.clear()\n", " sys.exit(0)\n", "\n", "_funcs = dict(tail=tail_events, quad=quad_logs, users=watch_users, simple=simple)\n", "_filts = str_enum('_filts', 'users', 'repo', 'org')\n", "_OpModes = str_enum('_OpModes', *_funcs)\n", "\n", "@call_parse\n", "def main(mode: Param(\"Operation mode to run\", _OpModes),\n", " include_bots: Param(\"Include bots (there's a lot of them!)\", store_true)=False,\n", " types: Param(\"Comma-separated types of event to include (e.g PushEvent)\", str)='',\n", " pause: Param(\"Number of seconds to pause between requests to the GitHub api\", float)=0.4,\n", " filt: Param(\"Filtering method\", _filts)=None,\n", " filtval: Param(\"Value to filter by (for `repo` use format `owner/repo`)\", str)=None):\n", " signal.signal(signal.SIGINT, _signal_handler)\n", " types = types.split(',') if types else None\n", " if filt and not filtval: _exit(\"Must pass `filter_value` if passing `filter_type`\")\n", " if filtval and not filt: _exit(\"Must pass `filter_type` if passing `filter_value`\")\n", " kwargs = {filt:filtval} if filt else {}\n", " api = GhApi(limit_cb=limit_cb, token=_get_token())\n", " evts = api.fetch_events(types=types, incl_bot=include_bots, pause=float(pause), **kwargs)\n", " _funcs[mode](evts, api)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Export -" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Converted 00_ghtop.ipynb.\n", "Converted index.ipynb.\n", "Converted richext.ipynb.\n" ] } ], "source": [ "#| include: false\n", "from nbdev import nbdev_export\n", "nbdev_export()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" } }, "nbformat": 4, "nbformat_minor": 4 }