Skip to content

Commit

Permalink
Revert "Replace AsgiLifespan with AsgiRunOnFirstRequest, refs #1955"
Browse files Browse the repository at this point in the history
This reverts commit dc18f62.
  • Loading branch information
simonw committed Dec 15, 2022
1 parent 38d28dd commit 0b68996
Show file tree
Hide file tree
Showing 5 changed files with 50 additions and 22 deletions.
20 changes: 17 additions & 3 deletions datasette/app.py
Expand Up @@ -69,17 +69,20 @@
row_sql_params_pks,
)
from .utils.asgi import (
AsgiLifespan,
Base400,
Forbidden,
NotFound,
DatabaseNotFound,
TableNotFound,
RowNotFound,
Request,
Response,
AsgiRunOnFirstRequest,
asgi_static,
asgi_send,
asgi_send_file,
asgi_send_html,
asgi_send_json,
asgi_send_redirect,
)
from .utils.internal_db import init_internal_db, populate_schema_tables
Expand Down Expand Up @@ -1417,7 +1420,7 @@ def app(self):

async def setup_db():
# First time server starts up, calculate table counts for immutable databases
for database in self.databases.values():
for dbname, database in self.databases.items():
if not database.is_mutable:
await database.table_counts(limit=60 * 60 * 1000)

Expand All @@ -1431,7 +1434,10 @@ async def setup_db():
)
if self.setting("trace_debug"):
asgi = AsgiTracer(asgi)
asgi = AsgiRunOnFirstRequest(asgi, on_startup=[setup_db, self.invoke_startup])
asgi = AsgiLifespan(
asgi,
on_startup=setup_db,
)
for wrapper in pm.hook.asgi_wrapper(datasette=self):
asgi = wrapper(asgi)
return asgi
Expand Down Expand Up @@ -1720,34 +1726,42 @@ def _fix(self, path, avoid_path_rewrites=False):
return path

async def get(self, path, **kwargs):
await self.ds.invoke_startup()
async with httpx.AsyncClient(app=self.app) as client:
return await client.get(self._fix(path), **kwargs)

async def options(self, path, **kwargs):
await self.ds.invoke_startup()
async with httpx.AsyncClient(app=self.app) as client:
return await client.options(self._fix(path), **kwargs)

async def head(self, path, **kwargs):
await self.ds.invoke_startup()
async with httpx.AsyncClient(app=self.app) as client:
return await client.head(self._fix(path), **kwargs)

async def post(self, path, **kwargs):
await self.ds.invoke_startup()
async with httpx.AsyncClient(app=self.app) as client:
return await client.post(self._fix(path), **kwargs)

async def put(self, path, **kwargs):
await self.ds.invoke_startup()
async with httpx.AsyncClient(app=self.app) as client:
return await client.put(self._fix(path), **kwargs)

async def patch(self, path, **kwargs):
await self.ds.invoke_startup()
async with httpx.AsyncClient(app=self.app) as client:
return await client.patch(self._fix(path), **kwargs)

async def delete(self, path, **kwargs):
await self.ds.invoke_startup()
async with httpx.AsyncClient(app=self.app) as client:
return await client.delete(self._fix(path), **kwargs)

async def request(self, method, path, **kwargs):
await self.ds.invoke_startup()
avoid_path_rewrites = kwargs.pop("avoid_path_rewrites", None)
async with httpx.AsyncClient(app=self.app) as client:
return await client.request(
Expand Down
44 changes: 29 additions & 15 deletions datasette/utils/asgi.py
Expand Up @@ -156,6 +156,35 @@ def fake(cls, path_with_query_string, method="GET", scheme="http", url_vars=None
return cls(scope, None)


class AsgiLifespan:
def __init__(self, app, on_startup=None, on_shutdown=None):
self.app = app
on_startup = on_startup or []
on_shutdown = on_shutdown or []
if not isinstance(on_startup or [], list):
on_startup = [on_startup]
if not isinstance(on_shutdown or [], list):
on_shutdown = [on_shutdown]
self.on_startup = on_startup
self.on_shutdown = on_shutdown

async def __call__(self, scope, receive, send):
if scope["type"] == "lifespan":
while True:
message = await receive()
if message["type"] == "lifespan.startup":
for fn in self.on_startup:
await fn()
await send({"type": "lifespan.startup.complete"})
elif message["type"] == "lifespan.shutdown":
for fn in self.on_shutdown:
await fn()
await send({"type": "lifespan.shutdown.complete"})
return
else:
await self.app(scope, receive, send)


class AsgiStream:
def __init__(self, stream_fn, status=200, headers=None, content_type="text/plain"):
self.stream_fn = stream_fn
Expand Down Expand Up @@ -420,18 +449,3 @@ async def asgi_send(self, send):
content_type=self.content_type,
headers=self.headers,
)


class AsgiRunOnFirstRequest:
def __init__(self, asgi, on_startup):
assert isinstance(on_startup, list)
self.asgi = asgi
self.on_startup = on_startup
self._started = False

async def __call__(self, scope, receive, send):
if not self._started:
self._started = True
for hook in self.on_startup:
await hook()
return await self.asgi(scope, receive, send)
5 changes: 2 additions & 3 deletions docs/plugin_hooks.rst
Expand Up @@ -902,14 +902,13 @@ Potential use-cases:

.. note::

If you are writing :ref:`unit tests <testing_plugins>` for a plugin that uses this hook and doesn't exercise Datasette by sending
any simulated requests through it you will need to explicitly call ``await ds.invoke_startup()`` in your tests. An example:
If you are writing :ref:`unit tests <testing_plugins>` for a plugin that uses this hook you will need to explicitly call ``await ds.invoke_startup()`` in your tests. An example:

.. code-block:: python
@pytest.mark.asyncio
async def test_my_plugin():
ds = Datasette()
ds = Datasette([], metadata={})
await ds.invoke_startup()
# Rest of test goes here
Expand Down
2 changes: 1 addition & 1 deletion docs/testing_plugins.rst
Expand Up @@ -80,7 +80,7 @@ Creating a ``Datasette()`` instance like this as useful shortcut in tests, but t
This method registers any :ref:`plugin_hook_startup` or :ref:`plugin_hook_prepare_jinja2_environment` plugins that might themselves need to make async calls.

If you are using ``await datasette.client.get()`` and similar methods then you don't need to worry about this - Datasette automatically calls ``invoke_startup()`` the first time it handles a request.
If you are using ``await datasette.client.get()`` and similar methods then you don't need to worry about this - those method calls ensure that ``.invoke_startup()`` has been called for you.

.. _testing_plugins_pdb:

Expand Down
1 change: 1 addition & 0 deletions tests/test_internals_datasette_client.py
Expand Up @@ -6,6 +6,7 @@

@pytest_asyncio.fixture
async def datasette(app_client):
await app_client.ds.invoke_startup()
return app_client.ds


Expand Down

0 comments on commit 0b68996

Please sign in to comment.