Implemented multi-db support plus initial URL structure

Refs #24

Fixes #15
pull/383/head
Simon Willison 2017-10-23 19:00:37 -07:00
rodzic 6a0c5de615
commit b2372605d6
5 zmienionych plików z 147 dodań i 47 usunięć

7
.gitignore vendored
Wyświetl plik

@ -1,4 +1,9 @@
build-metadata.json
datasets.json
# SQLite databases
*.db
*.sqlite
# Byte-compiled / optimized / DLL files
__pycache__/
@ -102,5 +107,3 @@ ENV/
# mypy
.mypy_cache/
# SQLite databases
*.db

111
app.py
Wyświetl plik

@ -1,5 +1,6 @@
from sanic import Sanic
from sanic import response
from sanic.exceptions import NotFound
from sanic_jinja2 import SanicJinja2
import sqlite3
from pathlib import Path
@ -13,6 +14,19 @@ BUILD_METADATA = 'build-metadata.json'
DB_GLOBS = ('*.db', '*.sqlite', '*.sqlite3')
HASH_BLOCK_SIZE = 1024 * 1024
conns = {}
def get_conn(name):
if name not in conns:
info = ensure_build_metadata()[name]
conns[name] = sqlite3.connect(
'file:{}?immutable=1'.format(info['file']),
uri=True
)
conns[name].row_factory = sqlite3.Row
return conns[name]
def ensure_build_metadata(regenerate=False):
build_metadata = app_root / BUILD_METADATA
@ -55,10 +69,6 @@ def ensure_build_metadata(regenerate=False):
app = Sanic(__name__)
jinja = SanicJinja2(app)
#conn = sqlite3.connect('file:flights.db?immutable=1', uri=True)
conn = sqlite3.connect('file:northwind.db?immutable=1', uri=True)
conn.row_factory = sqlite3.Row
def sqlerrors(fn):
@wraps(fn)
@ -76,32 +86,85 @@ def sqlerrors(fn):
@app.route('/')
@sqlerrors
async def index(request, sql=None):
sql = sql or request.args.get('sql', '')
if not sql:
sql = 'select * from sqlite_master'
rows = conn.execute(sql)
headers = [r[0] for r in rows.description]
return jinja.render('index.html', request,
headers=headers,
rows=list(rows),
metadata=json.dumps(ensure_build_metadata(True), indent=2)
databases = ensure_build_metadata(True)
return jinja.render(
'index.html',
request,
databases=databases,
)
@app.route('/<table:[a-zA-Z0-9].*>.json')
@sqlerrors
async def table_json(request, table):
sql = 'select * from {} limit 20'.format(table)
return response.json([
dict(r) for r in conn.execute(sql)
])
@app.route('/favicon.ico')
async def favicon(request):
return response.text('')
@app.route('/<table:[a-zA-Z0-9].*>')
@app.route('/<db_name:[^/]+$>')
@sqlerrors
async def table(request, table):
sql = 'select * from {} limit 20'.format(table)
return await index(request, sql)
async def database(request, db_name):
name, hash, should_redirect = resolve_db_name(db_name)
if should_redirect:
return response.redirect(should_redirect)
conn = get_conn(name)
rows = conn.execute('select * from sqlite_master')
headers = [r[0] for r in rows.description]
return jinja.render(
'database.html',
request,
database=name,
database_hash=hash,
headers=headers,
rows=rows,
)
@app.route('/<db_name:[^/]+>/<table:[^/]+$>')
@sqlerrors
async def table(request, db_name, table):
# The name should have the hash - if it
# does not, serve a redirect
name, hash, should_redirect = resolve_db_name(db_name)
if should_redirect:
return response.redirect(should_redirect + '/' + table)
conn = get_conn(name)
rows = conn.execute('select * from {} limit 20'.format(table))
headers = [r[0] for r in rows.description]
return jinja.render(
'table.html',
request,
database=name,
database_hash=hash,
table=table,
headers=headers,
rows=rows,
)
def resolve_db_name(db_name):
databases = ensure_build_metadata()
hash = None
name = None
if '-' in db_name:
# Might be name-and-hash, or might just be
# a name with a hyphen in it
name, hash = db_name.rsplit('-', 1)
if name not in databases:
# Try the whole name
name = db_name
hash = None
else:
name = db_name
# Verify the hash
try:
info = databases[name]
except KeyError:
raise NotFound()
expected = info['hash'][:7]
if expected != hash:
return name, expected, '/{}-{}'.format(
name, expected,
)
return name, expected, None
if __name__ == '__main__':

Wyświetl plik

@ -0,0 +1,27 @@
<h1>{{ database }}</h1>
<style>
td {
vertical-align: top;
border-top: 1px solid #666;
padding: 2px 4px;
}
</style>
<table>
<tr>
{% for header in headers %}<th scope="col">{{ header }}</th>{% endfor %}
</tr>
{% for row in rows %}
<tr>
{% for td in row %}
<td>
{% if loop.index == 2 and row.type == "table" %}
<a href="/{{ database }}/{{ td }}">{{ td }}</a>
{% else %}
{{ td }}
{% endif %}
</td>
{% endfor %}
</tr>
{% endfor %}
</table>

Wyświetl plik

@ -1,21 +1,4 @@
<style>
td {
white-space: pre;
vertical-align: top;
border-top: 1px solid #666;
padding: 2px 4px;
}
</style>
<pre>{{ metadata }}</pre>
<table>
<tr>
{% for header in headers %}<th scope="col">{{ header }}</th>{% endfor %}
</tr>
{% for row in rows %}
<tr>
{% for td in row %}
<td>{{ td }}</td>
{% endfor %}
</tr>
{% endfor %}
</table>
<h1>Database{% if databases.keys()|length != 1 %}s{% endif %}</h1>
{% for name, info in databases.items() %}
<p><a href="{{ name}}-{{ info.hash|truncate(7, end='') }}">{{ name }}</a></p>
{% endfor %}

Wyświetl plik

@ -0,0 +1,24 @@
<h1><a href="/{{ database }}-{{ database_hash }}">{{ database }}</a></h1>
<h2>{{ table }}</h2>
<style>
td {
white-space: pre;
vertical-align: top;
border-top: 1px solid #666;
padding: 2px 4px;
}
</style>
<table>
<tr>
{% for header in headers %}<th scope="col">{{ header }}</th>{% endfor %}
</tr>
{% for row in rows %}
<tr>
{% for td in row %}
<td>{{ td }}</td>
{% endfor %}
</tr>
{% endfor %}
</table>