mirror of
https://github.com/simonw/datasette.git
synced 2025-12-10 16:51:24 +01:00
Compare commits
1 commit
main
...
new-starle
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dcfdc0c49a |
4 changed files with 127 additions and 29 deletions
|
|
@ -439,35 +439,47 @@ class Datasette:
|
||||||
self.executor, sql_operation_in_thread
|
self.executor, sql_operation_in_thread
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def asgi_app(self):
|
||||||
|
self.configure_jinja()
|
||||||
|
from starlette.routing import Router, Mount, Route
|
||||||
|
from starlette.responses import FileResponse
|
||||||
|
from starlette.staticfiles import StaticFiles
|
||||||
|
|
||||||
|
async def favicon(request):
|
||||||
|
return FileResponse(
|
||||||
|
path=str(app_root / "datasette" / "static" / "favicon.ico"),
|
||||||
|
media_type="image/x-icon"
|
||||||
|
)
|
||||||
|
return Router([
|
||||||
|
Route(
|
||||||
|
'/',
|
||||||
|
endpoint=IndexView(self).starlette_asgi_endpoint(),
|
||||||
|
methods=['GET']
|
||||||
|
),
|
||||||
|
Route(
|
||||||
|
'/favicon.ico',
|
||||||
|
favicon,
|
||||||
|
methods=['GET']
|
||||||
|
),
|
||||||
|
Mount(
|
||||||
|
'/-/static',
|
||||||
|
app=StaticFiles(directory=app_root / "datasette" / "static")
|
||||||
|
),
|
||||||
|
Route(
|
||||||
|
'/{db_name}',
|
||||||
|
endpoint=DatabaseView(self).starlette_asgi_endpoint(),
|
||||||
|
methods=['GET']
|
||||||
|
),
|
||||||
|
Route(
|
||||||
|
'/{db_name}/{table}',
|
||||||
|
endpoint=TableView(self).starlette_asgi_endpoint(),
|
||||||
|
methods=['GET']
|
||||||
|
),
|
||||||
|
])
|
||||||
|
|
||||||
def app(self):
|
def app(self):
|
||||||
app = Sanic(__name__)
|
app = Sanic(__name__)
|
||||||
default_templates = str(app_root / "datasette" / "templates")
|
self.configure_jinja()
|
||||||
template_paths = []
|
|
||||||
if self.template_dir:
|
|
||||||
template_paths.append(self.template_dir)
|
|
||||||
template_paths.extend(
|
|
||||||
[
|
|
||||||
plugin["templates_path"]
|
|
||||||
for plugin in get_plugins(pm)
|
|
||||||
if plugin["templates_path"]
|
|
||||||
]
|
|
||||||
)
|
|
||||||
template_paths.append(default_templates)
|
|
||||||
template_loader = ChoiceLoader(
|
|
||||||
[
|
|
||||||
FileSystemLoader(template_paths),
|
|
||||||
# Support {% extends "default:table.html" %}:
|
|
||||||
PrefixLoader(
|
|
||||||
{"default": FileSystemLoader(default_templates)}, delimiter=":"
|
|
||||||
),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
self.jinja_env = Environment(loader=template_loader, autoescape=True)
|
|
||||||
self.jinja_env.filters["escape_css_string"] = escape_css_string
|
|
||||||
self.jinja_env.filters["quote_plus"] = lambda u: urllib.parse.quote_plus(u)
|
|
||||||
self.jinja_env.filters["escape_sqlite"] = escape_sqlite
|
|
||||||
self.jinja_env.filters["to_css_class"] = to_css_class
|
|
||||||
pm.hook.prepare_jinja2_environment(env=self.jinja_env)
|
|
||||||
app.add_route(IndexView.as_view(self), r"/<as_format:(\.jsono?)?$>")
|
app.add_route(IndexView.as_view(self), r"/<as_format:(\.jsono?)?$>")
|
||||||
# TODO: /favicon.ico and /-/static/ deserve far-future cache expires
|
# TODO: /favicon.ico and /-/static/ deserve far-future cache expires
|
||||||
app.add_route(favicon, "/favicon.ico")
|
app.add_route(favicon, "/favicon.ico")
|
||||||
|
|
@ -561,3 +573,33 @@ class Datasette:
|
||||||
return response.html(template.render(info), status=status)
|
return response.html(template.render(info), status=status)
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
|
||||||
|
def configure_jinja(self):
|
||||||
|
default_templates = str(app_root / "datasette" / "templates")
|
||||||
|
template_paths = []
|
||||||
|
if self.template_dir:
|
||||||
|
template_paths.append(self.template_dir)
|
||||||
|
template_paths.extend(
|
||||||
|
[
|
||||||
|
plugin["templates_path"]
|
||||||
|
for plugin in get_plugins(pm)
|
||||||
|
if plugin["templates_path"]
|
||||||
|
]
|
||||||
|
)
|
||||||
|
template_paths.append(default_templates)
|
||||||
|
template_loader = ChoiceLoader(
|
||||||
|
[
|
||||||
|
FileSystemLoader(template_paths),
|
||||||
|
# Support {% extends "default:table.html" %}:
|
||||||
|
PrefixLoader(
|
||||||
|
{"default": FileSystemLoader(default_templates)}, delimiter=":"
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
self.jinja_env = Environment(loader=template_loader, autoescape=True)
|
||||||
|
self.jinja_env.filters["escape_css_string"] = escape_css_string
|
||||||
|
self.jinja_env.filters["quote_plus"] = lambda u: urllib.parse.quote_plus(u)
|
||||||
|
self.jinja_env.filters["escape_sqlite"] = escape_sqlite
|
||||||
|
self.jinja_env.filters["to_css_class"] = to_css_class
|
||||||
|
pm.hook.prepare_jinja2_environment(env=self.jinja_env)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -256,6 +256,9 @@ def package(
|
||||||
"-h", "--host", default="127.0.0.1", help="host for server, defaults to 127.0.0.1"
|
"-h", "--host", default="127.0.0.1", help="host for server, defaults to 127.0.0.1"
|
||||||
)
|
)
|
||||||
@click.option("-p", "--port", default=8001, help="port for server, defaults to 8001")
|
@click.option("-p", "--port", default=8001, help="port for server, defaults to 8001")
|
||||||
|
@click.option(
|
||||||
|
"--asgi", is_flag=True, help="Run in ASGI mode"
|
||||||
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
"--debug", is_flag=True, help="Enable debug mode - useful for development"
|
"--debug", is_flag=True, help="Enable debug mode - useful for development"
|
||||||
)
|
)
|
||||||
|
|
@ -316,6 +319,7 @@ def serve(
|
||||||
files,
|
files,
|
||||||
host,
|
host,
|
||||||
port,
|
port,
|
||||||
|
asgi,
|
||||||
debug,
|
debug,
|
||||||
reload,
|
reload,
|
||||||
cors,
|
cors,
|
||||||
|
|
@ -373,4 +377,9 @@ def serve(
|
||||||
)
|
)
|
||||||
# Force initial hashing/table counting
|
# Force initial hashing/table counting
|
||||||
ds.inspect()
|
ds.inspect()
|
||||||
|
if asgi:
|
||||||
|
import uvicorn
|
||||||
|
app = ds.asgi_app()
|
||||||
|
uvicorn.run(app, host, port, log_level="info")
|
||||||
|
else:
|
||||||
ds.app().run(host=host, port=port, debug=debug)
|
ds.app().run(host=host, port=port, debug=debug)
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,9 @@ from datasette.utils import (
|
||||||
value_as_boolean,
|
value_as_boolean,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from starlette.endpoints import HTTPEndpoint as StarletteEndpoint
|
||||||
|
from starlette.responses import Response as StarletteResponse
|
||||||
|
|
||||||
ureg = pint.UnitRegistry()
|
ureg = pint.UnitRegistry()
|
||||||
|
|
||||||
HASH_LENGTH = 7
|
HASH_LENGTH = 7
|
||||||
|
|
@ -45,6 +48,39 @@ class DatasetteError(Exception):
|
||||||
self.messagge_is_html = messagge_is_html
|
self.messagge_is_html = messagge_is_html
|
||||||
|
|
||||||
|
|
||||||
|
class RequestWrapper:
|
||||||
|
# Implements the subset of the Sanic request that my code uses
|
||||||
|
def __init__(self, starlette_request):
|
||||||
|
self._request = starlette_request
|
||||||
|
|
||||||
|
@property
|
||||||
|
def url(self):
|
||||||
|
return self._request.url._url
|
||||||
|
|
||||||
|
@property
|
||||||
|
def path(self):
|
||||||
|
return self._request.url.path
|
||||||
|
|
||||||
|
@property
|
||||||
|
def query_string(self):
|
||||||
|
q = self._request._scope["query_string"].decode("utf8")
|
||||||
|
return q
|
||||||
|
|
||||||
|
@property
|
||||||
|
def args(self):
|
||||||
|
# Key/list-of-values
|
||||||
|
# There's probably a better way to do this:
|
||||||
|
d = {}
|
||||||
|
for key, value in self._request.query_params.items():
|
||||||
|
d.setdefault(key, []).append(value)
|
||||||
|
return d
|
||||||
|
|
||||||
|
@property
|
||||||
|
def raw_args(self):
|
||||||
|
# Flat key/first-value dictionary
|
||||||
|
return dict(self.args)
|
||||||
|
|
||||||
|
|
||||||
class RenderMixin(HTTPMethodView):
|
class RenderMixin(HTTPMethodView):
|
||||||
|
|
||||||
def _asset_urls(self, key, template, context):
|
def _asset_urls(self, key, template, context):
|
||||||
|
|
@ -73,6 +109,17 @@ class RenderMixin(HTTPMethodView):
|
||||||
else:
|
else:
|
||||||
yield {"url": url}
|
yield {"url": url}
|
||||||
|
|
||||||
|
def starlette_asgi_endpoint(self):
|
||||||
|
class App(StarletteEndpoint):
|
||||||
|
async def get(other_self, request):
|
||||||
|
#import pdb; pdb.set_trace()
|
||||||
|
sanic_response = await self.get(
|
||||||
|
RequestWrapper(request), **request.path_params,
|
||||||
|
)
|
||||||
|
# TODO: media_type, status_code, headers etc
|
||||||
|
return StarletteResponse(sanic_response.body)
|
||||||
|
return App
|
||||||
|
|
||||||
def render(self, templates, **context):
|
def render(self, templates, **context):
|
||||||
template = self.ds.jinja_env.select_template(templates)
|
template = self.ds.jinja_env.select_template(templates)
|
||||||
select_templates = [
|
select_templates = [
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ class IndexView(RenderMixin):
|
||||||
def __init__(self, datasette):
|
def __init__(self, datasette):
|
||||||
self.ds = datasette
|
self.ds = datasette
|
||||||
|
|
||||||
async def get(self, request, as_format):
|
async def get(self, request, as_format=''):
|
||||||
databases = []
|
databases = []
|
||||||
for key, info in sorted(self.ds.inspect().items()):
|
for key, info in sorted(self.ds.inspect().items()):
|
||||||
tables = [t for t in info["tables"].values() if not t["hidden"]]
|
tables = [t for t in info["tables"].values() if not t["hidden"]]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue