Skip to content

Commit b2d49b6

Browse files
committed
github-to-sqlite get command, refs #50
1 parent 7aeb51e commit b2d49b6

File tree

4 files changed

+169
-0
lines changed

4 files changed

+169
-0
lines changed

README.md

+20
Original file line numberDiff line numberDiff line change
@@ -166,3 +166,23 @@ If you add the `--fetch` option the command will also fetch the binary content o
166166
[########----------------------------] 397/1799 22% 00:03:43
167167

168168
You can then use the [datasette-render-images](https://github.com/simonw/datasette-render-images) plugin to browse them visually.
169+
170+
## Making authenticated API calls
171+
172+
The `github-to-sqlite get` command provides a convenient shortcut for making authenticated calls to the API. Once you have created your `auth.json` file (or set a `GITHUB_TOKEN` environment variable) you can use it like this:
173+
174+
$ github-to-sqlite get https://api.github.com/gists
175+
176+
This will make an authenticated call to the URL you provide and pretty-print the resulting JSON to the console.
177+
178+
You can ommit the `https://api.github.com/` prefix, for example:
179+
180+
$ github-to-sqlite get /gists
181+
182+
Many GitHub APIs are [paginated using the HTTP Link header](https://docs.github.com/en/rest/guides/traversing-with-pagination). You can follow this pagination and output a list of all of the resulting items using `--paginate`:
183+
184+
$ github-to-sqlite get /users/simonw/repos --paginate
185+
186+
You can outline newline-delimited JSON for each item using `--nl`. This can be useful for streaming items into another tool.
187+
188+
$ github-to-sqlite get /users/simonw/repos --nl

github_to_sqlite/cli.py

+47
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import click
22
import datetime
33
import pathlib
4+
import textwrap
45
import os
56
import sqlite_utils
67
import time
@@ -442,6 +443,52 @@ def emojis(db_path, auth, fetch):
442443
table.update(emoji["name"], {"image": utils.fetch_image(emoji["url"])})
443444

444445

446+
@cli.command()
447+
@click.argument("url", type=str)
448+
@click.option(
449+
"-a",
450+
"--auth",
451+
type=click.Path(file_okay=True, dir_okay=False, allow_dash=True),
452+
default="auth.json",
453+
help="Path to auth.json token file",
454+
)
455+
@click.option(
456+
"--paginate",
457+
is_flag=True,
458+
help="Paginate through all results",
459+
)
460+
@click.option(
461+
"--nl",
462+
is_flag=True,
463+
help="Output newline-delimited JSON",
464+
)
465+
def get(url, auth, paginate, nl):
466+
"Save repos owened by the specified (or authenticated) username or organization"
467+
token = load_token(auth)
468+
if paginate or nl:
469+
first = True
470+
while url:
471+
response = utils.get(url, token)
472+
items = response.json()
473+
if first and not nl:
474+
click.echo("[")
475+
for item in items:
476+
if not first and not nl:
477+
click.echo(",")
478+
first = False
479+
if not nl:
480+
to_dump = json.dumps(item, indent=4)
481+
click.echo(textwrap.indent(to_dump, " "), nl=False)
482+
else:
483+
click.echo(json.dumps(item))
484+
url = response.links.get("next", {}).get("url")
485+
if not nl:
486+
click.echo("\n]")
487+
else:
488+
response = utils.get(url, token)
489+
click.echo(json.dumps(response.json(), indent=4))
490+
491+
445492
def load_token(auth):
446493
try:
447494
token = json.load(open(auth))["github_personal_token"]

github_to_sqlite/utils.py

+9
Original file line numberDiff line numberDiff line change
@@ -649,3 +649,12 @@ def fetch_emojis(token=None):
649649

650650
def fetch_image(url):
651651
return requests.get(url).content
652+
653+
654+
def get(url, token=None):
655+
headers = make_headers(token)
656+
if url.startswith("/"):
657+
url = "https://api.github.com{}".format(url)
658+
response = requests.get(url, headers=headers)
659+
response.raise_for_status()
660+
return response

tests/test_get.py

+93
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
from click.testing import CliRunner
2+
from github_to_sqlite import cli
3+
import pytest
4+
import textwrap
5+
6+
7+
@pytest.fixture
8+
def mocked_paginated(requests_mock):
9+
requests_mock.get(
10+
"https://api.github.com/paginated",
11+
json=[{"id": 1, "title": "Item 1"}, {"id": 2, "title": "Item 2"}],
12+
headers={"link": '<https://api.github.com/paginated?page=2>; rel="next"'},
13+
)
14+
requests_mock.get(
15+
"https://api.github.com/paginated?page=2",
16+
json=[{"id": 3, "title": "Item 3"}, {"id": 4, "title": "Item 4"}],
17+
headers={"link": '<https://api.github.com/paginated>; rel="prev"'},
18+
)
19+
20+
21+
@pytest.mark.parametrize("url", ["https://api.github.com/paginated", "/paginated"])
22+
def test_get(mocked_paginated, url):
23+
runner = CliRunner()
24+
with runner.isolated_filesystem():
25+
result = runner.invoke(cli.cli, ["get", url])
26+
assert 0 == result.exit_code
27+
expected = textwrap.dedent(
28+
"""
29+
[
30+
{
31+
"id": 1,
32+
"title": "Item 1"
33+
},
34+
{
35+
"id": 2,
36+
"title": "Item 2"
37+
}
38+
]
39+
"""
40+
).strip()
41+
assert result.output.strip() == expected
42+
43+
44+
@pytest.mark.parametrize(
45+
"nl,expected",
46+
(
47+
(
48+
False,
49+
textwrap.dedent(
50+
"""
51+
[
52+
{
53+
"id": 1,
54+
"title": "Item 1"
55+
},
56+
{
57+
"id": 2,
58+
"title": "Item 2"
59+
},
60+
{
61+
"id": 3,
62+
"title": "Item 3"
63+
},
64+
{
65+
"id": 4,
66+
"title": "Item 4"
67+
}
68+
]"""
69+
).strip(),
70+
),
71+
(
72+
True,
73+
textwrap.dedent(
74+
"""
75+
{"id": 1, "title": "Item 1"}
76+
{"id": 2, "title": "Item 2"}
77+
{"id": 3, "title": "Item 3"}
78+
{"id": 4, "title": "Item 4"}
79+
"""
80+
).strip(),
81+
),
82+
),
83+
)
84+
def test_get_paginate(mocked_paginated, nl, expected):
85+
runner = CliRunner()
86+
with runner.isolated_filesystem():
87+
result = runner.invoke(
88+
cli.cli,
89+
["get", "https://api.github.com/paginated", "--paginate"]
90+
+ (["--nl"] if nl else []),
91+
)
92+
assert 0 == result.exit_code
93+
assert result.output.strip() == expected

0 commit comments

Comments
 (0)