Commit graph

2,399 commits

Author SHA1 Message Date
Simon Willison
d9fa2bf7ba Use rowid if no primary key available
Allows us to link to individual records even for tables that do not have a primary key.

Refs #5
2017-11-09 06:39:50 -08:00
Simon Willison
b2dee11fcd Databases now get distinct colours
A left border based on their content hash.

Closes #31
2017-11-09 06:14:40 -08:00
Simon Willison
abb591d832 Added --reload argument to 'immutabase serve' command
Uses hupper to restart the server when the code changes. Useful for development.

Depends on https://pypi.python.org/pypi/hupper
2017-11-09 06:14:26 -08:00
Simon Willison
8af7bc100c Added a MANIFEST.in
Now python setup.py bdist_wheel creates a .whl that includes the CSS.
2017-11-08 18:39:42 -08:00
Simon Willison
7e0cedae3d Now using bootstrap 4 beta
Refs #16
2017-11-05 18:49:07 -08:00
Simon Willison
9e9e961390 Fixed error in RowView 2017-11-05 18:38:06 -08:00
Simon Willison
25c241fa5a Renamed project to immutabase 2017-11-05 18:32:13 -08:00
Simon Willison
a0bb9da17f Now requires DB files to be passed as arguments
Refs #40
2017-11-05 18:24:43 -08:00
Simon Willison
186c513a61 Support parameterized SQL and block potentially harmful queries
You can now call arbitrary SQL like this:

    /flights?sql=select%20*%20from%20airports%20where%20country%20like%20:c&c=iceland

Unescaped, those querystring params look like this:

    sql = select * from airports where country like :c
    c = iceland

So SQL can be constructed with named parameters embedded in it, which will
then be read from the querystring and correctly escaped.

This means we can aggressively filter the SQL parameter for potentially
dangerous syntax. For the moment we enforce that it starts with a SELECT
statement and we ban the sequence "pragma" from it entirely.

If you need to use pragma in a query, you can use the new named parameter
mechanism.

Fixes #39
2017-11-04 19:49:18 -07:00
Simon Willison
31b21f5c5e Moved all SQLite queries to threads
SQLite operations are blocking, but we're running everything in Sanic, an
asyncio web framework, so blocking operations are bad - a long-running DB
operation could hold up the entire server.

Instead, I've moved all SQLite operations into threads. These are managed by a
concurrent.futures ThreadPoolExecutor. This means I can run up to X queries in
parallel, and I can continue to queue up additional incoming HTTP traffic
while the threadpool is busy.

Each thread is responsible for managing its own SQLite connections - one per
database. These are cached in a threadlocal.

Since we are working with immutable, read-only SQLite databases it should be
safe to share SQLite objects across threads. On this assumption I'm using the
check_same_thread=False option. Opening a database connection looks like this:

    conn = sqlite3.connect(
        'file:filename.db?immutable=1',
        uri=True,
        check_same_thread=False,
    )

The following articles were helpful in figuring this out:

* https://pymotw.com/3/asyncio/executors.html
* https://marlinux.wordpress.com/2017/05/19/python-3-6-asyncio-sqlalchemy/

Closes #45. Refs #38.
2017-11-04 19:21:44 -07:00
Simon Willison
1fc75809a6 Refactored everything into a factory function
I now call a factory function to construct the Sanic app:

    app = app_factory(files)

This allows me to pass additional arguments to it, e.g. the files to serve.

Also refactored my class-based views to accept jinja as an argument, e.g:

    app.add_route(
        TableView.as_view(jinja),
        '/<db_name:[^/]+>/<table:[^/]+?><as_json:(.jsono?)?$>'
    )
2017-11-04 19:13:44 -07:00
Simon Willison
0ac8bbce2e Default subcommand is now serve
Using click-default-group: https://github.com/click-contrib/click-default-group

Also removed requirements.txt in favour of setup.py
2017-11-04 16:53:50 -07:00
Simon Willison
edaa10587e Configured Travis CI 2017-11-04 16:47:46 -07:00
Simon Willison
be768f26d0 python setup.py test now runs the tests 2017-11-04 16:40:27 -07:00
Simon Willison
2c625e31ed Fixed bug on Row page with tables containing spaces
We were attempting to run this SQL:

    select * from "Order%20Details" where ...

On this page:

    http://0.0.0.0:8877/northwind-40d049b/Order%20Details/10250,41
2017-10-27 00:16:18 -07:00
Simon Willison
1592fd0419 Started work on cli, which also meant adding setup.py
I'm using click, and click recommends using a setup.py - so I've added one of
those. I also refactored code into a new datasite package. It's not quite
deploying to now properly at the moment though - I seem to have messed up the
path handling a bit.

Also snuck in a new template for the "Row" view.

Refs #40
2017-10-27 00:08:24 -07:00
Simon Willison
2a9799bae6 Implemented database summary on index page
Closes #41
2017-10-26 21:05:17 -07:00
Simon Willison
3cce63b598 URL to allow direct database download
It's just the database URL with .db on the end, e.g. /flights.db

Closes #19
2017-10-25 08:19:32 -07:00
Simon Willison
f1b0521810 Preserve .json through redirects 2017-10-25 08:01:22 -07:00
Simon Willison
d94d4465d7 Double quote around column names
This means filters still work even with column names that contain spaces
2017-10-25 07:47:20 -07:00
Simon Willison
e55bc3b2fa th align left for all tables 2017-10-25 07:46:38 -07:00
Simon Willison
1c5977961f Added glob and like lookups - refs #23 2017-10-24 18:53:01 -07:00
Simon Willison
630b40038e Added support for gt, gte, lt, lte lookups
Refs #23
2017-10-24 18:46:55 -07:00
Simon Willison
6823b09406 Set time limit of 1000ms on SQL queries
Using the (undocumented in the Python docs) fact that if you return 1 from a
set_progress_handler callback, SQLite will cancel the current query.

Closes #35
2017-10-24 18:34:54 -07:00
Simon Willison
2fe94641b0 Don't try to show row counts for views
Also handle tables/views with spaces in their name in the URL.
2017-10-24 18:33:12 -07:00
Simon Willison
bd5f3b2ba1 Show time taken at bottom of table page 2017-10-24 18:31:54 -07:00
Simon Willison
3eb79e1a5f Show total row count at top of table page 2017-10-24 18:31:43 -07:00
Simon Willison
eef213ab4d Show total number of rows in table 2017-10-24 17:11:36 -07:00
Simon Willison
1ae8ea0f03 Started implementing ?name__contains=X filters
So far we support __contains=, __startswith=, __endswith= and __exact=

Refs #23
2017-10-24 17:06:23 -07:00
Simon Willison
4c7379a898 Don't crash on weird character encodings
Expecting SQLite columns to all be valid utf8 doesn't work, because we are
deailing with all kinds of databases. Instead, we now use the 'replace'
encoding mode to replace any non-UTF8 characters with a [X] character.
2017-10-24 17:01:34 -07:00
Simon Willison
c371f06fde Include took_ms in JSON output 2017-10-24 16:55:53 -07:00
Simon Willison
2a09d37087 Allow views to be browsed as well as tables 2017-10-24 16:55:08 -07:00
Simon Willison
02b1814fcf Redirects now preserve query string
Fixes #28
2017-10-24 16:54:26 -07:00
Simon Willison
5f806880c9 Correctly JSON serialize sqlite3.Cursor 2017-10-24 16:53:21 -07:00
Simon Willison
bc9379aabc Added .jsono extension
Returns JSON key/value objects for each row instead of lists of values.

Closes #6
2017-10-24 08:07:52 -07:00
Simon Willison
f643f7aee1 base64 encode bytestrings from DB in JSON
Fixes #29
2017-10-24 07:58:41 -07:00
Simon Willison
b46e370ee6 Link to pages-per-row
Closes #1
2017-10-24 07:10:58 -07:00
Simon Willison
6a9fdcc071 Added addressable page per row
Refs #1 - only exists for tables with introspectable primary keys.

Still need to link to this page.

Also added first unit tests - refs #9
2017-10-23 22:54:58 -07:00
Simon Willison
606ff9e35e python app.py --build to generate build-metadata.json
This is now run by the Dockerfile to build this at compile time.
2017-10-23 22:53:13 -07:00
Simon Willison
b20d7119e4 Implemented template inheritance and brought back errors 2017-10-23 19:56:27 -07:00
Simon Willison
0fa1772697 Allow ?sql= argument against database
e.g. /database-234324?sql=select * from table limit 1
2017-10-23 19:48:56 -07:00
Simon Willison
255e2611e5 CORS headers for JSON responses
Access-Control-Allow-Origin: *
2017-10-23 19:48:06 -07:00
Simon Willison
12f7e1dc56 Hashed URLs now have far-future cache expiry
Since the URL now includes a hash of the database, we can return a Cache-
Control: max-age=31536000 header for every response.

The exception is our 302 redirects. These we now serve with a Link: header
that tells any HTTP/2 server-push aware fronting proxies (such as Cloudfront)
to push the target of the redirect.

Closes #4
2017-10-23 19:36:44 -07:00
Simon Willison
9d21914069 Refactored to use class based views
Closes #22
2017-10-23 19:25:48 -07:00
Simon Willison
b2372605d6 Implemented multi-db support plus initial URL structure
Refs #24

Fixes #15
2017-10-23 19:00:37 -07:00
Simon Willison
6a0c5de615 ensure_build_metadata() function for metadata
This will be run at compile time - the goal is to generate a build-
metadata.json file with a bunch of useful facts about the databases that could
be expensive to generate at run-time.

Example metadata:

    {
        "flights": {
        "file": "flights.db",
        "tables": {
            "airlines": 6048,
            "airports": 8107,
            "routes": 67663
        },
        "hash": "07d1283e07786b1235bb7041ea445ae103d1571565580a29eab0203c555725fd"
    }

So far we have a sha256 hash of the database file itself, plus a row count for
each table.

Fixes #11
2017-10-23 09:02:40 -07:00
Simon Willison
f571b19d8a sqlerrors() decorator catching and returning useful errors
Closes #8
2017-10-23 08:28:00 -07:00
Simon Willison
de04d7a854 Initial working proof of concept 2017-10-22 17:41:19 -07:00
Simon Willison
ac9d66817d Initial commit 2017-10-22 17:39:03 -07:00