mirror of
https://github.com/simonw/datasette.git
synced 2025-12-10 16:51:24 +01:00
parent
4e47a2d894
commit
711767bcd3
3 changed files with 72 additions and 24 deletions
|
|
@ -60,6 +60,7 @@ from .utils import (
|
||||||
module_from_path,
|
module_from_path,
|
||||||
parse_metadata,
|
parse_metadata,
|
||||||
resolve_env_secrets,
|
resolve_env_secrets,
|
||||||
|
resolve_routes,
|
||||||
to_css_class,
|
to_css_class,
|
||||||
)
|
)
|
||||||
from .utils.asgi import (
|
from .utils.asgi import (
|
||||||
|
|
@ -974,8 +975,7 @@ class Datasette:
|
||||||
output.append(script)
|
output.append(script)
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def app(self):
|
def _routes(self):
|
||||||
"""Returns an ASGI app function that serves the whole of Datasette"""
|
|
||||||
routes = []
|
routes = []
|
||||||
|
|
||||||
for routes_to_add in pm.hook.register_routes(datasette=self):
|
for routes_to_add in pm.hook.register_routes(datasette=self):
|
||||||
|
|
@ -1099,6 +1099,15 @@ class Datasette:
|
||||||
+ renderer_regex
|
+ renderer_regex
|
||||||
+ r")?$",
|
+ r")?$",
|
||||||
)
|
)
|
||||||
|
return [
|
||||||
|
# Compile any strings to regular expressions
|
||||||
|
((re.compile(pattern) if isinstance(pattern, str) else pattern), view)
|
||||||
|
for pattern, view in routes
|
||||||
|
]
|
||||||
|
|
||||||
|
def app(self):
|
||||||
|
"""Returns an ASGI app function that serves the whole of Datasette"""
|
||||||
|
routes = self._routes()
|
||||||
self._register_custom_units()
|
self._register_custom_units()
|
||||||
|
|
||||||
async def setup_db():
|
async def setup_db():
|
||||||
|
|
@ -1129,12 +1138,7 @@ class Datasette:
|
||||||
class DatasetteRouter:
|
class DatasetteRouter:
|
||||||
def __init__(self, datasette, routes):
|
def __init__(self, datasette, routes):
|
||||||
self.ds = datasette
|
self.ds = datasette
|
||||||
routes = routes or []
|
self.routes = routes or []
|
||||||
self.routes = [
|
|
||||||
# Compile any strings to regular expressions
|
|
||||||
((re.compile(pattern) if isinstance(pattern, str) else pattern), view)
|
|
||||||
for pattern, view in routes
|
|
||||||
]
|
|
||||||
# Build a list of pages/blah/{name}.html matching expressions
|
# Build a list of pages/blah/{name}.html matching expressions
|
||||||
pattern_templates = [
|
pattern_templates = [
|
||||||
filepath
|
filepath
|
||||||
|
|
@ -1187,22 +1191,24 @@ class DatasetteRouter:
|
||||||
break
|
break
|
||||||
scope_modifications["actor"] = actor or default_actor
|
scope_modifications["actor"] = actor or default_actor
|
||||||
scope = dict(scope, **scope_modifications)
|
scope = dict(scope, **scope_modifications)
|
||||||
for regex, view in self.routes:
|
|
||||||
match = regex.match(path)
|
match, view = resolve_routes(self.routes, path)
|
||||||
if match is not None:
|
|
||||||
new_scope = dict(scope, url_route={"kwargs": match.groupdict()})
|
if match is None:
|
||||||
request.scope = new_scope
|
return await self.handle_404(request, send)
|
||||||
try:
|
|
||||||
response = await view(request, send)
|
new_scope = dict(scope, url_route={"kwargs": match.groupdict()})
|
||||||
if response:
|
request.scope = new_scope
|
||||||
self.ds._write_messages_to_response(request, response)
|
try:
|
||||||
await response.asgi_send(send)
|
response = await view(request, send)
|
||||||
return
|
if response:
|
||||||
except NotFound as exception:
|
self.ds._write_messages_to_response(request, response)
|
||||||
return await self.handle_404(request, send, exception)
|
await response.asgi_send(send)
|
||||||
except Exception as exception:
|
return
|
||||||
return await self.handle_500(request, send, exception)
|
except NotFound as exception:
|
||||||
return await self.handle_404(request, send)
|
return await self.handle_404(request, send, exception)
|
||||||
|
except Exception as exception:
|
||||||
|
return await self.handle_500(request, send, exception)
|
||||||
|
|
||||||
async def handle_404(self, request, send, exception=None):
|
async def handle_404(self, request, send, exception=None):
|
||||||
# If path contains % encoding, redirect to tilde encoding
|
# If path contains % encoding, redirect to tilde encoding
|
||||||
|
|
|
||||||
|
|
@ -1178,3 +1178,11 @@ def tilde_decode(s: str) -> str:
|
||||||
s = s.replace("%", temp)
|
s = s.replace("%", temp)
|
||||||
decoded = urllib.parse.unquote(s.replace("~", "%"))
|
decoded = urllib.parse.unquote(s.replace("~", "%"))
|
||||||
return decoded.replace(temp, "%")
|
return decoded.replace(temp, "%")
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_routes(routes, path):
|
||||||
|
for regex, view in routes:
|
||||||
|
match = regex.match(path)
|
||||||
|
if match is not None:
|
||||||
|
return match, view
|
||||||
|
return None, None
|
||||||
|
|
|
||||||
34
tests/test_routes.py
Normal file
34
tests/test_routes.py
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
from datasette.app import Datasette
|
||||||
|
from datasette.utils import resolve_routes
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def routes():
|
||||||
|
ds = Datasette()
|
||||||
|
return ds._routes()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"path,expected",
|
||||||
|
(
|
||||||
|
("/", "IndexView"),
|
||||||
|
("/foo", "DatabaseView"),
|
||||||
|
("/foo.csv", "DatabaseView"),
|
||||||
|
("/foo.json", "DatabaseView"),
|
||||||
|
("/foo.humbug", "DatabaseView"),
|
||||||
|
("/foo/humbug", "TableView"),
|
||||||
|
("/foo/humbug.json", "TableView"),
|
||||||
|
("/foo/humbug.blah", "TableView"),
|
||||||
|
("/foo/humbug/1", "RowView"),
|
||||||
|
("/foo/humbug/1.json", "RowView"),
|
||||||
|
("/-/metadata.json", "JsonDataView"),
|
||||||
|
("/-/metadata", "JsonDataView"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_routes(routes, path, expected):
|
||||||
|
match, view = resolve_routes(routes, path)
|
||||||
|
if expected is None:
|
||||||
|
assert match is None
|
||||||
|
else:
|
||||||
|
assert view.view_class.__name__ == expected
|
||||||
Loading…
Add table
Add a link
Reference in a new issue