Skip to content

Commit

Permalink
Initial prototype of API explorer at /-/api, refs #1871
Browse files Browse the repository at this point in the history
  • Loading branch information
simonw committed Oct 30, 2022
1 parent c35859a commit f6bf2d8
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 1 deletion.
5 changes: 5 additions & 0 deletions datasette/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
JsonDataView,
PatternPortfolioView,
AuthTokenView,
ApiExplorerView,
CreateTokenView,
LogoutView,
AllowDebugView,
Expand Down Expand Up @@ -1235,6 +1236,10 @@ def add_route(view, regex):
CreateTokenView.as_view(self),
r"/-/create-token$",
)
add_route(
ApiExplorerView.as_view(self),
r"/-/api$",
)
add_route(
LogoutView.as_view(self),
r"/-/logout$",
Expand Down
73 changes: 73 additions & 0 deletions datasette/templates/api_explorer.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
{% extends "base.html" %}

{% block title %}API Explorer{% endblock %}

{% block content %}

<h1>API Explorer</h1>

<p>Use this tool to try out the Datasette write API.</p>

{% if errors %}
{% for error in errors %}
<p class="message-error">{{ error }}</p>
{% endfor %}
{% endif %}

<form method="post" id="api-explorer">
<div>
<label for="auth-token">API token:</label>
<input type="text" id="auth-token" name="token" value="" style="width: 40%">
</div>
<div>
<label for="path">API path:</label>
<input type="text" id="path" name="path" value="/fixtures/searchable/-/insert" style="width: 40%">
</div>
<div>
<textarea name="json" style="width: 60%; height: 200px; font-family: monospace; font-size: 0.8em;"></textarea>
</div>
<p><button id="json-format" type="button">Format JSON</button> <input type="submit" value="POST"></p>
</form>

<script>
document.querySelector('#json-format').addEventListener('click', (ev) => {
ev.preventDefault();
const json = document.querySelector('textarea[name="json"]').value;
try {
const parsed = JSON.parse(json);
document.querySelector('textarea[name="json"]').value = JSON.stringify(parsed, null, 2);
} catch (e) {
alert("Error parsing JSON: " + e);
}
});
var form = document.getElementById('api-explorer');
form.addEventListener("submit", (ev) => {
ev.preventDefault();
var formData = new FormData(form);
var json = formData.get('json');
var path = formData.get('path');
var token = formData.get('token');
// Validate JSON
try {
var data = JSON.parse(json);
} catch (err) {
alert("Invalid JSON: " + err);
return;
}
// POST JSON to path with content-type application/json
fetch(path, {
method: 'POST',
body: json,
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
}
}).then(r => r.json()).then(r => {
alert(JSON.stringify(r, null, 2));
}).catch(err => {
alert("Error: " + err);
});
});
</script>

{% endblock %}
8 changes: 8 additions & 0 deletions datasette/views/special.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,3 +235,11 @@ async def post(self, request):
"token_bits": token_bits,
},
)


class ApiExplorerView(BaseView):
name = "api_explorer"
has_json_alternate = False

async def get(self, request):
return await self.render(["api_explorer.html"], request)
2 changes: 1 addition & 1 deletion tests/test_docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def documented_views():
if first_word.endswith("View"):
view_labels.add(first_word)
# We deliberately don't document these:
view_labels.update(("PatternPortfolioView", "AuthTokenView"))
view_labels.update(("PatternPortfolioView", "AuthTokenView", "ApiExplorerView"))
return view_labels


Expand Down

0 comments on commit f6bf2d8

Please sign in to comment.