From 4e27f0b33c5070379a4315a04d5bb9ef4a10fac1 Mon Sep 17 00:00:00 2001 From: Daniel Roy Greenfeld Date: Thu, 13 Mar 2025 15:12:39 +1100 Subject: [PATCH 01/12] Correct background tasks when used by FtResponse Co-Authored-By: Audrey M. Roy Greenfeld --- fasthtml/core.py | 5 ++-- nbs/api/00_core.ipynb | 53 ++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 53 insertions(+), 5 deletions(-) diff --git a/fasthtml/core.py b/fasthtml/core.py index f734d97d..1fbe9682 100644 --- a/fasthtml/core.py +++ b/fasthtml/core.py @@ -775,12 +775,13 @@ async def __call__(self, scope, receive, send) -> None: # %% ../nbs/api/00_core.ipynb class FtResponse: "Wrap an FT response with any Starlette `Response`" - def __init__(self, content, status_code:int=200, headers=None, cls=HTMLResponse, media_type:str|None=None): + def __init__(self, content, status_code:int=200, headers=None, cls=HTMLResponse, media_type:str|None=None, background: BackgroundTask | None = None): self.content,self.status_code,self.headers = content,status_code,headers - self.cls,self.media_type = cls,media_type + self.cls,self.media_type,self.background = cls,media_type,background def __response__(self, req): cts,httphdrs,tasks = _xt_cts(req, self.content) + tasks = getattr(self, 'background', tasks) headers = {**(self.headers or {}), **httphdrs} return self.cls(cts, status_code=self.status_code, headers=headers, media_type=self.media_type, background=tasks) diff --git a/nbs/api/00_core.ipynb b/nbs/api/00_core.ipynb index 217039cf..b01de965 100644 --- a/nbs/api/00_core.ipynb +++ b/nbs/api/00_core.ipynb @@ -81,6 +81,7 @@ "from IPython import display\n", "from enum import Enum\n", "from pprint import pprint\n", + "from asyncio import sleep\n", "\n", "from fastcore.test import *\n", "from starlette.testclient import TestClient\n", @@ -1231,7 +1232,7 @@ { "data": { "text/plain": [ - "'cc87253c-bfc1-4544-bbc0-58dd8d3291bc'" + "'a2597a39-e99d-4460-90b9-2ca21e87bc8f'" ] }, "execution_count": null, @@ -3236,12 +3237,13 @@ "#| export\n", "class FtResponse:\n", " \"Wrap an FT response with any Starlette `Response`\"\n", - " def __init__(self, content, status_code:int=200, headers=None, cls=HTMLResponse, media_type:str|None=None):\n", + " def __init__(self, content, status_code:int=200, headers=None, cls=HTMLResponse, media_type:str|None=None, background: BackgroundTask | None = None):\n", " self.content,self.status_code,self.headers = content,status_code,headers\n", - " self.cls,self.media_type = cls,media_type\n", + " self.cls,self.media_type,self.background = cls,media_type,background\n", "\n", " def __response__(self, req):\n", " cts,httphdrs,tasks = _xt_cts(req, self.content)\n", + " tasks = getattr(self, 'background', tasks)\n", " headers = {**(self.headers or {}), **httphdrs}\n", " return self.cls(cts, status_code=self.status_code, headers=headers, media_type=self.media_type, background=tasks)" ] @@ -3266,6 +3268,51 @@ "assert 'Foo' in txt and '

bar

' in txt and '' in txt" ] }, + { + "cell_type": "markdown", + "id": "4fc65545", + "metadata": {}, + "source": [ + "The cell below, demonstrating a BackgroundTask, should take 3 seconds to run. Need to come up with a better way to test it." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4ea66093", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0\n", + "1\n", + "2\n" + ] + } + ], + "source": [ + "async def counter(loops):\n", + " for i in range(loops):\n", + " print(i)\n", + " await sleep(i)\n", + "\n", + "timestamp = datetime.now()\n", + "\n", + "@rt('/ftr-background')\n", + "def get():\n", + " cts = Title(timestamp)\n", + " task = BackgroundTask(counter, loops=3)\n", + " return FtResponse(cts, background=task)\n", + "\n", + "r = cli.get('/ftr-background')\n", + "\n", + "test_eq(r.status_code, 200)\n", + "txt = r.text\n", + "assert f'{timestamp}' in txt\n" + ] + }, { "cell_type": "code", "execution_count": null, From 5a343d15c4856e3461fc2ad9719d64e07a74a683 Mon Sep 17 00:00:00 2001 From: Daniel Roy Greenfeld Date: Thu, 13 Mar 2025 21:19:42 +1000 Subject: [PATCH 02/12] Tests on background task Inspired by Starlette's tests for background tasks Co-Authored-By: Audrey M. Roy Greenfeld --- nbs/api/00_core.ipynb | 70 +++++++++++++++++++++++++++++++------------ 1 file changed, 51 insertions(+), 19 deletions(-) diff --git a/nbs/api/00_core.ipynb b/nbs/api/00_core.ipynb index b01de965..184ccdbb 100644 --- a/nbs/api/00_core.ipynb +++ b/nbs/api/00_core.ipynb @@ -3270,10 +3270,10 @@ }, { "cell_type": "markdown", - "id": "4fc65545", + "id": "3f506103", "metadata": {}, "source": [ - "The cell below, demonstrating a BackgroundTask, should take 3 seconds to run. Need to come up with a better way to test it." + "Test on a single background task" ] }, { @@ -3281,36 +3281,68 @@ "execution_count": null, "id": "4ea66093", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "0\n", - "1\n", - "2\n" - ] - } - ], + "outputs": [], "source": [ - "async def counter(loops):\n", - " for i in range(loops):\n", - " print(i)\n", - " await sleep(i)\n", + "TASK_COMPLETE = False\n", + "\n", + "async def async_task():\n", + " await sleep(2) # Show runs in background\n", + " global TASK_COMPLETE\n", + " TASK_COMPLETE = True\n", "\n", "timestamp = datetime.now()\n", "\n", "@rt('/ftr-background')\n", "def get():\n", " cts = Title(timestamp)\n", - " task = BackgroundTask(counter, loops=3)\n", + " task = BackgroundTask(async_task)\n", " return FtResponse(cts, background=task)\n", "\n", "r = cli.get('/ftr-background')\n", "\n", "test_eq(r.status_code, 200)\n", "txt = r.text\n", - "assert f'{timestamp}' in txt\n" + "assert f'{timestamp}' in txt\n", + "assert TASK_COMPLETE\n" + ] + }, + { + "cell_type": "markdown", + "id": "881a88bb", + "metadata": {}, + "source": [ + "Test one multiple tasks" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1764a6aa", + "metadata": {}, + "outputs": [], + "source": [ + "TASK_COUNTER = 0\n", + "\n", + "async def increment(amount):\n", + " await sleep(1) # Show runs in background\n", + " global TASK_COUNTER\n", + " TASK_COUNTER += amount\n", + "\n", + "timestamp = datetime.now()\n", + "\n", + "@rt('/ftr-background-mult')\n", + "def get():\n", + " cts = Title(timestamp)\n", + " tasks = BackgroundTasks()\n", + " for i in range(3): tasks.add_task(increment, i)\n", + " return FtResponse(cts, background=tasks)\n", + "\n", + "r = cli.get('/ftr-background-mult')\n", + "\n", + "test_eq(r.status_code, 200)\n", + "txt = r.text\n", + "assert f'{timestamp}' in txt\n", + "assert TASK_COUNTER == 3\n" ] }, { From 320317a0bef519bf67ec13f66c00e5f71cf278f1 Mon Sep 17 00:00:00 2001 From: Daniel Roy Greenfeld Date: Fri, 14 Mar 2025 16:16:01 +1000 Subject: [PATCH 03/12] First pass at explains page for background tasks Co-authored-by: Audrey Roy Greenfeld --- nbs/explains/background_tasks.ipynb | 99 +++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 nbs/explains/background_tasks.ipynb diff --git a/nbs/explains/background_tasks.ipynb b/nbs/explains/background_tasks.ipynb new file mode 100644 index 00000000..715a939c --- /dev/null +++ b/nbs/explains/background_tasks.ipynb @@ -0,0 +1,99 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Background Tasks\n", + "\n", + "> Background tasks are functions run after returning a response." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Useful for operations where you don't want or need the user waiting for it to finish. Typical scenarios include:\n", + "\n", + "- User setup in complex systems where you can inform the user later in email that their account is complete\n", + "- Batch processes that take a significant amount of time where you don't want to keep an HTTP connection alive\n", + "- Queries to slow LLMs" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Background tasks are an easy-to-use wrapper over Python's asyncio library. They are used to improve user experience, often making apps feel faster to the end user." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## A Simple Background Task Example" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Bekow we demonstrate attaching a task to FtResponse by assigning it via the background argument. When the page is visited, it will display 'Simple Background Task Example' almost instantly, while in the terminal it will slowly count upward from 0." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "``` {.python filename=\"main.py\" code-line-numbers=\"true\"}\n", + "from fasthtml.common import *\n", + "from starlette.background import BackgroundTask\n", + "from time import time, sleep\n", + "\n", + "app, rt = fast_app()\n", + "\n", + "def counter(loops): # <1>\n", + " \"\"\"Slowly print integers to the terminal\"\"\"\n", + " for i in range(loops):\n", + " print(i)\n", + " sleep(i)\n", + "\n", + "@rt\n", + "def index():\n", + " task = BackgroundTask(counter, loops=5) # <2>\n", + " return FtResponse(Titled('Simple Background Task Example'), background=task) # <3>\n", + "\n", + "serve()\n", + "```\n", + "\n", + "1. `counter` is our task function. There is nothing special about it, although it is a good practice for its arguments to be serializable as JSON\n", + "2. We use `starlette.background.BackgroundTask` to turn the counter function into a background task\n", + "3. `FtResponse` is called explicitly so we can attack the task to its background. Normally we don't need to call `FtResponse` explicitly, setting background tasks is the exception." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "::: {.callout-caution}\n", + "## Background tasks are not distributed task queues\n", + "\n", + "While background tasks often provides the user with a faster experience, the server itself isn't accelerated. What that means is that processes are still happening, but are hidden. So if a server is struggling under the load of a lot of user activity, so long as it isn't an issue with HTTP, background tasks won't help with server load.\n", + "\n", + "This is where full-fledged distributed task queue libraries like Celery and Dramatiq come into play. At the cost of dramatically increased complexity over background tasks they allow for the distribution of tasks over addition servers, as well as providing improved observability, retry mechanisms, and persistence in case of server shutdown.\n", + "\n", + "In our experience, it's often better to build something with background tasks and then convert it to a task queue.\n", + ":::" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "python3", + "language": "python", + "name": "python3" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} From 35ef1669bb71c651d07d8fa28ec61efb1f32692b Mon Sep 17 00:00:00 2001 From: Daniel Roy Greenfeld Date: Sat, 15 Mar 2025 03:14:08 +1000 Subject: [PATCH 04/12] Nicer code example for background tasks --- nbs/explains/background_tasks.ipynb | 167 ++++++++++++++++++++++++++-- 1 file changed, 158 insertions(+), 9 deletions(-) diff --git a/nbs/explains/background_tasks.ipynb b/nbs/explains/background_tasks.ipynb index 715a939c..77c599d6 100644 --- a/nbs/explains/background_tasks.ipynb +++ b/nbs/explains/background_tasks.ipynb @@ -13,32 +13,32 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Useful for operations where you don't want or need the user waiting for it to finish. Typical scenarios include:\n", + "Useful for operations where you want the user to get a response quickly but you don't want or need the user waiting for it to finish. Typical scenarios include:\n", "\n", "- User setup in complex systems where you can inform the user later in email that their account is complete\n", - "- Batch processes that take a significant amount of time where you don't want to keep an HTTP connection alive\n", - "- Queries to slow LLMs" + "- Batch processes that take a significant amount of time (bulk email or API calls)\n", + "- Any other process where the user can be notified later by email, websocket, webhook, or pop-up" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Background tasks are an easy-to-use wrapper over Python's asyncio library. They are used to improve user experience, often making apps feel faster to the end user." + "Background tasks are an easy-to-use wrapper over Python's concurrency mechanisms. They are used to improve user experience, often making apps feel faster to the end user." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## A Simple Background Task Example" + "## A simple background task example" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Bekow we demonstrate attaching a task to FtResponse by assigning it via the background argument. When the page is visited, it will display 'Simple Background Task Example' almost instantly, while in the terminal it will slowly count upward from 0." + "In this example attaching a task to FtResponse by assigning it via the background argument. When the page is visited, it will display 'Simple Background Task Example' almost instantly, while in the terminal it will slowly count upward from 0." ] }, { @@ -52,7 +52,7 @@ "\n", "app, rt = fast_app()\n", "\n", - "def counter(loops): # <1>\n", + "def counter(loops:int): # <1>\n", " \"\"\"Slowly print integers to the terminal\"\"\"\n", " for i in range(loops):\n", " print(i)\n", @@ -61,7 +61,8 @@ "@rt\n", "def index():\n", " task = BackgroundTask(counter, loops=5) # <2>\n", - " return FtResponse(Titled('Simple Background Task Example'), background=task) # <3>\n", + " cts = Titled('Simple Background Task Example')\n", + " return FtResponse(cts, background=task) # <3>\n", "\n", "serve()\n", "```\n", @@ -71,6 +72,142 @@ "3. `FtResponse` is called explicitly so we can attack the task to its background. Normally we don't need to call `FtResponse` explicitly, setting background tasks is the exception." ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## A more complex example" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "from ContextKit import read_url" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "text = read_url('https://fastht.ml/docs/llms-ctx.txt')" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "%%ai\n", + "Read $(text) and create a FastHTML example of accessing a database to get 1000 users and then accessing an API for them " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Based on the context provided, I'll create a FastHTML example that demonstrates accessing a database to retrieve 1000 users and then accessing an API for each of them as a background task.\n", + "\n", + "```python\n", + "from fasthtml.common import *\n", + "from starlette.background import BackgroundTask\n", + "import asyncio\n", + "import httpx\n", + "from datetime import datetime\n", + "import time\n", + "\n", + "app, rt = fast_app()\n", + "\n", + "db = database('data.db')\n", + "\n", + "class User: email: str; account_updated:int\n", + " \n", + "users = db.create(User, pk='email')\n", + "users\n", + "\n", + "\n", + "def process_all_users():\n", + " \"\"\"Process all users through the API\"\"\"\n", + " start_time = time.time()\n", + "\n", + " for user in users():\n", + " httpx.post(f'http://example.com/update-user/{user.email}')\n", + " \n", + "\n", + "@rt\n", + "def index():\n", + " \"\"\"Main page with form to trigger the background task\"\"\"\n", + " return Titled(\"User API Processing\", \n", + " [\n", + " P(\"Click the button to retrieve 1000 users from the database and process them through an API.\"),\n", + " P(\"This operation will run in the background and you'll get an immediate response.\"),\n", + " Form(action=\"/process\", method=\"post\", \n", + " contents=[\n", + " Submit(\"Start Processing\")\n", + " ])\n", + " ])\n", + "\n", + "@rt.post\n", + "def process():\n", + " # Initialize the database if needed\n", + " setup_db()\n", + " \n", + " # Get users from the database\n", + " users = get_users(1000)\n", + " \n", + " # Create the background task\n", + " task = BackgroundTask(process_all_users, users=users)\n", + " \n", + " # Return immediate response while processing continues in background\n", + " return FtResponse(Titled(\"Processing Started\", \n", + " [\n", + " P(f\"Started processing {len(users)} users in the background.\"),\n", + " P(\"You can check the server logs for progress updates.\"),\n", + " P(\"Results will be saved to api_results.txt when complete.\"),\n", + " A(\"Back to Home\", href=\"/\")\n", + " ]), \n", + " background=task)\n", + "\n", + "@rt\n", + "def results():\n", + " \"\"\"View the results if available\"\"\"\n", + " try:\n", + " with open('api_results.txt', 'r') as f:\n", + " results = f.readlines()\n", + " \n", + " return Titled(\"API Processing Results\",\n", + " [\n", + " P(f\"Found {len(results)} processed users.\"),\n", + " Pre(\"\\n\".join(results[:20]) + \"\\n...\" if len(results) > 20 else \"\\n\".join(results)),\n", + " A(\"Back to Home\", href=\"/\")\n", + " ])\n", + " except FileNotFoundError:\n", + " return Titled(\"Results Not Available\",\n", + " [\n", + " P(\"The API processing hasn't completed yet or hasn't been started.\"),\n", + " A(\"Back to Home\", href=\"/\")\n", + " ])\n", + "\n", + "serve()\n", + "```\n", + "\n", + "This example:\n", + "\n", + "1. Creates a simulated SQLite database with 1000 users\n", + "2. Provides a simple web interface to trigger the background task\n", + "3. Uses `BackgroundTask` to process all users through a simulated API\n", + "4. Processes users in batches asynchronously to improve performance\n", + "5. Returns an immediate response to the user while processing continues\n", + "6. Logs progress to the terminal and saves results to a file\n", + "\n", + "The key part is the `/process` route where we create the background task and attach it to the `FtResponse`. This allows the server to return a response immediately while continuing to process the API calls in the background." + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -89,9 +226,21 @@ ], "metadata": { "kernelspec": { - "display_name": "python3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.6" } }, "nbformat": 4, From c78214f270dd100fbadce3b4405e10273ba9851f Mon Sep 17 00:00:00 2001 From: Daniel Roy Greenfeld Date: Mon, 17 Mar 2025 12:59:56 +1000 Subject: [PATCH 05/12] Add more realistic background task example Co-authored-by: Audrey Roy Greenfeld --- nbs/explains/background_tasks.ipynb | 157 ++++++++++++---------------- 1 file changed, 66 insertions(+), 91 deletions(-) diff --git a/nbs/explains/background_tasks.ipynb b/nbs/explains/background_tasks.ipynb index 77c599d6..6aaf8c27 100644 --- a/nbs/explains/background_tasks.ipynb +++ b/nbs/explains/background_tasks.ipynb @@ -16,7 +16,7 @@ "Useful for operations where you want the user to get a response quickly but you don't want or need the user waiting for it to finish. Typical scenarios include:\n", "\n", "- User setup in complex systems where you can inform the user later in email that their account is complete\n", - "- Batch processes that take a significant amount of time (bulk email or API calls)\n", + "- Batch processes that can take a significant amount of time (bulk email or API calls)\n", "- Any other process where the user can be notified later by email, websocket, webhook, or pop-up" ] }, @@ -76,136 +76,111 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## A more complex example" + "## A more realistic example" ] }, { - "cell_type": "code", - "execution_count": 3, + "cell_type": "markdown", "metadata": {}, - "outputs": [], "source": [ - "from ContextKit import read_url" + "Let's imagine that we are accessing a slow-to-process critical service. We don't want our users to have to wait. While we could set up SSE to notify on completion, instead we decide to periodically check to see if the status of their record has changed.\n", + "\n", + "So first, let's create a very simple slow timestamp API. What it does is stall requests for 10 seconds before returning JSON containing a timestamp." ] }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "text = read_url('https://fastht.ml/docs/llms-ctx.txt')" + "from fasthtml.common import *\n", + "from time import sleep, time\n", + "\n", + "app, rt = fast_app()\n", + "\n", + "@rt\n", + "def index():\n", + " sleep(10)\n", + " return {'timestamp': time()}\n", + "\n", + "serve(port=8123)" ] }, { - "cell_type": "code", - "execution_count": 5, + "cell_type": "markdown", "metadata": {}, - "outputs": [], "source": [ - "%%ai\n", - "Read $(text) and create a FastHTML example of accessing a database to get 1000 users and then accessing an API for them " + "Now let's create a website that uses this API to fetch the timestamp from the glacially slow service." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Based on the context provided, I'll create a FastHTML example that demonstrates accessing a database to retrieve 1000 users and then accessing an API for each of them as a background task.\n", - "\n", "```python\n", "from fasthtml.common import *\n", + "from fastlite import *\n", "from starlette.background import BackgroundTask\n", - "import asyncio\n", + "from time import time, sleep\n", "import httpx\n", - "from datetime import datetime\n", - "import time\n", "\n", "app, rt = fast_app()\n", "\n", "db = database('data.db')\n", "\n", - "class User: email: str; account_updated:int\n", - " \n", - "users = db.create(User, pk='email')\n", - "users\n", + "class Moment: timestamp: float\n", + "moments = db.create(Moment, pk='timestamp')\n", "\n", "\n", - "def process_all_users():\n", - " \"\"\"Process all users through the API\"\"\"\n", - " start_time = time.time()\n", + "async def submit_record():\n", + " client = httpx.AsyncClient()\n", + " r = await client.post('http://127.0.0.1:8123', timeout=20)\n", + " moments.insert(timestamp=r.json()['timestamp'])\n", "\n", - " for user in users():\n", - " httpx.post(f'http://example.com/update-user/{user.email}')\n", " \n", + "@rt('/submit-record')\n", + "async def post():\n", + " cts = H3(f'Record submitted at {time()}, click again', hx_post='submit-record')\n", + " task = BackgroundTask(submit_record)\n", + " return FtResponse(cts, background=task)\n", "\n", "@rt\n", "def index():\n", - " \"\"\"Main page with form to trigger the background task\"\"\"\n", - " return Titled(\"User API Processing\", \n", - " [\n", - " P(\"Click the button to retrieve 1000 users from the database and process them through an API.\"),\n", - " P(\"This operation will run in the background and you'll get an immediate response.\"),\n", - " Form(action=\"/process\", method=\"post\", \n", - " contents=[\n", - " Submit(\"Start Processing\")\n", - " ])\n", - " ])\n", - "\n", - "@rt.post\n", - "def process():\n", - " # Initialize the database if needed\n", - " setup_db()\n", - " \n", - " # Get users from the database\n", - " users = get_users(1000)\n", - " \n", - " # Create the background task\n", - " task = BackgroundTask(process_all_users, users=users)\n", - " \n", - " # Return immediate response while processing continues in background\n", - " return FtResponse(Titled(\"Processing Started\", \n", - " [\n", - " P(f\"Started processing {len(users)} users in the background.\"),\n", - " P(\"You can check the server logs for progress updates.\"),\n", - " P(\"Results will be saved to api_results.txt when complete.\"),\n", - " A(\"Back to Home\", href=\"/\")\n", - " ]), \n", - " background=task)\n", - "\n", - "@rt\n", - "def results():\n", - " \"\"\"View the results if available\"\"\"\n", - " try:\n", - " with open('api_results.txt', 'r') as f:\n", - " results = f.readlines()\n", - " \n", - " return Titled(\"API Processing Results\",\n", - " [\n", - " P(f\"Found {len(results)} processed users.\"),\n", - " Pre(\"\\n\".join(results[:20]) + \"\\n...\" if len(results) > 20 else \"\\n\".join(results)),\n", - " A(\"Back to Home\", href=\"/\")\n", - " ])\n", - " except FileNotFoundError:\n", - " return Titled(\"Results Not Available\",\n", - " [\n", - " P(\"The API processing hasn't completed yet or hasn't been started.\"),\n", - " A(\"Back to Home\", href=\"/\")\n", - " ])\n", + " timestamps = moments(order_by='timestamp')\n", + " return Titled('Slow access dashboard',\n", + " H2('Click here to ping slow service', hx_post='/submit-record'),\n", + " H3('Refresh the page to check the latest timestamp'), \n", + " P(timestamps),\n", + " )\n", "\n", "serve()\n", - "```\n", - "\n", - "This example:\n", - "\n", - "1. Creates a simulated SQLite database with 1000 users\n", - "2. Provides a simple web interface to trigger the background task\n", - "3. Uses `BackgroundTask` to process all users through a simulated API\n", - "4. Processes users in batches asynchronously to improve performance\n", - "5. Returns an immediate response to the user while processing continues\n", - "6. Logs progress to the terminal and saves results to a file\n", - "\n", - "The key part is the `/process` route where we create the background task and attach it to the `FtResponse`. This allows the server to return a response immediately while continuing to process the API calls in the background." + "```" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "ename": "KeyError", + "evalue": "'nbmeta'", + "output_type": "error", + "traceback": [ + "\u001b[31m---------------------------------------------------------------------------\u001b[39m", + "\u001b[31mKeyError\u001b[39m Traceback (most recent call last)", + "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[2]\u001b[39m\u001b[32m, line 1\u001b[39m\n\u001b[32m----> \u001b[39m\u001b[32m1\u001b[39m \u001b[43mget_ipython\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\u001b[43m.\u001b[49m\u001b[43mrun_cell_magic\u001b[49m\u001b[43m(\u001b[49m\u001b[33;43m'\u001b[39;49m\u001b[33;43mai\u001b[39;49m\u001b[33;43m'\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[33;43m'\u001b[39;49m\u001b[33;43m'\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[33;43m'\u001b[39;49m\u001b[33;43mHow to use HTMX to ping a route every 5 seconds\u001b[39;49m\u001b[38;5;130;43;01m\\n\u001b[39;49;00m\u001b[33;43m'\u001b[39;49m\u001b[43m)\u001b[49m\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/.venv/lib/python3.12/site-packages/IPython/core/interactiveshell.py:2542\u001b[39m, in \u001b[36mInteractiveShell.run_cell_magic\u001b[39m\u001b[34m(self, magic_name, line, cell)\u001b[39m\n\u001b[32m 2540\u001b[39m \u001b[38;5;28;01mwith\u001b[39;00m \u001b[38;5;28mself\u001b[39m.builtin_trap:\n\u001b[32m 2541\u001b[39m args = (magic_arg_s, cell)\n\u001b[32m-> \u001b[39m\u001b[32m2542\u001b[39m result = \u001b[43mfn\u001b[49m\u001b[43m(\u001b[49m\u001b[43m*\u001b[49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43m*\u001b[49m\u001b[43m*\u001b[49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 2544\u001b[39m \u001b[38;5;66;03m# The code below prevents the output from being displayed\u001b[39;00m\n\u001b[32m 2545\u001b[39m \u001b[38;5;66;03m# when using magics with decorator @output_can_be_silenced\u001b[39;00m\n\u001b[32m 2546\u001b[39m \u001b[38;5;66;03m# when the last Python token in the expression is a ';'.\u001b[39;00m\n\u001b[32m 2547\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mgetattr\u001b[39m(fn, magic.MAGIC_OUTPUT_CAN_BE_SILENCED, \u001b[38;5;28;01mFalse\u001b[39;00m):\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/git/aimagic/build/__editable__.aimagic-0.0.3-py3-none-any/aimagic/core.py:455\u001b[39m, in \u001b[36mcreate_magic..f\u001b[39m\u001b[34m(line, cell, pre)\u001b[39m\n\u001b[32m--> \u001b[39m\u001b[32m455\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34mf\u001b[39m(line, cell=\u001b[38;5;28;01mNone\u001b[39;00m, pre=\u001b[33m'\u001b[39m\u001b[33m'\u001b[39m): \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mr\u001b[49m\u001b[43m.\u001b[49m\u001b[43mcell\u001b[49m\u001b[43m(\u001b[49m\u001b[43mline\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcell\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mpre\u001b[49m\u001b[43m)\u001b[49m\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/git/aimagic/build/__editable__.aimagic-0.0.3-py3-none-any/aimagic/core.py:396\u001b[39m, in \u001b[36mAiMagic.cell\u001b[39m\u001b[34m(self, line, cell, pre, temp)\u001b[39m\n\u001b[32m 394\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m\n\u001b[32m 395\u001b[39m is_code = pre.startswith(\u001b[33m'\u001b[39m\u001b[33m```\u001b[39m\u001b[33m'\u001b[39m)\n\u001b[32m--> \u001b[39m\u001b[32m396\u001b[39m cells = get_cells(\u001b[43mns\u001b[49m\u001b[43m[\u001b[49m\u001b[33;43m'\u001b[39;49m\u001b[33;43mnbmeta\u001b[39;49m\u001b[33;43m'\u001b[39;49m\u001b[43m]\u001b[49m)\n\u001b[32m 397\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m line.startswith(\u001b[33m'\u001b[39m\u001b[33m-i\u001b[39m\u001b[33m'\u001b[39m): cells.insert(-\u001b[32m1\u001b[39m, add_url(line))\n\u001b[32m 398\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m cell: cell=\u001b[33m'\u001b[39m\u001b[33m'\u001b[39m\n", + "\u001b[31mKeyError\u001b[39m: 'nbmeta'" + ] + } + ], + "source": [ + "%%ai\n", + "How to use HTMX to ping a route every 5 seconds" ] }, { @@ -226,7 +201,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": ".venv", "language": "python", "name": "python3" }, From 3c8ebadf6498267bc9c243ff09e8174dd99dd627 Mon Sep 17 00:00:00 2001 From: Daniel Roy Greenfeld Date: Tue, 18 Mar 2025 16:07:59 +1000 Subject: [PATCH 06/12] Use self.background as fallback --- fasthtml/core.py | 2 +- nbs/api/00_core.ipynb | 12 ++++++------ nbs/explains/background_tasks.ipynb | 16 ++-------------- 3 files changed, 9 insertions(+), 21 deletions(-) diff --git a/fasthtml/core.py b/fasthtml/core.py index 1fbe9682..50658ec5 100644 --- a/fasthtml/core.py +++ b/fasthtml/core.py @@ -781,7 +781,7 @@ def __init__(self, content, status_code:int=200, headers=None, cls=HTMLResponse, def __response__(self, req): cts,httphdrs,tasks = _xt_cts(req, self.content) - tasks = getattr(self, 'background', tasks) + if not tasks.tasks: tasks = self.background headers = {**(self.headers or {}), **httphdrs} return self.cls(cts, status_code=self.status_code, headers=headers, media_type=self.media_type, background=tasks) diff --git a/nbs/api/00_core.ipynb b/nbs/api/00_core.ipynb index 184ccdbb..774536a4 100644 --- a/nbs/api/00_core.ipynb +++ b/nbs/api/00_core.ipynb @@ -132,7 +132,7 @@ { "data": { "text/plain": [ - "datetime.datetime(2025, 3, 16, 14, 0)" + "datetime.datetime(2025, 3, 18, 14, 0)" ] }, "execution_count": null, @@ -2579,13 +2579,13 @@ "name": "stdout", "output_type": "stream", "text": [ - "Set to 2025-03-16 04:41:09.011712\n" + "Set to 2025-03-18 16:06:25.074690\n" ] }, { "data": { "text/plain": [ - "'Session time: 2025-03-16 04:41:09.011712'" + "'Session time: 2025-03-18 16:06:25.074690'" ] }, "execution_count": null, @@ -3106,7 +3106,7 @@ { "data": { "text/plain": [ - "'Cookie was set at time 04:41:09.620176'" + "'Cookie was set at time 16:06:25.997002'" ] }, "execution_count": null, @@ -3243,7 +3243,7 @@ "\n", " def __response__(self, req):\n", " cts,httphdrs,tasks = _xt_cts(req, self.content)\n", - " tasks = getattr(self, 'background', tasks)\n", + " if not tasks.tasks: tasks = self.background\n", " headers = {**(self.headers or {}), **httphdrs}\n", " return self.cls(cts, status_code=self.status_code, headers=headers, media_type=self.media_type, background=tasks)" ] @@ -3311,7 +3311,7 @@ "id": "881a88bb", "metadata": {}, "source": [ - "Test one multiple tasks" + "Test one multiple background tasks" ] }, { diff --git a/nbs/explains/background_tasks.ipynb b/nbs/explains/background_tasks.ipynb index 6aaf8c27..b564240e 100644 --- a/nbs/explains/background_tasks.ipynb +++ b/nbs/explains/background_tasks.ipynb @@ -160,7 +160,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "metadata": {}, "outputs": [ { @@ -201,21 +201,9 @@ ], "metadata": { "kernelspec": { - "display_name": ".venv", + "display_name": "python3", "language": "python", "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.12.6" } }, "nbformat": 4, From 9c7519b35e07d5cbee1abc759a91f73f61da6cd0 Mon Sep 17 00:00:00 2001 From: Daniel Roy Greenfeld Date: Wed, 19 Mar 2025 08:05:41 +1000 Subject: [PATCH 07/12] Finish complex example, add multiple tasks example Co-authored-by: Audrey Roy Greenfeld --- nbs/explains/background_tasks.ipynb | 139 ++++++++++++++++++---------- 1 file changed, 88 insertions(+), 51 deletions(-) diff --git a/nbs/explains/background_tasks.ipynb b/nbs/explains/background_tasks.ipynb index b564240e..8f855d07 100644 --- a/nbs/explains/background_tasks.ipynb +++ b/nbs/explains/background_tasks.ipynb @@ -85,26 +85,27 @@ "source": [ "Let's imagine that we are accessing a slow-to-process critical service. We don't want our users to have to wait. While we could set up SSE to notify on completion, instead we decide to periodically check to see if the status of their record has changed.\n", "\n", - "So first, let's create a very simple slow timestamp API. What it does is stall requests for 10 seconds before returning JSON containing a timestamp." + "First, create a very simple slow timestamp API. All it does is stall requests for a few seconds before returning JSON containing timestamps." ] }, { - "cell_type": "code", - "execution_count": null, + "cell_type": "markdown", "metadata": {}, - "outputs": [], "source": [ + "```python\n", + "# slow_api.py\n", "from fasthtml.common import *\n", "from time import sleep, time\n", "\n", "app, rt = fast_app()\n", "\n", - "@rt\n", - "def index():\n", - " sleep(10)\n", - " return {'timestamp': time()}\n", + "@rt('/slow/{ts}')\n", + "def slow(ts: int):\n", + " sleep(3)\n", + " return dict(ts=ts, response_ts=int(time()))\n", "\n", - "serve(port=8123)" + "serve(port=8123)\n", + "```" ] }, { @@ -119,68 +120,104 @@ "metadata": {}, "source": [ "```python\n", + "# main.py\n", "from fasthtml.common import *\n", "from fastlite import *\n", "from starlette.background import BackgroundTask\n", - "from time import time, sleep\n", + "import time\n", "import httpx\n", "\n", "app, rt = fast_app()\n", "\n", - "db = database('data.db')\n", + "db = database(':memory:')\n", "\n", - "class Moment: timestamp: float\n", - "moments = db.create(Moment, pk='timestamp')\n", + "class Moment: ts: int; response_ts: int\n", + "moments = db.create(Moment, pk='ts')\n", "\n", + "def task_submit(ts:int): # <1>\n", + " # This task function calls the slow service, representing a slow process that\n", + " # we've encapsulated in a function.\n", + " client = httpx.Client()\n", + " r = client.post(f'http://127.0.0.1:8123/slow/{ts}')\n", + " moments.insert(**r.json())\n", "\n", - "async def submit_record():\n", - " client = httpx.AsyncClient()\n", - " r = await client.post('http://127.0.0.1:8123', timeout=20)\n", - " moments.insert(timestamp=r.json()['timestamp'])\n", + "@rt\n", + "def submit():\n", + " ts = int(time.time())\n", + " task = BackgroundTask(task_submit, ts=ts) # <2> \n", + " cts = P(f'Submitted: {ts}')\n", + " return FtResponse(cts, background=task) # <3> \n", "\n", - " \n", - "@rt('/submit-record')\n", - "async def post():\n", - " cts = H3(f'Record submitted at {time()}, click again', hx_post='submit-record')\n", - " task = BackgroundTask(submit_record)\n", - " return FtResponse(cts, background=task)\n", + "@rt\n", + "def showmoments(): return Ul(*[Li(m) for m in moments()])\n", "\n", "@rt\n", "def index():\n", - " timestamps = moments(order_by='timestamp')\n", - " return Titled('Slow access dashboard',\n", - " H2('Click here to ping slow service', hx_post='/submit-record'),\n", - " H3('Refresh the page to check the latest timestamp'), \n", - " P(timestamps),\n", + " return Titled('Background Task Dashboard',\n", + " P(Button('Press to call slow service', # <4> \n", + " hx_post='/submit', hx_target='#res')),\n", + " P('', id='res'),\n", + " Div(Ul(*[Li(m) for m in moments()]),\n", + " hx_get=showmoments, hx_trigger='every 5s'), # <5>\n", " )\n", "\n", "serve()\n", - "```" + "```\n", + "\n", + "1. It is common, but not necessary to prefix task functions with 'task_'\n", + "2. Create a background task by passing in the function to a BackgroundTask object, followed by any arguments.\n", + "3. In FtResponse, use the background keyword argument to set the task to be run after the HTTP Response is generated.\n", + "4. When this button is pressed, the 'submit' handler will respond instantly. The task_submit function will insert the slow API response into the db later. \n", + "5. Every 5 seconds get the moments stored in the DB." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "::: {.callout-tip}\n", + "\n", + "In the example above we use a synchronous background task function set in the `FtResponse` of a synchronous handler. However, we can also use asynchronous functions and handlers.\n", + "\n", + ":::" ] }, { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "ename": "KeyError", - "evalue": "'nbmeta'", - "output_type": "error", - "traceback": [ - "\u001b[31m---------------------------------------------------------------------------\u001b[39m", - "\u001b[31mKeyError\u001b[39m Traceback (most recent call last)", - "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[2]\u001b[39m\u001b[32m, line 1\u001b[39m\n\u001b[32m----> \u001b[39m\u001b[32m1\u001b[39m \u001b[43mget_ipython\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\u001b[43m.\u001b[49m\u001b[43mrun_cell_magic\u001b[49m\u001b[43m(\u001b[49m\u001b[33;43m'\u001b[39;49m\u001b[33;43mai\u001b[39;49m\u001b[33;43m'\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[33;43m'\u001b[39;49m\u001b[33;43m'\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[33;43m'\u001b[39;49m\u001b[33;43mHow to use HTMX to ping a route every 5 seconds\u001b[39;49m\u001b[38;5;130;43;01m\\n\u001b[39;49;00m\u001b[33;43m'\u001b[39;49m\u001b[43m)\u001b[49m\n", - "\u001b[36mFile \u001b[39m\u001b[32m~/.venv/lib/python3.12/site-packages/IPython/core/interactiveshell.py:2542\u001b[39m, in \u001b[36mInteractiveShell.run_cell_magic\u001b[39m\u001b[34m(self, magic_name, line, cell)\u001b[39m\n\u001b[32m 2540\u001b[39m \u001b[38;5;28;01mwith\u001b[39;00m \u001b[38;5;28mself\u001b[39m.builtin_trap:\n\u001b[32m 2541\u001b[39m args = (magic_arg_s, cell)\n\u001b[32m-> \u001b[39m\u001b[32m2542\u001b[39m result = \u001b[43mfn\u001b[49m\u001b[43m(\u001b[49m\u001b[43m*\u001b[49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43m*\u001b[49m\u001b[43m*\u001b[49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 2544\u001b[39m \u001b[38;5;66;03m# The code below prevents the output from being displayed\u001b[39;00m\n\u001b[32m 2545\u001b[39m \u001b[38;5;66;03m# when using magics with decorator @output_can_be_silenced\u001b[39;00m\n\u001b[32m 2546\u001b[39m \u001b[38;5;66;03m# when the last Python token in the expression is a ';'.\u001b[39;00m\n\u001b[32m 2547\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mgetattr\u001b[39m(fn, magic.MAGIC_OUTPUT_CAN_BE_SILENCED, \u001b[38;5;28;01mFalse\u001b[39;00m):\n", - "\u001b[36mFile \u001b[39m\u001b[32m~/git/aimagic/build/__editable__.aimagic-0.0.3-py3-none-any/aimagic/core.py:455\u001b[39m, in \u001b[36mcreate_magic..f\u001b[39m\u001b[34m(line, cell, pre)\u001b[39m\n\u001b[32m--> \u001b[39m\u001b[32m455\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34mf\u001b[39m(line, cell=\u001b[38;5;28;01mNone\u001b[39;00m, pre=\u001b[33m'\u001b[39m\u001b[33m'\u001b[39m): \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mr\u001b[49m\u001b[43m.\u001b[49m\u001b[43mcell\u001b[49m\u001b[43m(\u001b[49m\u001b[43mline\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcell\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mpre\u001b[49m\u001b[43m)\u001b[49m\n", - "\u001b[36mFile \u001b[39m\u001b[32m~/git/aimagic/build/__editable__.aimagic-0.0.3-py3-none-any/aimagic/core.py:396\u001b[39m, in \u001b[36mAiMagic.cell\u001b[39m\u001b[34m(self, line, cell, pre, temp)\u001b[39m\n\u001b[32m 394\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m\n\u001b[32m 395\u001b[39m is_code = pre.startswith(\u001b[33m'\u001b[39m\u001b[33m```\u001b[39m\u001b[33m'\u001b[39m)\n\u001b[32m--> \u001b[39m\u001b[32m396\u001b[39m cells = get_cells(\u001b[43mns\u001b[49m\u001b[43m[\u001b[49m\u001b[33;43m'\u001b[39;49m\u001b[33;43mnbmeta\u001b[39;49m\u001b[33;43m'\u001b[39;49m\u001b[43m]\u001b[49m)\n\u001b[32m 397\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m line.startswith(\u001b[33m'\u001b[39m\u001b[33m-i\u001b[39m\u001b[33m'\u001b[39m): cells.insert(-\u001b[32m1\u001b[39m, add_url(line))\n\u001b[32m 398\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m cell: cell=\u001b[33m'\u001b[39m\u001b[33m'\u001b[39m\n", - "\u001b[31mKeyError\u001b[39m: 'nbmeta'" - ] - } - ], - "source": [ - "%%ai\n", - "How to use HTMX to ping a route every 5 seconds" + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Multiple background tasks in a handler" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "It is possible to add multiple background tasks to an FtResponse." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```python\n", + "from fasthtml.common import *\n", + "from starlette.background import BackgroundTasks\n", + "\n", + "@rt\n", + "async def signup(email, username):\n", + " tasks = BackgroundTasks()\n", + " tasks.add_task(send_welcome_email, to_address=email)\n", + " tasks.add_task(send_admin_notification, username=username)\n", + " cts = Titled('Signup successful!')\n", + " return FtResponse(cts, background=tasks)\n", + "\n", + "async def send_welcome_email(to_address):\n", + " ...\n", + "\n", + "async def send_admin_notification(username):\n", + " ...\n", + "```" ] }, { From 9c8073b6f327389835217b176c8582493b8c4cbd Mon Sep 17 00:00:00 2001 From: Daniel Roy Greenfeld Date: Wed, 19 Mar 2025 08:31:44 +1000 Subject: [PATCH 08/12] Rely on globals and not sleep functions Co-authored-by: Audrey Roy Greenfeld --- nbs/api/00_core.ipynb | 22 +++++++++++++--------- nbs/explains/background_tasks.ipynb | 21 +++++++++++++-------- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/nbs/api/00_core.ipynb b/nbs/api/00_core.ipynb index 774536a4..4eb74a32 100644 --- a/nbs/api/00_core.ipynb +++ b/nbs/api/00_core.ipynb @@ -132,7 +132,7 @@ { "data": { "text/plain": [ - "datetime.datetime(2025, 3, 18, 14, 0)" + "datetime.datetime(2025, 3, 19, 14, 0)" ] }, "execution_count": null, @@ -2579,13 +2579,13 @@ "name": "stdout", "output_type": "stream", "text": [ - "Set to 2025-03-18 16:06:25.074690\n" + "Set to 2025-03-19 08:28:55.982469\n" ] }, { "data": { "text/plain": [ - "'Session time: 2025-03-18 16:06:25.074690'" + "'Session time: 2025-03-19 08:28:55.982469'" ] }, "execution_count": null, @@ -3106,7 +3106,7 @@ { "data": { "text/plain": [ - "'Cookie was set at time 16:06:25.997002'" + "'Cookie was set at time 08:28:56.864757'" ] }, "execution_count": null, @@ -3273,7 +3273,7 @@ "id": "3f506103", "metadata": {}, "source": [ - "Test on a single background task" + "Test on a single background task:" ] }, { @@ -3286,7 +3286,6 @@ "TASK_COMPLETE = False\n", "\n", "async def async_task():\n", - " await sleep(2) # Show runs in background\n", " global TASK_COMPLETE\n", " TASK_COMPLETE = True\n", "\n", @@ -3303,6 +3302,7 @@ "test_eq(r.status_code, 200)\n", "txt = r.text\n", "assert f'{timestamp}' in txt\n", + "# TASK_COMPLETE won't assert unless async_task is run\n", "assert TASK_COMPLETE\n" ] }, @@ -3311,7 +3311,7 @@ "id": "881a88bb", "metadata": {}, "source": [ - "Test one multiple background tasks" + "Test multiple background tasks:" ] }, { @@ -3324,7 +3324,6 @@ "TASK_COUNTER = 0\n", "\n", "async def increment(amount):\n", - " await sleep(1) # Show runs in background\n", " global TASK_COUNTER\n", " TASK_COUNTER += amount\n", "\n", @@ -3342,6 +3341,7 @@ "test_eq(r.status_code, 200)\n", "txt = r.text\n", "assert f'{timestamp}' in txt\n", + "# TASK_COUNTER won't assert==3 unless increment is run\n", "assert TASK_COUNTER == 3\n" ] }, @@ -3421,9 +3421,13 @@ ], "metadata": { "kernelspec": { - "display_name": "python3", + "display_name": ".venv", "language": "python", "name": "python3" + }, + "language_info": { + "name": "python", + "version": "3.12.6" } }, "nbformat": 4, diff --git a/nbs/explains/background_tasks.ipynb b/nbs/explains/background_tasks.ipynb index 8f855d07..fe9a658f 100644 --- a/nbs/explains/background_tasks.ipynb +++ b/nbs/explains/background_tasks.ipynb @@ -6,16 +6,16 @@ "source": [ "# Background Tasks\n", "\n", - "> Background tasks are functions run after returning a response." + "> Background tasks are functions run after handlers return a response." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Useful for operations where you want the user to get a response quickly but you don't want or need the user waiting for it to finish. Typical scenarios include:\n", + "Useful for operations where the users gets a response quickly but doesn't need to wait for the operation to finish. Typical scenarios include:\n", "\n", - "- User setup in complex systems where you can inform the user later in email that their account is complete\n", + "- User setup in complex systems where you can inform the user and other people later in email that their account is complete\n", "- Batch processes that can take a significant amount of time (bulk email or API calls)\n", "- Any other process where the user can be notified later by email, websocket, webhook, or pop-up" ] @@ -24,7 +24,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Background tasks are an easy-to-use wrapper over Python's concurrency mechanisms. They are used to improve user experience, often making apps feel faster to the end user." + "Background tasks are an easy-to-use wrapper over Python's async and threading libraries. Used to improve user experience, background tasks often making apps feel faster to the end user." ] }, { @@ -68,7 +68,7 @@ "```\n", "\n", "1. `counter` is our task function. There is nothing special about it, although it is a good practice for its arguments to be serializable as JSON\n", - "2. We use `starlette.background.BackgroundTask` to turn the counter function into a background task\n", + "2. We use `starlette.background.BackgroundTask` to turn the task_counter function into a background task\n", "3. `FtResponse` is called explicitly so we can attack the task to its background. Normally we don't need to call `FtResponse` explicitly, setting background tasks is the exception." ] }, @@ -227,13 +227,18 @@ "::: {.callout-caution}\n", "## Background tasks are not distributed task queues\n", "\n", - "While background tasks often provides the user with a faster experience, the server itself isn't accelerated. What that means is that processes are still happening, but are hidden. So if a server is struggling under the load of a lot of user activity, so long as it isn't an issue with HTTP, background tasks won't help with server load.\n", + "While background tasks often provide the user with a faster experience, the server itself isn't accelerated. What that means is that processes are still happening, but are hidden. So if a server is struggling under the load of a lot of user activity, so long as it isn't an issue with open HTTP connections consuming resources, background tasks won't help with server load.\n", "\n", - "This is where full-fledged distributed task queue libraries like Celery and Dramatiq come into play. At the cost of dramatically increased complexity over background tasks they allow for the distribution of tasks over addition servers, as well as providing improved observability, retry mechanisms, and persistence in case of server shutdown.\n", + "This is where full-fledged distributed task queue libraries like Celery and Dramatiq come into play. At the cost of dramatically increased complexity over background tasks, they allow for the distribution of tasks over additional servers. These libraries also provide improved observability, retry mechanisms, and persistence in case of server shutdown.\n", "\n", - "In our experience, it's often better to build something with background tasks and then convert it to a task queue.\n", + "In our experience, most of the time background tasks like the ones built into FastHTML suffice. We recommend trying out background tasks before using a distributed task queue. If background task functions are written with JSON serializable arguments, then converting the functions to work in a distributed task queue is a straight-forward process.\n", ":::" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [] } ], "metadata": { From 352d972bfd407b5bd4be0a4ef69176b2c7eae6ab Mon Sep 17 00:00:00 2001 From: Daniel Roy Greenfeld Date: Wed, 19 Mar 2025 22:34:02 +1000 Subject: [PATCH 09/12] New approach for testing BG Tasks Co-authored-by: Audrey Roy Greenfeld --- nbs/api/00_core.ipynb | 450 +++++++++++++++++++++--------------------- 1 file changed, 229 insertions(+), 221 deletions(-) diff --git a/nbs/api/00_core.ipynb b/nbs/api/00_core.ipynb index 4eb74a32..7f5c3db0 100644 --- a/nbs/api/00_core.ipynb +++ b/nbs/api/00_core.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": null, + "execution_count": 1736, "id": "fa505c58", "metadata": {}, "outputs": [], @@ -37,7 +37,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1737, "id": "23503b9e", "metadata": {}, "outputs": [], @@ -71,7 +71,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1738, "id": "7f5d0a72", "metadata": {}, "outputs": [], @@ -91,7 +91,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1739, "id": "19d3f2a7", "metadata": {}, "outputs": [], @@ -112,7 +112,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1740, "id": "e68a76c9", "metadata": {}, "outputs": [], @@ -125,7 +125,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1741, "id": "5331a3e7", "metadata": {}, "outputs": [ @@ -135,7 +135,7 @@ "datetime.datetime(2025, 3, 19, 14, 0)" ] }, - "execution_count": null, + "execution_count": 1741, "metadata": {}, "output_type": "execute_result" } @@ -146,7 +146,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1742, "id": "c40c9071", "metadata": {}, "outputs": [ @@ -156,7 +156,7 @@ "True" ] }, - "execution_count": null, + "execution_count": 1742, "metadata": {}, "output_type": "execute_result" } @@ -167,7 +167,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1743, "id": "7c820373", "metadata": {}, "outputs": [], @@ -181,7 +181,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1744, "id": "442a5aac", "metadata": {}, "outputs": [ @@ -191,7 +191,7 @@ "'Snake-Case'" ] }, - "execution_count": null, + "execution_count": 1744, "metadata": {}, "output_type": "execute_result" } @@ -202,7 +202,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1745, "id": "25f3b8f8", "metadata": {}, "outputs": [], @@ -231,7 +231,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1746, "id": "5b4b5d95", "metadata": {}, "outputs": [], @@ -253,7 +253,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1747, "id": "36e2cac0", "metadata": {}, "outputs": [ @@ -263,7 +263,7 @@ "HtmxHeaders(boosted=None, current_url=None, history_restore_request=None, prompt=None, request='1', target=None, trigger_name=None, trigger=None)" ] }, - "execution_count": null, + "execution_count": 1747, "metadata": {}, "output_type": "execute_result" } @@ -275,7 +275,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1748, "id": "1d53e8e7", "metadata": {}, "outputs": [], @@ -294,7 +294,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1749, "id": "5ab74473", "metadata": {}, "outputs": [], @@ -305,7 +305,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1750, "id": "0afb520c", "metadata": {}, "outputs": [], @@ -325,7 +325,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1751, "id": "95b1f5c9", "metadata": {}, "outputs": [], @@ -340,7 +340,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1752, "id": "c58ccadb", "metadata": {}, "outputs": [], @@ -358,7 +358,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1753, "id": "59757d76", "metadata": {}, "outputs": [], @@ -371,7 +371,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1754, "id": "5fc04751", "metadata": {}, "outputs": [], @@ -383,7 +383,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1755, "id": "94e18161", "metadata": {}, "outputs": [], @@ -398,7 +398,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1756, "id": "592d6c8e", "metadata": {}, "outputs": [ @@ -408,7 +408,7 @@ "'HX-Trigger-After-Settle'" ] }, - "execution_count": null, + "execution_count": 1756, "metadata": {}, "output_type": "execute_result" } @@ -419,7 +419,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1757, "id": "f6a2e62e", "metadata": {}, "outputs": [], @@ -434,7 +434,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1758, "id": "e89857eb", "metadata": {}, "outputs": [ @@ -444,7 +444,7 @@ "HttpHeader(k='HX-Trigger-After-Settle', v='hi')" ] }, - "execution_count": null, + "execution_count": 1758, "metadata": {}, "output_type": "execute_result" } @@ -455,7 +455,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1759, "id": "56cc589f", "metadata": {}, "outputs": [], @@ -469,7 +469,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1760, "id": "cb4ed4aa", "metadata": {}, "outputs": [], @@ -480,7 +480,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1761, "id": "ffdde66f", "metadata": {}, "outputs": [], @@ -495,7 +495,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1762, "id": "5fe74444", "metadata": {}, "outputs": [], @@ -509,7 +509,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1763, "id": "cf80ea34", "metadata": {}, "outputs": [], @@ -523,7 +523,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1764, "id": "42c9cea0", "metadata": {}, "outputs": [], @@ -544,7 +544,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1765, "id": "33d3bc16", "metadata": {}, "outputs": [], @@ -562,7 +562,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1766, "id": "2a8b10f4", "metadata": {}, "outputs": [ @@ -590,7 +590,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1767, "id": "9246153f", "metadata": {}, "outputs": [ @@ -600,7 +600,7 @@ "\"['1', '2']\"" ] }, - "execution_count": null, + "execution_count": 1767, "metadata": {}, "output_type": "execute_result" } @@ -613,7 +613,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1768, "id": "6775cbf8", "metadata": {}, "outputs": [], @@ -664,7 +664,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1769, "id": "bf945ee8", "metadata": {}, "outputs": [ @@ -672,7 +672,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "[, , '1', HttpHeader(k='value1', v='value3')]\n" + "[, , '1', HttpHeader(k='value1', v='value3')]\n" ] } ], @@ -690,7 +690,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1770, "id": "a3ded5ec", "metadata": {}, "outputs": [ @@ -698,7 +698,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "[, , '1', HttpHeader(k='value1', v='value3')]\n" + "[, , '1', HttpHeader(k='value1', v='value3')]\n" ] } ], @@ -716,7 +716,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1771, "id": "7a661bfa", "metadata": {}, "outputs": [], @@ -734,7 +734,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1772, "id": "2ee5adf1", "metadata": {}, "outputs": [], @@ -746,7 +746,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1773, "id": "aacff5ac", "metadata": {}, "outputs": [], @@ -758,7 +758,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1774, "id": "78c3c357", "metadata": {}, "outputs": [], @@ -778,7 +778,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1775, "id": "f2277c02", "metadata": {}, "outputs": [], @@ -814,7 +814,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1776, "id": "dcc15129", "metadata": {}, "outputs": [], @@ -848,7 +848,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1777, "id": "983bcfe2", "metadata": {}, "outputs": [], @@ -864,7 +864,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1778, "id": "5b0e7677", "metadata": {}, "outputs": [], @@ -877,7 +877,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1779, "id": "0dd0a414", "metadata": {}, "outputs": [], @@ -904,7 +904,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1780, "id": "03f4c639", "metadata": {}, "outputs": [], @@ -916,7 +916,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1781, "id": "bdb2ac14", "metadata": {}, "outputs": [], @@ -929,7 +929,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1782, "id": "b80ce139", "metadata": {}, "outputs": [], @@ -940,7 +940,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1783, "id": "a27adc1e", "metadata": {}, "outputs": [], @@ -958,7 +958,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1784, "id": "46614165", "metadata": {}, "outputs": [], @@ -972,7 +972,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1785, "id": "c1707d59", "metadata": {}, "outputs": [], @@ -1010,7 +1010,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1786, "id": "f1e3ed2d", "metadata": {}, "outputs": [], @@ -1021,7 +1021,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1787, "id": "a407dc0e", "metadata": {}, "outputs": [], @@ -1040,7 +1040,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1788, "id": "d36402e6", "metadata": {}, "outputs": [], @@ -1053,7 +1053,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1789, "id": "7f49728d", "metadata": {}, "outputs": [], @@ -1070,7 +1070,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1790, "id": "da449a7b", "metadata": {}, "outputs": [], @@ -1094,7 +1094,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1791, "id": "0150aeec", "metadata": {}, "outputs": [], @@ -1107,7 +1107,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1792, "id": "775fb66b", "metadata": {}, "outputs": [], @@ -1118,7 +1118,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1793, "id": "968d9245", "metadata": {}, "outputs": [], @@ -1142,7 +1142,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1794, "id": "eac44461", "metadata": {}, "outputs": [], @@ -1158,7 +1158,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1795, "id": "1ba52822", "metadata": {}, "outputs": [], @@ -1171,7 +1171,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1796, "id": "6d9cc95f", "metadata": {}, "outputs": [], @@ -1192,7 +1192,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1797, "id": "fb97d46b", "metadata": {}, "outputs": [], @@ -1208,7 +1208,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1798, "id": "8bd78eeb", "metadata": {}, "outputs": [], @@ -1225,7 +1225,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1799, "id": "4771838e", "metadata": {}, "outputs": [ @@ -1235,7 +1235,7 @@ "'a2597a39-e99d-4460-90b9-2ca21e87bc8f'" ] }, - "execution_count": null, + "execution_count": 1799, "metadata": {}, "output_type": "execute_result" } @@ -1246,7 +1246,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1800, "id": "042666e2", "metadata": {}, "outputs": [], @@ -1257,7 +1257,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1801, "id": "d276fc71", "metadata": {}, "outputs": [], @@ -1274,7 +1274,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1802, "id": "bc323fd4", "metadata": {}, "outputs": [], @@ -1302,7 +1302,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1803, "id": "ff82dc78", "metadata": {}, "outputs": [], @@ -1312,7 +1312,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1804, "id": "c0836a8f", "metadata": {}, "outputs": [], @@ -1331,7 +1331,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1805, "id": "50ebb1ee", "metadata": {}, "outputs": [], @@ -1343,7 +1343,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1806, "id": "f86690c4", "metadata": {}, "outputs": [], @@ -1359,7 +1359,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1807, "id": "2c5285ae", "metadata": {}, "outputs": [], @@ -1381,7 +1381,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1808, "id": "e0accf76", "metadata": {}, "outputs": [], @@ -1432,7 +1432,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1809, "id": "246bd8d1", "metadata": {}, "outputs": [], @@ -1443,7 +1443,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1810, "id": "ab73f7df", "metadata": {}, "outputs": [], @@ -1475,7 +1475,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1811, "id": "3818575c", "metadata": {}, "outputs": [], @@ -1492,7 +1492,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1812, "id": "669e76eb", "metadata": {}, "outputs": [], @@ -1507,7 +1507,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1813, "id": "919618c3", "metadata": {}, "outputs": [], @@ -1524,7 +1524,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1814, "id": "d2ecc738", "metadata": {}, "outputs": [], @@ -1537,7 +1537,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1815, "id": "c0f13ece", "metadata": {}, "outputs": [], @@ -1549,7 +1549,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1816, "id": "b218f738", "metadata": {}, "outputs": [ @@ -1559,7 +1559,7 @@ "'f_g'" ] }, - "execution_count": null, + "execution_count": 1816, "metadata": {}, "output_type": "execute_result" } @@ -1571,7 +1571,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1817, "id": "72760b09", "metadata": {}, "outputs": [], @@ -1594,7 +1594,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1818, "id": "f5cb2c2b", "metadata": {}, "outputs": [], @@ -1611,7 +1611,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1819, "id": "e6ee3a86", "metadata": {}, "outputs": [ @@ -1621,7 +1621,7 @@ "'/foo?a=bar&b=1&b=2'" ] }, - "execution_count": null, + "execution_count": 1819, "metadata": {}, "output_type": "execute_result" } @@ -1636,7 +1636,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1820, "id": "9b9f1f03", "metadata": {}, "outputs": [ @@ -1646,7 +1646,7 @@ "'/foo/bar?b=1&b=2'" ] }, - "execution_count": null, + "execution_count": 1820, "metadata": {}, "output_type": "execute_result" } @@ -1660,7 +1660,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1821, "id": "3a348474", "metadata": {}, "outputs": [], @@ -1691,7 +1691,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1822, "id": "8121968a", "metadata": {}, "outputs": [], @@ -1711,7 +1711,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1823, "id": "b163c933", "metadata": {}, "outputs": [ @@ -1721,7 +1721,7 @@ "'test'" ] }, - "execution_count": null, + "execution_count": 1823, "metadata": {}, "output_type": "execute_result" } @@ -1751,7 +1751,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1824, "id": "9abc3781", "metadata": {}, "outputs": [], @@ -1761,7 +1761,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1825, "id": "645d8d95", "metadata": {}, "outputs": [], @@ -1771,7 +1771,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1826, "id": "421262a8", "metadata": {}, "outputs": [ @@ -1788,7 +1788,7 @@ "'/foo?param=value'" ] }, - "execution_count": null, + "execution_count": 1826, "metadata": {}, "output_type": "execute_result" } @@ -1808,7 +1808,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1827, "id": "2ebd6270", "metadata": {}, "outputs": [], @@ -1824,7 +1824,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1828, "id": "8a6ed879", "metadata": {}, "outputs": [ @@ -1834,7 +1834,7 @@ "'Hi there'" ] }, - "execution_count": null, + "execution_count": 1828, "metadata": {}, "output_type": "execute_result" } @@ -1849,7 +1849,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1829, "id": "a8e723fc", "metadata": {}, "outputs": [ @@ -1859,7 +1859,7 @@ "'Postal'" ] }, - "execution_count": null, + "execution_count": 1829, "metadata": {}, "output_type": "execute_result" } @@ -1873,7 +1873,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1830, "id": "274666db", "metadata": {}, "outputs": [ @@ -1883,7 +1883,7 @@ "'testserver'" ] }, - "execution_count": null, + "execution_count": 1830, "metadata": {}, "output_type": "execute_result" } @@ -1897,7 +1897,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1831, "id": "72428702", "metadata": {}, "outputs": [ @@ -1928,7 +1928,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1832, "id": "36292bf3", "metadata": {}, "outputs": [ @@ -1938,7 +1938,7 @@ "'a yoyo'" ] }, - "execution_count": null, + "execution_count": 1832, "metadata": {}, "output_type": "execute_result" } @@ -1952,7 +1952,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1833, "id": "0d813cd0", "metadata": {}, "outputs": [ @@ -1976,7 +1976,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1834, "id": "52dde0da", "metadata": {}, "outputs": [ @@ -2002,7 +2002,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1835, "id": "d84d98f1", "metadata": {}, "outputs": [ @@ -2026,7 +2026,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1836, "id": "64343367", "metadata": {}, "outputs": [ @@ -2036,7 +2036,7 @@ "'Good day to you, Alexis!'" ] }, - "execution_count": null, + "execution_count": 1836, "metadata": {}, "output_type": "execute_result" } @@ -2049,7 +2049,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1837, "id": "29a96715", "metadata": {}, "outputs": [ @@ -2073,7 +2073,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1838, "id": "54266599", "metadata": {}, "outputs": [ @@ -2083,7 +2083,7 @@ "'http://testserver/user/Alexis; http://testserver/hostie'" ] }, - "execution_count": null, + "execution_count": 1838, "metadata": {}, "output_type": "execute_result" } @@ -2097,7 +2097,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1839, "id": "162b811e", "metadata": {}, "outputs": [ @@ -2122,7 +2122,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1840, "id": "db0281cf", "metadata": {}, "outputs": [], @@ -2132,7 +2132,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1841, "id": "b827960a", "metadata": {}, "outputs": [], @@ -2162,7 +2162,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1842, "id": "381a1b68", "metadata": {}, "outputs": [], @@ -2174,7 +2174,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1843, "id": "6b022adb", "metadata": {}, "outputs": [], @@ -2189,7 +2189,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1844, "id": "1b4b5717", "metadata": {}, "outputs": [], @@ -2200,7 +2200,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1845, "id": "dd017867", "metadata": {}, "outputs": [], @@ -2220,7 +2220,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1846, "id": "52d9a3f2", "metadata": {}, "outputs": [], @@ -2239,7 +2239,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1847, "id": "bb8154cc", "metadata": {}, "outputs": [ @@ -2249,7 +2249,7 @@ "'got sub/a.b'" ] }, - "execution_count": null, + "execution_count": 1847, "metadata": {}, "output_type": "execute_result" } @@ -2265,7 +2265,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1848, "id": "6bc9564c", "metadata": {}, "outputs": [], @@ -2275,7 +2275,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1849, "id": "b8be4ef3", "metadata": {}, "outputs": [], @@ -2310,7 +2310,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1850, "id": "5f045cf9", "metadata": {}, "outputs": [], @@ -2327,7 +2327,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1851, "id": "52f9f934", "metadata": {}, "outputs": [], @@ -2339,7 +2339,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1852, "id": "26d316b6", "metadata": {}, "outputs": [], @@ -2350,7 +2350,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1853, "id": "704aeba6", "metadata": {}, "outputs": [], @@ -2361,7 +2361,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1854, "id": "5fa127d5", "metadata": {}, "outputs": [], @@ -2372,7 +2372,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1855, "id": "356db6c0", "metadata": {}, "outputs": [], @@ -2390,7 +2390,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1856, "id": "a3596991", "metadata": {}, "outputs": [], @@ -2404,7 +2404,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1857, "id": "fdb9239c", "metadata": {}, "outputs": [], @@ -2419,7 +2419,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1858, "id": "3366d88f", "metadata": {}, "outputs": [], @@ -2456,7 +2456,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1859, "id": "fcc024e6", "metadata": {}, "outputs": [], @@ -2466,7 +2466,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1860, "id": "48f0a45e", "metadata": {}, "outputs": [], @@ -2483,7 +2483,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1861, "id": "73363a37", "metadata": {}, "outputs": [], @@ -2499,7 +2499,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1862, "id": "0aeaac36", "metadata": {}, "outputs": [], @@ -2514,7 +2514,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1863, "id": "4034ee37", "metadata": {}, "outputs": [], @@ -2525,7 +2525,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1864, "id": "04881594", "metadata": {}, "outputs": [ @@ -2550,7 +2550,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1865, "id": "28a99667", "metadata": {}, "outputs": [ @@ -2571,7 +2571,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1866, "id": "5bce33f0", "metadata": {}, "outputs": [ @@ -2579,16 +2579,16 @@ "name": "stdout", "output_type": "stream", "text": [ - "Set to 2025-03-19 08:28:55.982469\n" + "Set to 2025-03-19 22:23:30.776463\n" ] }, { "data": { "text/plain": [ - "'Session time: 2025-03-19 08:28:55.982469'" + "'Session time: 2025-03-19 22:23:30.776463'" ] }, - "execution_count": null, + "execution_count": 1866, "metadata": {}, "output_type": "execute_result" } @@ -2611,7 +2611,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1867, "id": "249fce31", "metadata": {}, "outputs": [], @@ -2631,7 +2631,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1868, "id": "4d17ab9c", "metadata": {}, "outputs": [ @@ -2653,7 +2653,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1869, "id": "102c17ab", "metadata": {}, "outputs": [], @@ -2670,7 +2670,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1870, "id": "85bf2984", "metadata": {}, "outputs": [], @@ -2681,7 +2681,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1871, "id": "e4fc13b0", "metadata": {}, "outputs": [], @@ -2691,7 +2691,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1872, "id": "1f11eab1", "metadata": {}, "outputs": [], @@ -2707,7 +2707,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1873, "id": "063b7f43", "metadata": {}, "outputs": [], @@ -2724,7 +2724,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1874, "id": "efd6fc6c", "metadata": {}, "outputs": [], @@ -2741,7 +2741,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1875, "id": "32520197", "metadata": {}, "outputs": [], @@ -2766,7 +2766,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1876, "id": "d5223a9a", "metadata": {}, "outputs": [], @@ -2784,7 +2784,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1877, "id": "0f08cb5e", "metadata": {}, "outputs": [], @@ -2832,7 +2832,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1878, "id": "22e17ec8", "metadata": {}, "outputs": [], @@ -2842,7 +2842,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1879, "id": "61030ee4", "metadata": {}, "outputs": [], @@ -2866,7 +2866,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1880, "id": "cd413b0d", "metadata": {}, "outputs": [], @@ -2877,7 +2877,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1881, "id": "8c265ff8", "metadata": {}, "outputs": [], @@ -2895,7 +2895,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1882, "id": "8f7c5710", "metadata": {}, "outputs": [], @@ -2911,7 +2911,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1883, "id": "1236de78", "metadata": {}, "outputs": [], @@ -2924,7 +2924,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1884, "id": "02a4e649", "metadata": {}, "outputs": [], @@ -2934,7 +2934,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1885, "id": "151b9e3c", "metadata": {}, "outputs": [], @@ -2958,7 +2958,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1886, "id": "77ce8548", "metadata": {}, "outputs": [], @@ -2969,7 +2969,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1887, "id": "f1fc8425", "metadata": {}, "outputs": [], @@ -2986,7 +2986,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1888, "id": "f265860d", "metadata": {}, "outputs": [], @@ -3002,7 +3002,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1889, "id": "e38d99cf", "metadata": {}, "outputs": [], @@ -3015,7 +3015,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1890, "id": "259a0f53", "metadata": {}, "outputs": [], @@ -3026,7 +3026,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1891, "id": "f61d110c", "metadata": {}, "outputs": [], @@ -3056,7 +3056,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1892, "id": "7438435e", "metadata": {}, "outputs": [], @@ -3066,7 +3066,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1893, "id": "e518de83", "metadata": {}, "outputs": [], @@ -3092,7 +3092,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1894, "id": "919e600a", "metadata": {}, "outputs": [ @@ -3106,10 +3106,10 @@ { "data": { "text/plain": [ - "'Cookie was set at time 08:28:56.864757'" + "'Cookie was set at time 22:23:30.964804'" ] }, - "execution_count": null, + "execution_count": 1894, "metadata": {}, "output_type": "execute_result" } @@ -3128,7 +3128,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1895, "id": "8816f277", "metadata": {}, "outputs": [], @@ -3141,7 +3141,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1896, "id": "e9535978", "metadata": {}, "outputs": [], @@ -3161,7 +3161,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1897, "id": "9a70842e", "metadata": {}, "outputs": [], @@ -3176,7 +3176,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1898, "id": "8bb51383", "metadata": {}, "outputs": [], @@ -3187,7 +3187,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1899, "id": "b31de65a", "metadata": {}, "outputs": [], @@ -3202,7 +3202,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1900, "id": "d4ff5919", "metadata": {}, "outputs": [], @@ -3213,7 +3213,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1901, "id": "1960d7ff", "metadata": {}, "outputs": [], @@ -3229,7 +3229,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1902, "id": "83a20f93", "metadata": {}, "outputs": [], @@ -3250,7 +3250,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1903, "id": "a9e66b7d", "metadata": {}, "outputs": [], @@ -3278,32 +3278,32 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1904, "id": "4ea66093", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Starting slow task\n", + "Finished slow task\n" + ] + } + ], "source": [ - "TASK_COMPLETE = False\n", - "\n", - "async def async_task():\n", - " global TASK_COMPLETE\n", - " TASK_COMPLETE = True\n", + "async def my_slow_task():\n", + " print('Starting slow task') \n", + " await sleep(3)\n", + " print('Finished slow task') \n", "\n", - "timestamp = datetime.now()\n", - "\n", - "@rt('/ftr-background')\n", + "@rt('/background')\n", "def get():\n", - " cts = Title(timestamp)\n", - " task = BackgroundTask(async_task)\n", - " return FtResponse(cts, background=task)\n", + " return FtResponse(P('BG Task'), background=BackgroundTask(my_slow_task))\n", "\n", - "r = cli.get('/ftr-background')\n", + "r = cli.get('/background')\n", "\n", - "test_eq(r.status_code, 200)\n", - "txt = r.text\n", - "assert f'{timestamp}' in txt\n", - "# TASK_COMPLETE won't assert unless async_task is run\n", - "assert TASK_COMPLETE\n" + "test_eq(r.status_code, 200)\n" ] }, { @@ -3316,7 +3316,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1905, "id": "1764a6aa", "metadata": {}, "outputs": [], @@ -3329,14 +3329,14 @@ "\n", "timestamp = datetime.now()\n", "\n", - "@rt('/ftr-background-mult')\n", + "@rt('/backgrounds')\n", "def get():\n", " cts = Title(timestamp)\n", " tasks = BackgroundTasks()\n", " for i in range(3): tasks.add_task(increment, i)\n", " return FtResponse(cts, background=tasks)\n", "\n", - "r = cli.get('/ftr-background-mult')\n", + "r = cli.get('/backgrounds')\n", "\n", "test_eq(r.status_code, 200)\n", "txt = r.text\n", @@ -3347,7 +3347,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1906, "id": "9dc1025e", "metadata": {}, "outputs": [], @@ -3360,7 +3360,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1907, "id": "5b67e014", "metadata": {}, "outputs": [], @@ -3374,7 +3374,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1908, "id": "1f590f25", "metadata": {}, "outputs": [], @@ -3401,7 +3401,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1909, "id": "d211e8e2", "metadata": {}, "outputs": [], @@ -3426,7 +3426,15 @@ "name": "python3" }, "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", "version": "3.12.6" } }, From da96dc5874e771afc9e9da4459be67177190929d Mon Sep 17 00:00:00 2001 From: Daniel Roy Greenfeld Date: Thu, 20 Mar 2025 08:54:33 +1000 Subject: [PATCH 10/12] Doc formatting --- nbs/explains/background_tasks.ipynb | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/nbs/explains/background_tasks.ipynb b/nbs/explains/background_tasks.ipynb index fe9a658f..059752b2 100644 --- a/nbs/explains/background_tasks.ipynb +++ b/nbs/explains/background_tasks.ipynb @@ -24,7 +24,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Background tasks are an easy-to-use wrapper over Python's async and threading libraries. Used to improve user experience, background tasks often making apps feel faster to the end user." + "::: {.callout-note}\n", + "Background tasks are an easy-to-use wrapper over Python's async and threading libraries. Used to improve user experience, background tasks often making apps feel faster to the end user.\n", + ":::" ] }, { @@ -196,6 +198,15 @@ "It is possible to add multiple background tasks to an FtResponse." ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "::: {.callout-warning}\n", + "Multiple background tasks on a background task are executed in order. In the case a task raises an exception, following tasks will not get the opportunity to be executed.\n", + ":::" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -224,21 +235,14 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "::: {.callout-caution}\n", "## Background tasks are not distributed task queues\n", "\n", "While background tasks often provide the user with a faster experience, the server itself isn't accelerated. What that means is that processes are still happening, but are hidden. So if a server is struggling under the load of a lot of user activity, so long as it isn't an issue with open HTTP connections consuming resources, background tasks won't help with server load.\n", "\n", "This is where full-fledged distributed task queue libraries like Celery and Dramatiq come into play. At the cost of dramatically increased complexity over background tasks, they allow for the distribution of tasks over additional servers. These libraries also provide improved observability, retry mechanisms, and persistence in case of server shutdown.\n", "\n", - "In our experience, most of the time background tasks like the ones built into FastHTML suffice. We recommend trying out background tasks before using a distributed task queue. If background task functions are written with JSON serializable arguments, then converting the functions to work in a distributed task queue is a straight-forward process.\n", - ":::" + "In our experience, most of the time background tasks like the ones built into FastHTML suffice. We recommend trying out background tasks before using a distributed task queue. If background task functions are written with JSON serializable arguments, then converting the functions to work in a distributed task queue is a straight-forward process." ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [] } ], "metadata": { From 8e6a8b5867a708b247a62b5cdd76d7bd07710cd5 Mon Sep 17 00:00:00 2001 From: Daniel Roy Greenfeld Date: Thu, 20 Mar 2025 09:02:51 +1000 Subject: [PATCH 11/12] First pass on revised multi-BG Tasks test --- nbs/api/00_core.ipynb | 444 +++++++++++++++++++++--------------------- 1 file changed, 224 insertions(+), 220 deletions(-) diff --git a/nbs/api/00_core.ipynb b/nbs/api/00_core.ipynb index 7f5c3db0..539b41e6 100644 --- a/nbs/api/00_core.ipynb +++ b/nbs/api/00_core.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 1736, + "execution_count": 2084, "id": "fa505c58", "metadata": {}, "outputs": [], @@ -37,7 +37,7 @@ }, { "cell_type": "code", - "execution_count": 1737, + "execution_count": 2085, "id": "23503b9e", "metadata": {}, "outputs": [], @@ -71,7 +71,7 @@ }, { "cell_type": "code", - "execution_count": 1738, + "execution_count": 2086, "id": "7f5d0a72", "metadata": {}, "outputs": [], @@ -81,7 +81,6 @@ "from IPython import display\n", "from enum import Enum\n", "from pprint import pprint\n", - "from asyncio import sleep\n", "\n", "from fastcore.test import *\n", "from starlette.testclient import TestClient\n", @@ -91,7 +90,7 @@ }, { "cell_type": "code", - "execution_count": 1739, + "execution_count": 2087, "id": "19d3f2a7", "metadata": {}, "outputs": [], @@ -112,7 +111,7 @@ }, { "cell_type": "code", - "execution_count": 1740, + "execution_count": 2088, "id": "e68a76c9", "metadata": {}, "outputs": [], @@ -125,17 +124,17 @@ }, { "cell_type": "code", - "execution_count": 1741, + "execution_count": 2089, "id": "5331a3e7", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "datetime.datetime(2025, 3, 19, 14, 0)" + "datetime.datetime(2025, 3, 20, 14, 0)" ] }, - "execution_count": 1741, + "execution_count": 2089, "metadata": {}, "output_type": "execute_result" } @@ -146,7 +145,7 @@ }, { "cell_type": "code", - "execution_count": 1742, + "execution_count": 2090, "id": "c40c9071", "metadata": {}, "outputs": [ @@ -156,7 +155,7 @@ "True" ] }, - "execution_count": 1742, + "execution_count": 2090, "metadata": {}, "output_type": "execute_result" } @@ -167,7 +166,7 @@ }, { "cell_type": "code", - "execution_count": 1743, + "execution_count": 2091, "id": "7c820373", "metadata": {}, "outputs": [], @@ -181,7 +180,7 @@ }, { "cell_type": "code", - "execution_count": 1744, + "execution_count": 2092, "id": "442a5aac", "metadata": {}, "outputs": [ @@ -191,7 +190,7 @@ "'Snake-Case'" ] }, - "execution_count": 1744, + "execution_count": 2092, "metadata": {}, "output_type": "execute_result" } @@ -202,7 +201,7 @@ }, { "cell_type": "code", - "execution_count": 1745, + "execution_count": 2093, "id": "25f3b8f8", "metadata": {}, "outputs": [], @@ -231,7 +230,7 @@ }, { "cell_type": "code", - "execution_count": 1746, + "execution_count": 2094, "id": "5b4b5d95", "metadata": {}, "outputs": [], @@ -253,7 +252,7 @@ }, { "cell_type": "code", - "execution_count": 1747, + "execution_count": 2095, "id": "36e2cac0", "metadata": {}, "outputs": [ @@ -263,7 +262,7 @@ "HtmxHeaders(boosted=None, current_url=None, history_restore_request=None, prompt=None, request='1', target=None, trigger_name=None, trigger=None)" ] }, - "execution_count": 1747, + "execution_count": 2095, "metadata": {}, "output_type": "execute_result" } @@ -275,7 +274,7 @@ }, { "cell_type": "code", - "execution_count": 1748, + "execution_count": 2096, "id": "1d53e8e7", "metadata": {}, "outputs": [], @@ -294,7 +293,7 @@ }, { "cell_type": "code", - "execution_count": 1749, + "execution_count": 2097, "id": "5ab74473", "metadata": {}, "outputs": [], @@ -305,7 +304,7 @@ }, { "cell_type": "code", - "execution_count": 1750, + "execution_count": 2098, "id": "0afb520c", "metadata": {}, "outputs": [], @@ -325,7 +324,7 @@ }, { "cell_type": "code", - "execution_count": 1751, + "execution_count": 2099, "id": "95b1f5c9", "metadata": {}, "outputs": [], @@ -340,7 +339,7 @@ }, { "cell_type": "code", - "execution_count": 1752, + "execution_count": 2100, "id": "c58ccadb", "metadata": {}, "outputs": [], @@ -358,7 +357,7 @@ }, { "cell_type": "code", - "execution_count": 1753, + "execution_count": 2101, "id": "59757d76", "metadata": {}, "outputs": [], @@ -371,7 +370,7 @@ }, { "cell_type": "code", - "execution_count": 1754, + "execution_count": 2102, "id": "5fc04751", "metadata": {}, "outputs": [], @@ -383,7 +382,7 @@ }, { "cell_type": "code", - "execution_count": 1755, + "execution_count": 2103, "id": "94e18161", "metadata": {}, "outputs": [], @@ -398,7 +397,7 @@ }, { "cell_type": "code", - "execution_count": 1756, + "execution_count": 2104, "id": "592d6c8e", "metadata": {}, "outputs": [ @@ -408,7 +407,7 @@ "'HX-Trigger-After-Settle'" ] }, - "execution_count": 1756, + "execution_count": 2104, "metadata": {}, "output_type": "execute_result" } @@ -419,7 +418,7 @@ }, { "cell_type": "code", - "execution_count": 1757, + "execution_count": 2105, "id": "f6a2e62e", "metadata": {}, "outputs": [], @@ -434,7 +433,7 @@ }, { "cell_type": "code", - "execution_count": 1758, + "execution_count": 2106, "id": "e89857eb", "metadata": {}, "outputs": [ @@ -444,7 +443,7 @@ "HttpHeader(k='HX-Trigger-After-Settle', v='hi')" ] }, - "execution_count": 1758, + "execution_count": 2106, "metadata": {}, "output_type": "execute_result" } @@ -455,7 +454,7 @@ }, { "cell_type": "code", - "execution_count": 1759, + "execution_count": 2107, "id": "56cc589f", "metadata": {}, "outputs": [], @@ -469,7 +468,7 @@ }, { "cell_type": "code", - "execution_count": 1760, + "execution_count": 2108, "id": "cb4ed4aa", "metadata": {}, "outputs": [], @@ -480,7 +479,7 @@ }, { "cell_type": "code", - "execution_count": 1761, + "execution_count": 2109, "id": "ffdde66f", "metadata": {}, "outputs": [], @@ -495,7 +494,7 @@ }, { "cell_type": "code", - "execution_count": 1762, + "execution_count": 2110, "id": "5fe74444", "metadata": {}, "outputs": [], @@ -509,7 +508,7 @@ }, { "cell_type": "code", - "execution_count": 1763, + "execution_count": 2111, "id": "cf80ea34", "metadata": {}, "outputs": [], @@ -523,7 +522,7 @@ }, { "cell_type": "code", - "execution_count": 1764, + "execution_count": 2112, "id": "42c9cea0", "metadata": {}, "outputs": [], @@ -544,7 +543,7 @@ }, { "cell_type": "code", - "execution_count": 1765, + "execution_count": 2113, "id": "33d3bc16", "metadata": {}, "outputs": [], @@ -562,7 +561,7 @@ }, { "cell_type": "code", - "execution_count": 1766, + "execution_count": 2114, "id": "2a8b10f4", "metadata": {}, "outputs": [ @@ -590,7 +589,7 @@ }, { "cell_type": "code", - "execution_count": 1767, + "execution_count": 2115, "id": "9246153f", "metadata": {}, "outputs": [ @@ -600,7 +599,7 @@ "\"['1', '2']\"" ] }, - "execution_count": 1767, + "execution_count": 2115, "metadata": {}, "output_type": "execute_result" } @@ -613,7 +612,7 @@ }, { "cell_type": "code", - "execution_count": 1768, + "execution_count": 2116, "id": "6775cbf8", "metadata": {}, "outputs": [], @@ -664,7 +663,7 @@ }, { "cell_type": "code", - "execution_count": 1769, + "execution_count": 2117, "id": "bf945ee8", "metadata": {}, "outputs": [ @@ -672,7 +671,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "[, , '1', HttpHeader(k='value1', v='value3')]\n" + "[, , '1', HttpHeader(k='value1', v='value3')]\n" ] } ], @@ -690,7 +689,7 @@ }, { "cell_type": "code", - "execution_count": 1770, + "execution_count": 2118, "id": "a3ded5ec", "metadata": {}, "outputs": [ @@ -698,7 +697,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "[, , '1', HttpHeader(k='value1', v='value3')]\n" + "[, , '1', HttpHeader(k='value1', v='value3')]\n" ] } ], @@ -716,7 +715,7 @@ }, { "cell_type": "code", - "execution_count": 1771, + "execution_count": 2119, "id": "7a661bfa", "metadata": {}, "outputs": [], @@ -734,7 +733,7 @@ }, { "cell_type": "code", - "execution_count": 1772, + "execution_count": 2120, "id": "2ee5adf1", "metadata": {}, "outputs": [], @@ -746,7 +745,7 @@ }, { "cell_type": "code", - "execution_count": 1773, + "execution_count": 2121, "id": "aacff5ac", "metadata": {}, "outputs": [], @@ -758,7 +757,7 @@ }, { "cell_type": "code", - "execution_count": 1774, + "execution_count": 2122, "id": "78c3c357", "metadata": {}, "outputs": [], @@ -778,7 +777,7 @@ }, { "cell_type": "code", - "execution_count": 1775, + "execution_count": 2123, "id": "f2277c02", "metadata": {}, "outputs": [], @@ -814,7 +813,7 @@ }, { "cell_type": "code", - "execution_count": 1776, + "execution_count": 2124, "id": "dcc15129", "metadata": {}, "outputs": [], @@ -848,7 +847,7 @@ }, { "cell_type": "code", - "execution_count": 1777, + "execution_count": 2125, "id": "983bcfe2", "metadata": {}, "outputs": [], @@ -864,7 +863,7 @@ }, { "cell_type": "code", - "execution_count": 1778, + "execution_count": 2126, "id": "5b0e7677", "metadata": {}, "outputs": [], @@ -877,7 +876,7 @@ }, { "cell_type": "code", - "execution_count": 1779, + "execution_count": 2127, "id": "0dd0a414", "metadata": {}, "outputs": [], @@ -904,7 +903,7 @@ }, { "cell_type": "code", - "execution_count": 1780, + "execution_count": 2128, "id": "03f4c639", "metadata": {}, "outputs": [], @@ -916,7 +915,7 @@ }, { "cell_type": "code", - "execution_count": 1781, + "execution_count": 2129, "id": "bdb2ac14", "metadata": {}, "outputs": [], @@ -929,7 +928,7 @@ }, { "cell_type": "code", - "execution_count": 1782, + "execution_count": 2130, "id": "b80ce139", "metadata": {}, "outputs": [], @@ -940,7 +939,7 @@ }, { "cell_type": "code", - "execution_count": 1783, + "execution_count": 2131, "id": "a27adc1e", "metadata": {}, "outputs": [], @@ -958,7 +957,7 @@ }, { "cell_type": "code", - "execution_count": 1784, + "execution_count": 2132, "id": "46614165", "metadata": {}, "outputs": [], @@ -972,7 +971,7 @@ }, { "cell_type": "code", - "execution_count": 1785, + "execution_count": 2133, "id": "c1707d59", "metadata": {}, "outputs": [], @@ -1010,7 +1009,7 @@ }, { "cell_type": "code", - "execution_count": 1786, + "execution_count": 2134, "id": "f1e3ed2d", "metadata": {}, "outputs": [], @@ -1021,7 +1020,7 @@ }, { "cell_type": "code", - "execution_count": 1787, + "execution_count": 2135, "id": "a407dc0e", "metadata": {}, "outputs": [], @@ -1040,7 +1039,7 @@ }, { "cell_type": "code", - "execution_count": 1788, + "execution_count": 2136, "id": "d36402e6", "metadata": {}, "outputs": [], @@ -1053,7 +1052,7 @@ }, { "cell_type": "code", - "execution_count": 1789, + "execution_count": 2137, "id": "7f49728d", "metadata": {}, "outputs": [], @@ -1070,7 +1069,7 @@ }, { "cell_type": "code", - "execution_count": 1790, + "execution_count": 2138, "id": "da449a7b", "metadata": {}, "outputs": [], @@ -1094,7 +1093,7 @@ }, { "cell_type": "code", - "execution_count": 1791, + "execution_count": 2139, "id": "0150aeec", "metadata": {}, "outputs": [], @@ -1107,7 +1106,7 @@ }, { "cell_type": "code", - "execution_count": 1792, + "execution_count": 2140, "id": "775fb66b", "metadata": {}, "outputs": [], @@ -1118,7 +1117,7 @@ }, { "cell_type": "code", - "execution_count": 1793, + "execution_count": 2141, "id": "968d9245", "metadata": {}, "outputs": [], @@ -1142,7 +1141,7 @@ }, { "cell_type": "code", - "execution_count": 1794, + "execution_count": 2142, "id": "eac44461", "metadata": {}, "outputs": [], @@ -1158,7 +1157,7 @@ }, { "cell_type": "code", - "execution_count": 1795, + "execution_count": 2143, "id": "1ba52822", "metadata": {}, "outputs": [], @@ -1171,7 +1170,7 @@ }, { "cell_type": "code", - "execution_count": 1796, + "execution_count": 2144, "id": "6d9cc95f", "metadata": {}, "outputs": [], @@ -1192,7 +1191,7 @@ }, { "cell_type": "code", - "execution_count": 1797, + "execution_count": 2145, "id": "fb97d46b", "metadata": {}, "outputs": [], @@ -1208,7 +1207,7 @@ }, { "cell_type": "code", - "execution_count": 1798, + "execution_count": 2146, "id": "8bd78eeb", "metadata": {}, "outputs": [], @@ -1225,7 +1224,7 @@ }, { "cell_type": "code", - "execution_count": 1799, + "execution_count": 2147, "id": "4771838e", "metadata": {}, "outputs": [ @@ -1235,7 +1234,7 @@ "'a2597a39-e99d-4460-90b9-2ca21e87bc8f'" ] }, - "execution_count": 1799, + "execution_count": 2147, "metadata": {}, "output_type": "execute_result" } @@ -1246,7 +1245,7 @@ }, { "cell_type": "code", - "execution_count": 1800, + "execution_count": 2148, "id": "042666e2", "metadata": {}, "outputs": [], @@ -1257,7 +1256,7 @@ }, { "cell_type": "code", - "execution_count": 1801, + "execution_count": 2149, "id": "d276fc71", "metadata": {}, "outputs": [], @@ -1274,7 +1273,7 @@ }, { "cell_type": "code", - "execution_count": 1802, + "execution_count": 2150, "id": "bc323fd4", "metadata": {}, "outputs": [], @@ -1302,7 +1301,7 @@ }, { "cell_type": "code", - "execution_count": 1803, + "execution_count": 2151, "id": "ff82dc78", "metadata": {}, "outputs": [], @@ -1312,7 +1311,7 @@ }, { "cell_type": "code", - "execution_count": 1804, + "execution_count": 2152, "id": "c0836a8f", "metadata": {}, "outputs": [], @@ -1331,7 +1330,7 @@ }, { "cell_type": "code", - "execution_count": 1805, + "execution_count": 2153, "id": "50ebb1ee", "metadata": {}, "outputs": [], @@ -1343,7 +1342,7 @@ }, { "cell_type": "code", - "execution_count": 1806, + "execution_count": 2154, "id": "f86690c4", "metadata": {}, "outputs": [], @@ -1359,7 +1358,7 @@ }, { "cell_type": "code", - "execution_count": 1807, + "execution_count": 2155, "id": "2c5285ae", "metadata": {}, "outputs": [], @@ -1381,7 +1380,7 @@ }, { "cell_type": "code", - "execution_count": 1808, + "execution_count": 2156, "id": "e0accf76", "metadata": {}, "outputs": [], @@ -1432,7 +1431,7 @@ }, { "cell_type": "code", - "execution_count": 1809, + "execution_count": 2157, "id": "246bd8d1", "metadata": {}, "outputs": [], @@ -1443,7 +1442,7 @@ }, { "cell_type": "code", - "execution_count": 1810, + "execution_count": 2158, "id": "ab73f7df", "metadata": {}, "outputs": [], @@ -1475,7 +1474,7 @@ }, { "cell_type": "code", - "execution_count": 1811, + "execution_count": 2159, "id": "3818575c", "metadata": {}, "outputs": [], @@ -1492,7 +1491,7 @@ }, { "cell_type": "code", - "execution_count": 1812, + "execution_count": 2160, "id": "669e76eb", "metadata": {}, "outputs": [], @@ -1507,7 +1506,7 @@ }, { "cell_type": "code", - "execution_count": 1813, + "execution_count": 2161, "id": "919618c3", "metadata": {}, "outputs": [], @@ -1524,7 +1523,7 @@ }, { "cell_type": "code", - "execution_count": 1814, + "execution_count": 2162, "id": "d2ecc738", "metadata": {}, "outputs": [], @@ -1537,7 +1536,7 @@ }, { "cell_type": "code", - "execution_count": 1815, + "execution_count": 2163, "id": "c0f13ece", "metadata": {}, "outputs": [], @@ -1549,7 +1548,7 @@ }, { "cell_type": "code", - "execution_count": 1816, + "execution_count": 2164, "id": "b218f738", "metadata": {}, "outputs": [ @@ -1559,7 +1558,7 @@ "'f_g'" ] }, - "execution_count": 1816, + "execution_count": 2164, "metadata": {}, "output_type": "execute_result" } @@ -1571,7 +1570,7 @@ }, { "cell_type": "code", - "execution_count": 1817, + "execution_count": 2165, "id": "72760b09", "metadata": {}, "outputs": [], @@ -1594,7 +1593,7 @@ }, { "cell_type": "code", - "execution_count": 1818, + "execution_count": 2166, "id": "f5cb2c2b", "metadata": {}, "outputs": [], @@ -1611,7 +1610,7 @@ }, { "cell_type": "code", - "execution_count": 1819, + "execution_count": 2167, "id": "e6ee3a86", "metadata": {}, "outputs": [ @@ -1621,7 +1620,7 @@ "'/foo?a=bar&b=1&b=2'" ] }, - "execution_count": 1819, + "execution_count": 2167, "metadata": {}, "output_type": "execute_result" } @@ -1636,7 +1635,7 @@ }, { "cell_type": "code", - "execution_count": 1820, + "execution_count": 2168, "id": "9b9f1f03", "metadata": {}, "outputs": [ @@ -1646,7 +1645,7 @@ "'/foo/bar?b=1&b=2'" ] }, - "execution_count": 1820, + "execution_count": 2168, "metadata": {}, "output_type": "execute_result" } @@ -1660,7 +1659,7 @@ }, { "cell_type": "code", - "execution_count": 1821, + "execution_count": 2169, "id": "3a348474", "metadata": {}, "outputs": [], @@ -1691,7 +1690,7 @@ }, { "cell_type": "code", - "execution_count": 1822, + "execution_count": 2170, "id": "8121968a", "metadata": {}, "outputs": [], @@ -1711,7 +1710,7 @@ }, { "cell_type": "code", - "execution_count": 1823, + "execution_count": 2171, "id": "b163c933", "metadata": {}, "outputs": [ @@ -1721,7 +1720,7 @@ "'test'" ] }, - "execution_count": 1823, + "execution_count": 2171, "metadata": {}, "output_type": "execute_result" } @@ -1751,7 +1750,7 @@ }, { "cell_type": "code", - "execution_count": 1824, + "execution_count": 2172, "id": "9abc3781", "metadata": {}, "outputs": [], @@ -1761,7 +1760,7 @@ }, { "cell_type": "code", - "execution_count": 1825, + "execution_count": 2173, "id": "645d8d95", "metadata": {}, "outputs": [], @@ -1771,7 +1770,7 @@ }, { "cell_type": "code", - "execution_count": 1826, + "execution_count": 2174, "id": "421262a8", "metadata": {}, "outputs": [ @@ -1788,7 +1787,7 @@ "'/foo?param=value'" ] }, - "execution_count": 1826, + "execution_count": 2174, "metadata": {}, "output_type": "execute_result" } @@ -1808,7 +1807,7 @@ }, { "cell_type": "code", - "execution_count": 1827, + "execution_count": 2175, "id": "2ebd6270", "metadata": {}, "outputs": [], @@ -1824,7 +1823,7 @@ }, { "cell_type": "code", - "execution_count": 1828, + "execution_count": 2176, "id": "8a6ed879", "metadata": {}, "outputs": [ @@ -1834,7 +1833,7 @@ "'Hi there'" ] }, - "execution_count": 1828, + "execution_count": 2176, "metadata": {}, "output_type": "execute_result" } @@ -1849,7 +1848,7 @@ }, { "cell_type": "code", - "execution_count": 1829, + "execution_count": 2177, "id": "a8e723fc", "metadata": {}, "outputs": [ @@ -1859,7 +1858,7 @@ "'Postal'" ] }, - "execution_count": 1829, + "execution_count": 2177, "metadata": {}, "output_type": "execute_result" } @@ -1873,7 +1872,7 @@ }, { "cell_type": "code", - "execution_count": 1830, + "execution_count": 2178, "id": "274666db", "metadata": {}, "outputs": [ @@ -1883,7 +1882,7 @@ "'testserver'" ] }, - "execution_count": 1830, + "execution_count": 2178, "metadata": {}, "output_type": "execute_result" } @@ -1897,7 +1896,7 @@ }, { "cell_type": "code", - "execution_count": 1831, + "execution_count": 2179, "id": "72428702", "metadata": {}, "outputs": [ @@ -1928,7 +1927,7 @@ }, { "cell_type": "code", - "execution_count": 1832, + "execution_count": 2180, "id": "36292bf3", "metadata": {}, "outputs": [ @@ -1938,7 +1937,7 @@ "'a yoyo'" ] }, - "execution_count": 1832, + "execution_count": 2180, "metadata": {}, "output_type": "execute_result" } @@ -1952,7 +1951,7 @@ }, { "cell_type": "code", - "execution_count": 1833, + "execution_count": 2181, "id": "0d813cd0", "metadata": {}, "outputs": [ @@ -1976,7 +1975,7 @@ }, { "cell_type": "code", - "execution_count": 1834, + "execution_count": 2182, "id": "52dde0da", "metadata": {}, "outputs": [ @@ -2002,7 +2001,7 @@ }, { "cell_type": "code", - "execution_count": 1835, + "execution_count": 2183, "id": "d84d98f1", "metadata": {}, "outputs": [ @@ -2026,7 +2025,7 @@ }, { "cell_type": "code", - "execution_count": 1836, + "execution_count": 2184, "id": "64343367", "metadata": {}, "outputs": [ @@ -2036,7 +2035,7 @@ "'Good day to you, Alexis!'" ] }, - "execution_count": 1836, + "execution_count": 2184, "metadata": {}, "output_type": "execute_result" } @@ -2049,7 +2048,7 @@ }, { "cell_type": "code", - "execution_count": 1837, + "execution_count": 2185, "id": "29a96715", "metadata": {}, "outputs": [ @@ -2073,7 +2072,7 @@ }, { "cell_type": "code", - "execution_count": 1838, + "execution_count": 2186, "id": "54266599", "metadata": {}, "outputs": [ @@ -2083,7 +2082,7 @@ "'http://testserver/user/Alexis; http://testserver/hostie'" ] }, - "execution_count": 1838, + "execution_count": 2186, "metadata": {}, "output_type": "execute_result" } @@ -2097,7 +2096,7 @@ }, { "cell_type": "code", - "execution_count": 1839, + "execution_count": 2187, "id": "162b811e", "metadata": {}, "outputs": [ @@ -2122,7 +2121,7 @@ }, { "cell_type": "code", - "execution_count": 1840, + "execution_count": 2188, "id": "db0281cf", "metadata": {}, "outputs": [], @@ -2132,7 +2131,7 @@ }, { "cell_type": "code", - "execution_count": 1841, + "execution_count": 2189, "id": "b827960a", "metadata": {}, "outputs": [], @@ -2162,7 +2161,7 @@ }, { "cell_type": "code", - "execution_count": 1842, + "execution_count": 2190, "id": "381a1b68", "metadata": {}, "outputs": [], @@ -2174,7 +2173,7 @@ }, { "cell_type": "code", - "execution_count": 1843, + "execution_count": 2191, "id": "6b022adb", "metadata": {}, "outputs": [], @@ -2189,7 +2188,7 @@ }, { "cell_type": "code", - "execution_count": 1844, + "execution_count": 2192, "id": "1b4b5717", "metadata": {}, "outputs": [], @@ -2200,7 +2199,7 @@ }, { "cell_type": "code", - "execution_count": 1845, + "execution_count": 2193, "id": "dd017867", "metadata": {}, "outputs": [], @@ -2220,7 +2219,7 @@ }, { "cell_type": "code", - "execution_count": 1846, + "execution_count": 2194, "id": "52d9a3f2", "metadata": {}, "outputs": [], @@ -2239,7 +2238,7 @@ }, { "cell_type": "code", - "execution_count": 1847, + "execution_count": 2195, "id": "bb8154cc", "metadata": {}, "outputs": [ @@ -2249,7 +2248,7 @@ "'got sub/a.b'" ] }, - "execution_count": 1847, + "execution_count": 2195, "metadata": {}, "output_type": "execute_result" } @@ -2265,7 +2264,7 @@ }, { "cell_type": "code", - "execution_count": 1848, + "execution_count": 2196, "id": "6bc9564c", "metadata": {}, "outputs": [], @@ -2275,7 +2274,7 @@ }, { "cell_type": "code", - "execution_count": 1849, + "execution_count": 2197, "id": "b8be4ef3", "metadata": {}, "outputs": [], @@ -2310,7 +2309,7 @@ }, { "cell_type": "code", - "execution_count": 1850, + "execution_count": 2198, "id": "5f045cf9", "metadata": {}, "outputs": [], @@ -2327,7 +2326,7 @@ }, { "cell_type": "code", - "execution_count": 1851, + "execution_count": 2199, "id": "52f9f934", "metadata": {}, "outputs": [], @@ -2339,7 +2338,7 @@ }, { "cell_type": "code", - "execution_count": 1852, + "execution_count": 2200, "id": "26d316b6", "metadata": {}, "outputs": [], @@ -2350,7 +2349,7 @@ }, { "cell_type": "code", - "execution_count": 1853, + "execution_count": 2201, "id": "704aeba6", "metadata": {}, "outputs": [], @@ -2361,7 +2360,7 @@ }, { "cell_type": "code", - "execution_count": 1854, + "execution_count": 2202, "id": "5fa127d5", "metadata": {}, "outputs": [], @@ -2372,7 +2371,7 @@ }, { "cell_type": "code", - "execution_count": 1855, + "execution_count": 2203, "id": "356db6c0", "metadata": {}, "outputs": [], @@ -2390,7 +2389,7 @@ }, { "cell_type": "code", - "execution_count": 1856, + "execution_count": 2204, "id": "a3596991", "metadata": {}, "outputs": [], @@ -2404,7 +2403,7 @@ }, { "cell_type": "code", - "execution_count": 1857, + "execution_count": 2205, "id": "fdb9239c", "metadata": {}, "outputs": [], @@ -2419,7 +2418,7 @@ }, { "cell_type": "code", - "execution_count": 1858, + "execution_count": 2206, "id": "3366d88f", "metadata": {}, "outputs": [], @@ -2456,7 +2455,7 @@ }, { "cell_type": "code", - "execution_count": 1859, + "execution_count": 2207, "id": "fcc024e6", "metadata": {}, "outputs": [], @@ -2466,7 +2465,7 @@ }, { "cell_type": "code", - "execution_count": 1860, + "execution_count": 2208, "id": "48f0a45e", "metadata": {}, "outputs": [], @@ -2483,7 +2482,7 @@ }, { "cell_type": "code", - "execution_count": 1861, + "execution_count": 2209, "id": "73363a37", "metadata": {}, "outputs": [], @@ -2499,7 +2498,7 @@ }, { "cell_type": "code", - "execution_count": 1862, + "execution_count": 2210, "id": "0aeaac36", "metadata": {}, "outputs": [], @@ -2514,7 +2513,7 @@ }, { "cell_type": "code", - "execution_count": 1863, + "execution_count": 2211, "id": "4034ee37", "metadata": {}, "outputs": [], @@ -2525,7 +2524,7 @@ }, { "cell_type": "code", - "execution_count": 1864, + "execution_count": 2212, "id": "04881594", "metadata": {}, "outputs": [ @@ -2550,7 +2549,7 @@ }, { "cell_type": "code", - "execution_count": 1865, + "execution_count": 2213, "id": "28a99667", "metadata": {}, "outputs": [ @@ -2571,7 +2570,7 @@ }, { "cell_type": "code", - "execution_count": 1866, + "execution_count": 2214, "id": "5bce33f0", "metadata": {}, "outputs": [ @@ -2579,16 +2578,16 @@ "name": "stdout", "output_type": "stream", "text": [ - "Set to 2025-03-19 22:23:30.776463\n" + "Set to 2025-03-20 09:00:21.844393\n" ] }, { "data": { "text/plain": [ - "'Session time: 2025-03-19 22:23:30.776463'" + "'Session time: 2025-03-20 09:00:21.844393'" ] }, - "execution_count": 1866, + "execution_count": 2214, "metadata": {}, "output_type": "execute_result" } @@ -2611,7 +2610,7 @@ }, { "cell_type": "code", - "execution_count": 1867, + "execution_count": 2215, "id": "249fce31", "metadata": {}, "outputs": [], @@ -2631,7 +2630,7 @@ }, { "cell_type": "code", - "execution_count": 1868, + "execution_count": 2216, "id": "4d17ab9c", "metadata": {}, "outputs": [ @@ -2653,7 +2652,7 @@ }, { "cell_type": "code", - "execution_count": 1869, + "execution_count": 2217, "id": "102c17ab", "metadata": {}, "outputs": [], @@ -2670,7 +2669,7 @@ }, { "cell_type": "code", - "execution_count": 1870, + "execution_count": 2218, "id": "85bf2984", "metadata": {}, "outputs": [], @@ -2681,7 +2680,7 @@ }, { "cell_type": "code", - "execution_count": 1871, + "execution_count": 2219, "id": "e4fc13b0", "metadata": {}, "outputs": [], @@ -2691,7 +2690,7 @@ }, { "cell_type": "code", - "execution_count": 1872, + "execution_count": 2220, "id": "1f11eab1", "metadata": {}, "outputs": [], @@ -2707,7 +2706,7 @@ }, { "cell_type": "code", - "execution_count": 1873, + "execution_count": 2221, "id": "063b7f43", "metadata": {}, "outputs": [], @@ -2724,7 +2723,7 @@ }, { "cell_type": "code", - "execution_count": 1874, + "execution_count": 2222, "id": "efd6fc6c", "metadata": {}, "outputs": [], @@ -2741,7 +2740,7 @@ }, { "cell_type": "code", - "execution_count": 1875, + "execution_count": 2223, "id": "32520197", "metadata": {}, "outputs": [], @@ -2766,7 +2765,7 @@ }, { "cell_type": "code", - "execution_count": 1876, + "execution_count": 2224, "id": "d5223a9a", "metadata": {}, "outputs": [], @@ -2784,7 +2783,7 @@ }, { "cell_type": "code", - "execution_count": 1877, + "execution_count": 2225, "id": "0f08cb5e", "metadata": {}, "outputs": [], @@ -2832,7 +2831,7 @@ }, { "cell_type": "code", - "execution_count": 1878, + "execution_count": 2226, "id": "22e17ec8", "metadata": {}, "outputs": [], @@ -2842,7 +2841,7 @@ }, { "cell_type": "code", - "execution_count": 1879, + "execution_count": 2227, "id": "61030ee4", "metadata": {}, "outputs": [], @@ -2866,7 +2865,7 @@ }, { "cell_type": "code", - "execution_count": 1880, + "execution_count": 2228, "id": "cd413b0d", "metadata": {}, "outputs": [], @@ -2877,7 +2876,7 @@ }, { "cell_type": "code", - "execution_count": 1881, + "execution_count": 2229, "id": "8c265ff8", "metadata": {}, "outputs": [], @@ -2895,7 +2894,7 @@ }, { "cell_type": "code", - "execution_count": 1882, + "execution_count": 2230, "id": "8f7c5710", "metadata": {}, "outputs": [], @@ -2911,7 +2910,7 @@ }, { "cell_type": "code", - "execution_count": 1883, + "execution_count": 2231, "id": "1236de78", "metadata": {}, "outputs": [], @@ -2924,7 +2923,7 @@ }, { "cell_type": "code", - "execution_count": 1884, + "execution_count": 2232, "id": "02a4e649", "metadata": {}, "outputs": [], @@ -2934,7 +2933,7 @@ }, { "cell_type": "code", - "execution_count": 1885, + "execution_count": 2233, "id": "151b9e3c", "metadata": {}, "outputs": [], @@ -2958,7 +2957,7 @@ }, { "cell_type": "code", - "execution_count": 1886, + "execution_count": 2234, "id": "77ce8548", "metadata": {}, "outputs": [], @@ -2969,7 +2968,7 @@ }, { "cell_type": "code", - "execution_count": 1887, + "execution_count": 2235, "id": "f1fc8425", "metadata": {}, "outputs": [], @@ -2986,7 +2985,7 @@ }, { "cell_type": "code", - "execution_count": 1888, + "execution_count": 2236, "id": "f265860d", "metadata": {}, "outputs": [], @@ -3002,7 +3001,7 @@ }, { "cell_type": "code", - "execution_count": 1889, + "execution_count": 2237, "id": "e38d99cf", "metadata": {}, "outputs": [], @@ -3015,7 +3014,7 @@ }, { "cell_type": "code", - "execution_count": 1890, + "execution_count": 2238, "id": "259a0f53", "metadata": {}, "outputs": [], @@ -3026,7 +3025,7 @@ }, { "cell_type": "code", - "execution_count": 1891, + "execution_count": 2239, "id": "f61d110c", "metadata": {}, "outputs": [], @@ -3056,7 +3055,7 @@ }, { "cell_type": "code", - "execution_count": 1892, + "execution_count": 2240, "id": "7438435e", "metadata": {}, "outputs": [], @@ -3066,7 +3065,7 @@ }, { "cell_type": "code", - "execution_count": 1893, + "execution_count": 2241, "id": "e518de83", "metadata": {}, "outputs": [], @@ -3092,7 +3091,7 @@ }, { "cell_type": "code", - "execution_count": 1894, + "execution_count": 2242, "id": "919e600a", "metadata": {}, "outputs": [ @@ -3106,10 +3105,10 @@ { "data": { "text/plain": [ - "'Cookie was set at time 22:23:30.964804'" + "'Cookie was set at time 09:00:22.051066'" ] }, - "execution_count": 1894, + "execution_count": 2242, "metadata": {}, "output_type": "execute_result" } @@ -3128,7 +3127,7 @@ }, { "cell_type": "code", - "execution_count": 1895, + "execution_count": 2243, "id": "8816f277", "metadata": {}, "outputs": [], @@ -3141,7 +3140,7 @@ }, { "cell_type": "code", - "execution_count": 1896, + "execution_count": 2244, "id": "e9535978", "metadata": {}, "outputs": [], @@ -3161,7 +3160,7 @@ }, { "cell_type": "code", - "execution_count": 1897, + "execution_count": 2245, "id": "9a70842e", "metadata": {}, "outputs": [], @@ -3176,7 +3175,7 @@ }, { "cell_type": "code", - "execution_count": 1898, + "execution_count": 2246, "id": "8bb51383", "metadata": {}, "outputs": [], @@ -3187,7 +3186,7 @@ }, { "cell_type": "code", - "execution_count": 1899, + "execution_count": 2247, "id": "b31de65a", "metadata": {}, "outputs": [], @@ -3202,7 +3201,7 @@ }, { "cell_type": "code", - "execution_count": 1900, + "execution_count": 2248, "id": "d4ff5919", "metadata": {}, "outputs": [], @@ -3213,7 +3212,7 @@ }, { "cell_type": "code", - "execution_count": 1901, + "execution_count": 2249, "id": "1960d7ff", "metadata": {}, "outputs": [], @@ -3229,7 +3228,7 @@ }, { "cell_type": "code", - "execution_count": 1902, + "execution_count": 2250, "id": "83a20f93", "metadata": {}, "outputs": [], @@ -3250,7 +3249,7 @@ }, { "cell_type": "code", - "execution_count": 1903, + "execution_count": 2251, "id": "a9e66b7d", "metadata": {}, "outputs": [], @@ -3278,7 +3277,7 @@ }, { "cell_type": "code", - "execution_count": 1904, + "execution_count": 2260, "id": "4ea66093", "metadata": {}, "outputs": [ @@ -3292,9 +3291,9 @@ } ], "source": [ - "async def my_slow_task():\n", + "def my_slow_task():\n", " print('Starting slow task') \n", - " await sleep(3)\n", + " time.sleep(3)\n", " print('Finished slow task') \n", "\n", "@rt('/background')\n", @@ -3316,38 +3315,43 @@ }, { "cell_type": "code", - "execution_count": 1905, + "execution_count": 2261, "id": "1764a6aa", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Sleeping for 0s\n", + "Slept for 0s\n", + "Sleeping for 1s\n", + "Slept for 1s\n", + "Sleeping for 2s\n", + "Slept for 2s\n" + ] + } + ], "source": [ - "TASK_COUNTER = 0\n", - "\n", - "async def increment(amount):\n", - " global TASK_COUNTER\n", - " TASK_COUNTER += amount\n", - "\n", - "timestamp = datetime.now()\n", + "def increment(amount):\n", + " print(f'Sleeping for {amount}s') \n", + " time.sleep(amount)\n", + " print(f'Slept for {amount}s') \n", "\n", "@rt('/backgrounds')\n", "def get():\n", - " cts = Title(timestamp)\n", " tasks = BackgroundTasks()\n", " for i in range(3): tasks.add_task(increment, i)\n", - " return FtResponse(cts, background=tasks)\n", + " return FtResponse(P('BG Tasks'), background=tasks)\n", "\n", "r = cli.get('/backgrounds')\n", "\n", - "test_eq(r.status_code, 200)\n", - "txt = r.text\n", - "assert f'{timestamp}' in txt\n", - "# TASK_COUNTER won't assert==3 unless increment is run\n", - "assert TASK_COUNTER == 3\n" + "test_eq(r.status_code, 200)" ] }, { "cell_type": "code", - "execution_count": 1906, + "execution_count": 2254, "id": "9dc1025e", "metadata": {}, "outputs": [], @@ -3360,7 +3364,7 @@ }, { "cell_type": "code", - "execution_count": 1907, + "execution_count": 2255, "id": "5b67e014", "metadata": {}, "outputs": [], @@ -3374,7 +3378,7 @@ }, { "cell_type": "code", - "execution_count": 1908, + "execution_count": 2256, "id": "1f590f25", "metadata": {}, "outputs": [], @@ -3401,7 +3405,7 @@ }, { "cell_type": "code", - "execution_count": 1909, + "execution_count": 2257, "id": "d211e8e2", "metadata": {}, "outputs": [], From 713f0675c5aeca07ddb45a9cb3b44c8fbdbf5bca Mon Sep 17 00:00:00 2001 From: "Audrey M. Roy Greenfeld" Date: Thu, 20 Mar 2025 17:09:53 +1000 Subject: [PATCH 12/12] Refactor of BG task code examples for clarity Co-authored-by: Daniel Roy Greenfeld --- nbs/api/00_core.ipynb | 410 ++++++++++++++-------------- nbs/explains/background_tasks.ipynb | 87 ++++-- 2 files changed, 258 insertions(+), 239 deletions(-) diff --git a/nbs/api/00_core.ipynb b/nbs/api/00_core.ipynb index 539b41e6..4d1af4b8 100644 --- a/nbs/api/00_core.ipynb +++ b/nbs/api/00_core.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 2084, + "execution_count": null, "id": "fa505c58", "metadata": {}, "outputs": [], @@ -37,7 +37,7 @@ }, { "cell_type": "code", - "execution_count": 2085, + "execution_count": null, "id": "23503b9e", "metadata": {}, "outputs": [], @@ -71,7 +71,7 @@ }, { "cell_type": "code", - "execution_count": 2086, + "execution_count": null, "id": "7f5d0a72", "metadata": {}, "outputs": [], @@ -90,7 +90,7 @@ }, { "cell_type": "code", - "execution_count": 2087, + "execution_count": null, "id": "19d3f2a7", "metadata": {}, "outputs": [], @@ -111,7 +111,7 @@ }, { "cell_type": "code", - "execution_count": 2088, + "execution_count": null, "id": "e68a76c9", "metadata": {}, "outputs": [], @@ -124,7 +124,7 @@ }, { "cell_type": "code", - "execution_count": 2089, + "execution_count": null, "id": "5331a3e7", "metadata": {}, "outputs": [ @@ -134,7 +134,7 @@ "datetime.datetime(2025, 3, 20, 14, 0)" ] }, - "execution_count": 2089, + "execution_count": null, "metadata": {}, "output_type": "execute_result" } @@ -145,7 +145,7 @@ }, { "cell_type": "code", - "execution_count": 2090, + "execution_count": null, "id": "c40c9071", "metadata": {}, "outputs": [ @@ -155,7 +155,7 @@ "True" ] }, - "execution_count": 2090, + "execution_count": null, "metadata": {}, "output_type": "execute_result" } @@ -166,7 +166,7 @@ }, { "cell_type": "code", - "execution_count": 2091, + "execution_count": null, "id": "7c820373", "metadata": {}, "outputs": [], @@ -180,7 +180,7 @@ }, { "cell_type": "code", - "execution_count": 2092, + "execution_count": null, "id": "442a5aac", "metadata": {}, "outputs": [ @@ -190,7 +190,7 @@ "'Snake-Case'" ] }, - "execution_count": 2092, + "execution_count": null, "metadata": {}, "output_type": "execute_result" } @@ -201,7 +201,7 @@ }, { "cell_type": "code", - "execution_count": 2093, + "execution_count": null, "id": "25f3b8f8", "metadata": {}, "outputs": [], @@ -230,7 +230,7 @@ }, { "cell_type": "code", - "execution_count": 2094, + "execution_count": null, "id": "5b4b5d95", "metadata": {}, "outputs": [], @@ -252,7 +252,7 @@ }, { "cell_type": "code", - "execution_count": 2095, + "execution_count": null, "id": "36e2cac0", "metadata": {}, "outputs": [ @@ -262,7 +262,7 @@ "HtmxHeaders(boosted=None, current_url=None, history_restore_request=None, prompt=None, request='1', target=None, trigger_name=None, trigger=None)" ] }, - "execution_count": 2095, + "execution_count": null, "metadata": {}, "output_type": "execute_result" } @@ -274,7 +274,7 @@ }, { "cell_type": "code", - "execution_count": 2096, + "execution_count": null, "id": "1d53e8e7", "metadata": {}, "outputs": [], @@ -293,7 +293,7 @@ }, { "cell_type": "code", - "execution_count": 2097, + "execution_count": null, "id": "5ab74473", "metadata": {}, "outputs": [], @@ -304,7 +304,7 @@ }, { "cell_type": "code", - "execution_count": 2098, + "execution_count": null, "id": "0afb520c", "metadata": {}, "outputs": [], @@ -324,7 +324,7 @@ }, { "cell_type": "code", - "execution_count": 2099, + "execution_count": null, "id": "95b1f5c9", "metadata": {}, "outputs": [], @@ -339,7 +339,7 @@ }, { "cell_type": "code", - "execution_count": 2100, + "execution_count": null, "id": "c58ccadb", "metadata": {}, "outputs": [], @@ -357,7 +357,7 @@ }, { "cell_type": "code", - "execution_count": 2101, + "execution_count": null, "id": "59757d76", "metadata": {}, "outputs": [], @@ -370,7 +370,7 @@ }, { "cell_type": "code", - "execution_count": 2102, + "execution_count": null, "id": "5fc04751", "metadata": {}, "outputs": [], @@ -382,7 +382,7 @@ }, { "cell_type": "code", - "execution_count": 2103, + "execution_count": null, "id": "94e18161", "metadata": {}, "outputs": [], @@ -397,7 +397,7 @@ }, { "cell_type": "code", - "execution_count": 2104, + "execution_count": null, "id": "592d6c8e", "metadata": {}, "outputs": [ @@ -407,7 +407,7 @@ "'HX-Trigger-After-Settle'" ] }, - "execution_count": 2104, + "execution_count": null, "metadata": {}, "output_type": "execute_result" } @@ -418,7 +418,7 @@ }, { "cell_type": "code", - "execution_count": 2105, + "execution_count": null, "id": "f6a2e62e", "metadata": {}, "outputs": [], @@ -433,7 +433,7 @@ }, { "cell_type": "code", - "execution_count": 2106, + "execution_count": null, "id": "e89857eb", "metadata": {}, "outputs": [ @@ -443,7 +443,7 @@ "HttpHeader(k='HX-Trigger-After-Settle', v='hi')" ] }, - "execution_count": 2106, + "execution_count": null, "metadata": {}, "output_type": "execute_result" } @@ -454,7 +454,7 @@ }, { "cell_type": "code", - "execution_count": 2107, + "execution_count": null, "id": "56cc589f", "metadata": {}, "outputs": [], @@ -468,7 +468,7 @@ }, { "cell_type": "code", - "execution_count": 2108, + "execution_count": null, "id": "cb4ed4aa", "metadata": {}, "outputs": [], @@ -479,7 +479,7 @@ }, { "cell_type": "code", - "execution_count": 2109, + "execution_count": null, "id": "ffdde66f", "metadata": {}, "outputs": [], @@ -494,7 +494,7 @@ }, { "cell_type": "code", - "execution_count": 2110, + "execution_count": null, "id": "5fe74444", "metadata": {}, "outputs": [], @@ -508,7 +508,7 @@ }, { "cell_type": "code", - "execution_count": 2111, + "execution_count": null, "id": "cf80ea34", "metadata": {}, "outputs": [], @@ -522,7 +522,7 @@ }, { "cell_type": "code", - "execution_count": 2112, + "execution_count": null, "id": "42c9cea0", "metadata": {}, "outputs": [], @@ -543,7 +543,7 @@ }, { "cell_type": "code", - "execution_count": 2113, + "execution_count": null, "id": "33d3bc16", "metadata": {}, "outputs": [], @@ -561,7 +561,7 @@ }, { "cell_type": "code", - "execution_count": 2114, + "execution_count": null, "id": "2a8b10f4", "metadata": {}, "outputs": [ @@ -589,7 +589,7 @@ }, { "cell_type": "code", - "execution_count": 2115, + "execution_count": null, "id": "9246153f", "metadata": {}, "outputs": [ @@ -599,7 +599,7 @@ "\"['1', '2']\"" ] }, - "execution_count": 2115, + "execution_count": null, "metadata": {}, "output_type": "execute_result" } @@ -612,7 +612,7 @@ }, { "cell_type": "code", - "execution_count": 2116, + "execution_count": null, "id": "6775cbf8", "metadata": {}, "outputs": [], @@ -663,7 +663,7 @@ }, { "cell_type": "code", - "execution_count": 2117, + "execution_count": null, "id": "bf945ee8", "metadata": {}, "outputs": [ @@ -671,7 +671,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "[, , '1', HttpHeader(k='value1', v='value3')]\n" + "[, , '1', HttpHeader(k='value1', v='value3')]\n" ] } ], @@ -689,7 +689,7 @@ }, { "cell_type": "code", - "execution_count": 2118, + "execution_count": null, "id": "a3ded5ec", "metadata": {}, "outputs": [ @@ -697,7 +697,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "[, , '1', HttpHeader(k='value1', v='value3')]\n" + "[, , '1', HttpHeader(k='value1', v='value3')]\n" ] } ], @@ -715,7 +715,7 @@ }, { "cell_type": "code", - "execution_count": 2119, + "execution_count": null, "id": "7a661bfa", "metadata": {}, "outputs": [], @@ -733,7 +733,7 @@ }, { "cell_type": "code", - "execution_count": 2120, + "execution_count": null, "id": "2ee5adf1", "metadata": {}, "outputs": [], @@ -745,7 +745,7 @@ }, { "cell_type": "code", - "execution_count": 2121, + "execution_count": null, "id": "aacff5ac", "metadata": {}, "outputs": [], @@ -757,7 +757,7 @@ }, { "cell_type": "code", - "execution_count": 2122, + "execution_count": null, "id": "78c3c357", "metadata": {}, "outputs": [], @@ -777,7 +777,7 @@ }, { "cell_type": "code", - "execution_count": 2123, + "execution_count": null, "id": "f2277c02", "metadata": {}, "outputs": [], @@ -813,7 +813,7 @@ }, { "cell_type": "code", - "execution_count": 2124, + "execution_count": null, "id": "dcc15129", "metadata": {}, "outputs": [], @@ -847,7 +847,7 @@ }, { "cell_type": "code", - "execution_count": 2125, + "execution_count": null, "id": "983bcfe2", "metadata": {}, "outputs": [], @@ -863,7 +863,7 @@ }, { "cell_type": "code", - "execution_count": 2126, + "execution_count": null, "id": "5b0e7677", "metadata": {}, "outputs": [], @@ -876,7 +876,7 @@ }, { "cell_type": "code", - "execution_count": 2127, + "execution_count": null, "id": "0dd0a414", "metadata": {}, "outputs": [], @@ -903,7 +903,7 @@ }, { "cell_type": "code", - "execution_count": 2128, + "execution_count": null, "id": "03f4c639", "metadata": {}, "outputs": [], @@ -915,7 +915,7 @@ }, { "cell_type": "code", - "execution_count": 2129, + "execution_count": null, "id": "bdb2ac14", "metadata": {}, "outputs": [], @@ -928,7 +928,7 @@ }, { "cell_type": "code", - "execution_count": 2130, + "execution_count": null, "id": "b80ce139", "metadata": {}, "outputs": [], @@ -939,7 +939,7 @@ }, { "cell_type": "code", - "execution_count": 2131, + "execution_count": null, "id": "a27adc1e", "metadata": {}, "outputs": [], @@ -957,7 +957,7 @@ }, { "cell_type": "code", - "execution_count": 2132, + "execution_count": null, "id": "46614165", "metadata": {}, "outputs": [], @@ -971,7 +971,7 @@ }, { "cell_type": "code", - "execution_count": 2133, + "execution_count": null, "id": "c1707d59", "metadata": {}, "outputs": [], @@ -1009,7 +1009,7 @@ }, { "cell_type": "code", - "execution_count": 2134, + "execution_count": null, "id": "f1e3ed2d", "metadata": {}, "outputs": [], @@ -1020,7 +1020,7 @@ }, { "cell_type": "code", - "execution_count": 2135, + "execution_count": null, "id": "a407dc0e", "metadata": {}, "outputs": [], @@ -1039,7 +1039,7 @@ }, { "cell_type": "code", - "execution_count": 2136, + "execution_count": null, "id": "d36402e6", "metadata": {}, "outputs": [], @@ -1052,7 +1052,7 @@ }, { "cell_type": "code", - "execution_count": 2137, + "execution_count": null, "id": "7f49728d", "metadata": {}, "outputs": [], @@ -1069,7 +1069,7 @@ }, { "cell_type": "code", - "execution_count": 2138, + "execution_count": null, "id": "da449a7b", "metadata": {}, "outputs": [], @@ -1093,7 +1093,7 @@ }, { "cell_type": "code", - "execution_count": 2139, + "execution_count": null, "id": "0150aeec", "metadata": {}, "outputs": [], @@ -1106,7 +1106,7 @@ }, { "cell_type": "code", - "execution_count": 2140, + "execution_count": null, "id": "775fb66b", "metadata": {}, "outputs": [], @@ -1117,7 +1117,7 @@ }, { "cell_type": "code", - "execution_count": 2141, + "execution_count": null, "id": "968d9245", "metadata": {}, "outputs": [], @@ -1141,7 +1141,7 @@ }, { "cell_type": "code", - "execution_count": 2142, + "execution_count": null, "id": "eac44461", "metadata": {}, "outputs": [], @@ -1157,7 +1157,7 @@ }, { "cell_type": "code", - "execution_count": 2143, + "execution_count": null, "id": "1ba52822", "metadata": {}, "outputs": [], @@ -1170,7 +1170,7 @@ }, { "cell_type": "code", - "execution_count": 2144, + "execution_count": null, "id": "6d9cc95f", "metadata": {}, "outputs": [], @@ -1191,7 +1191,7 @@ }, { "cell_type": "code", - "execution_count": 2145, + "execution_count": null, "id": "fb97d46b", "metadata": {}, "outputs": [], @@ -1207,7 +1207,7 @@ }, { "cell_type": "code", - "execution_count": 2146, + "execution_count": null, "id": "8bd78eeb", "metadata": {}, "outputs": [], @@ -1224,7 +1224,7 @@ }, { "cell_type": "code", - "execution_count": 2147, + "execution_count": null, "id": "4771838e", "metadata": {}, "outputs": [ @@ -1234,7 +1234,7 @@ "'a2597a39-e99d-4460-90b9-2ca21e87bc8f'" ] }, - "execution_count": 2147, + "execution_count": null, "metadata": {}, "output_type": "execute_result" } @@ -1245,7 +1245,7 @@ }, { "cell_type": "code", - "execution_count": 2148, + "execution_count": null, "id": "042666e2", "metadata": {}, "outputs": [], @@ -1256,7 +1256,7 @@ }, { "cell_type": "code", - "execution_count": 2149, + "execution_count": null, "id": "d276fc71", "metadata": {}, "outputs": [], @@ -1273,7 +1273,7 @@ }, { "cell_type": "code", - "execution_count": 2150, + "execution_count": null, "id": "bc323fd4", "metadata": {}, "outputs": [], @@ -1301,7 +1301,7 @@ }, { "cell_type": "code", - "execution_count": 2151, + "execution_count": null, "id": "ff82dc78", "metadata": {}, "outputs": [], @@ -1311,7 +1311,7 @@ }, { "cell_type": "code", - "execution_count": 2152, + "execution_count": null, "id": "c0836a8f", "metadata": {}, "outputs": [], @@ -1330,7 +1330,7 @@ }, { "cell_type": "code", - "execution_count": 2153, + "execution_count": null, "id": "50ebb1ee", "metadata": {}, "outputs": [], @@ -1342,7 +1342,7 @@ }, { "cell_type": "code", - "execution_count": 2154, + "execution_count": null, "id": "f86690c4", "metadata": {}, "outputs": [], @@ -1358,7 +1358,7 @@ }, { "cell_type": "code", - "execution_count": 2155, + "execution_count": null, "id": "2c5285ae", "metadata": {}, "outputs": [], @@ -1380,7 +1380,7 @@ }, { "cell_type": "code", - "execution_count": 2156, + "execution_count": null, "id": "e0accf76", "metadata": {}, "outputs": [], @@ -1431,7 +1431,7 @@ }, { "cell_type": "code", - "execution_count": 2157, + "execution_count": null, "id": "246bd8d1", "metadata": {}, "outputs": [], @@ -1442,7 +1442,7 @@ }, { "cell_type": "code", - "execution_count": 2158, + "execution_count": null, "id": "ab73f7df", "metadata": {}, "outputs": [], @@ -1474,7 +1474,7 @@ }, { "cell_type": "code", - "execution_count": 2159, + "execution_count": null, "id": "3818575c", "metadata": {}, "outputs": [], @@ -1491,7 +1491,7 @@ }, { "cell_type": "code", - "execution_count": 2160, + "execution_count": null, "id": "669e76eb", "metadata": {}, "outputs": [], @@ -1506,7 +1506,7 @@ }, { "cell_type": "code", - "execution_count": 2161, + "execution_count": null, "id": "919618c3", "metadata": {}, "outputs": [], @@ -1523,7 +1523,7 @@ }, { "cell_type": "code", - "execution_count": 2162, + "execution_count": null, "id": "d2ecc738", "metadata": {}, "outputs": [], @@ -1536,7 +1536,7 @@ }, { "cell_type": "code", - "execution_count": 2163, + "execution_count": null, "id": "c0f13ece", "metadata": {}, "outputs": [], @@ -1548,7 +1548,7 @@ }, { "cell_type": "code", - "execution_count": 2164, + "execution_count": null, "id": "b218f738", "metadata": {}, "outputs": [ @@ -1558,7 +1558,7 @@ "'f_g'" ] }, - "execution_count": 2164, + "execution_count": null, "metadata": {}, "output_type": "execute_result" } @@ -1570,7 +1570,7 @@ }, { "cell_type": "code", - "execution_count": 2165, + "execution_count": null, "id": "72760b09", "metadata": {}, "outputs": [], @@ -1593,7 +1593,7 @@ }, { "cell_type": "code", - "execution_count": 2166, + "execution_count": null, "id": "f5cb2c2b", "metadata": {}, "outputs": [], @@ -1610,7 +1610,7 @@ }, { "cell_type": "code", - "execution_count": 2167, + "execution_count": null, "id": "e6ee3a86", "metadata": {}, "outputs": [ @@ -1620,7 +1620,7 @@ "'/foo?a=bar&b=1&b=2'" ] }, - "execution_count": 2167, + "execution_count": null, "metadata": {}, "output_type": "execute_result" } @@ -1635,7 +1635,7 @@ }, { "cell_type": "code", - "execution_count": 2168, + "execution_count": null, "id": "9b9f1f03", "metadata": {}, "outputs": [ @@ -1645,7 +1645,7 @@ "'/foo/bar?b=1&b=2'" ] }, - "execution_count": 2168, + "execution_count": null, "metadata": {}, "output_type": "execute_result" } @@ -1659,7 +1659,7 @@ }, { "cell_type": "code", - "execution_count": 2169, + "execution_count": null, "id": "3a348474", "metadata": {}, "outputs": [], @@ -1690,7 +1690,7 @@ }, { "cell_type": "code", - "execution_count": 2170, + "execution_count": null, "id": "8121968a", "metadata": {}, "outputs": [], @@ -1710,7 +1710,7 @@ }, { "cell_type": "code", - "execution_count": 2171, + "execution_count": null, "id": "b163c933", "metadata": {}, "outputs": [ @@ -1720,7 +1720,7 @@ "'test'" ] }, - "execution_count": 2171, + "execution_count": null, "metadata": {}, "output_type": "execute_result" } @@ -1750,7 +1750,7 @@ }, { "cell_type": "code", - "execution_count": 2172, + "execution_count": null, "id": "9abc3781", "metadata": {}, "outputs": [], @@ -1760,7 +1760,7 @@ }, { "cell_type": "code", - "execution_count": 2173, + "execution_count": null, "id": "645d8d95", "metadata": {}, "outputs": [], @@ -1770,7 +1770,7 @@ }, { "cell_type": "code", - "execution_count": 2174, + "execution_count": null, "id": "421262a8", "metadata": {}, "outputs": [ @@ -1787,7 +1787,7 @@ "'/foo?param=value'" ] }, - "execution_count": 2174, + "execution_count": null, "metadata": {}, "output_type": "execute_result" } @@ -1807,7 +1807,7 @@ }, { "cell_type": "code", - "execution_count": 2175, + "execution_count": null, "id": "2ebd6270", "metadata": {}, "outputs": [], @@ -1823,7 +1823,7 @@ }, { "cell_type": "code", - "execution_count": 2176, + "execution_count": null, "id": "8a6ed879", "metadata": {}, "outputs": [ @@ -1833,7 +1833,7 @@ "'Hi there'" ] }, - "execution_count": 2176, + "execution_count": null, "metadata": {}, "output_type": "execute_result" } @@ -1848,7 +1848,7 @@ }, { "cell_type": "code", - "execution_count": 2177, + "execution_count": null, "id": "a8e723fc", "metadata": {}, "outputs": [ @@ -1858,7 +1858,7 @@ "'Postal'" ] }, - "execution_count": 2177, + "execution_count": null, "metadata": {}, "output_type": "execute_result" } @@ -1872,7 +1872,7 @@ }, { "cell_type": "code", - "execution_count": 2178, + "execution_count": null, "id": "274666db", "metadata": {}, "outputs": [ @@ -1882,7 +1882,7 @@ "'testserver'" ] }, - "execution_count": 2178, + "execution_count": null, "metadata": {}, "output_type": "execute_result" } @@ -1896,7 +1896,7 @@ }, { "cell_type": "code", - "execution_count": 2179, + "execution_count": null, "id": "72428702", "metadata": {}, "outputs": [ @@ -1927,7 +1927,7 @@ }, { "cell_type": "code", - "execution_count": 2180, + "execution_count": null, "id": "36292bf3", "metadata": {}, "outputs": [ @@ -1937,7 +1937,7 @@ "'a yoyo'" ] }, - "execution_count": 2180, + "execution_count": null, "metadata": {}, "output_type": "execute_result" } @@ -1951,7 +1951,7 @@ }, { "cell_type": "code", - "execution_count": 2181, + "execution_count": null, "id": "0d813cd0", "metadata": {}, "outputs": [ @@ -1975,7 +1975,7 @@ }, { "cell_type": "code", - "execution_count": 2182, + "execution_count": null, "id": "52dde0da", "metadata": {}, "outputs": [ @@ -2001,7 +2001,7 @@ }, { "cell_type": "code", - "execution_count": 2183, + "execution_count": null, "id": "d84d98f1", "metadata": {}, "outputs": [ @@ -2025,7 +2025,7 @@ }, { "cell_type": "code", - "execution_count": 2184, + "execution_count": null, "id": "64343367", "metadata": {}, "outputs": [ @@ -2035,7 +2035,7 @@ "'Good day to you, Alexis!'" ] }, - "execution_count": 2184, + "execution_count": null, "metadata": {}, "output_type": "execute_result" } @@ -2048,7 +2048,7 @@ }, { "cell_type": "code", - "execution_count": 2185, + "execution_count": null, "id": "29a96715", "metadata": {}, "outputs": [ @@ -2072,7 +2072,7 @@ }, { "cell_type": "code", - "execution_count": 2186, + "execution_count": null, "id": "54266599", "metadata": {}, "outputs": [ @@ -2082,7 +2082,7 @@ "'http://testserver/user/Alexis; http://testserver/hostie'" ] }, - "execution_count": 2186, + "execution_count": null, "metadata": {}, "output_type": "execute_result" } @@ -2096,7 +2096,7 @@ }, { "cell_type": "code", - "execution_count": 2187, + "execution_count": null, "id": "162b811e", "metadata": {}, "outputs": [ @@ -2121,7 +2121,7 @@ }, { "cell_type": "code", - "execution_count": 2188, + "execution_count": null, "id": "db0281cf", "metadata": {}, "outputs": [], @@ -2131,7 +2131,7 @@ }, { "cell_type": "code", - "execution_count": 2189, + "execution_count": null, "id": "b827960a", "metadata": {}, "outputs": [], @@ -2161,7 +2161,7 @@ }, { "cell_type": "code", - "execution_count": 2190, + "execution_count": null, "id": "381a1b68", "metadata": {}, "outputs": [], @@ -2173,7 +2173,7 @@ }, { "cell_type": "code", - "execution_count": 2191, + "execution_count": null, "id": "6b022adb", "metadata": {}, "outputs": [], @@ -2188,7 +2188,7 @@ }, { "cell_type": "code", - "execution_count": 2192, + "execution_count": null, "id": "1b4b5717", "metadata": {}, "outputs": [], @@ -2199,7 +2199,7 @@ }, { "cell_type": "code", - "execution_count": 2193, + "execution_count": null, "id": "dd017867", "metadata": {}, "outputs": [], @@ -2219,7 +2219,7 @@ }, { "cell_type": "code", - "execution_count": 2194, + "execution_count": null, "id": "52d9a3f2", "metadata": {}, "outputs": [], @@ -2238,7 +2238,7 @@ }, { "cell_type": "code", - "execution_count": 2195, + "execution_count": null, "id": "bb8154cc", "metadata": {}, "outputs": [ @@ -2248,7 +2248,7 @@ "'got sub/a.b'" ] }, - "execution_count": 2195, + "execution_count": null, "metadata": {}, "output_type": "execute_result" } @@ -2264,7 +2264,7 @@ }, { "cell_type": "code", - "execution_count": 2196, + "execution_count": null, "id": "6bc9564c", "metadata": {}, "outputs": [], @@ -2274,7 +2274,7 @@ }, { "cell_type": "code", - "execution_count": 2197, + "execution_count": null, "id": "b8be4ef3", "metadata": {}, "outputs": [], @@ -2309,7 +2309,7 @@ }, { "cell_type": "code", - "execution_count": 2198, + "execution_count": null, "id": "5f045cf9", "metadata": {}, "outputs": [], @@ -2326,7 +2326,7 @@ }, { "cell_type": "code", - "execution_count": 2199, + "execution_count": null, "id": "52f9f934", "metadata": {}, "outputs": [], @@ -2338,7 +2338,7 @@ }, { "cell_type": "code", - "execution_count": 2200, + "execution_count": null, "id": "26d316b6", "metadata": {}, "outputs": [], @@ -2349,7 +2349,7 @@ }, { "cell_type": "code", - "execution_count": 2201, + "execution_count": null, "id": "704aeba6", "metadata": {}, "outputs": [], @@ -2360,7 +2360,7 @@ }, { "cell_type": "code", - "execution_count": 2202, + "execution_count": null, "id": "5fa127d5", "metadata": {}, "outputs": [], @@ -2371,7 +2371,7 @@ }, { "cell_type": "code", - "execution_count": 2203, + "execution_count": null, "id": "356db6c0", "metadata": {}, "outputs": [], @@ -2389,7 +2389,7 @@ }, { "cell_type": "code", - "execution_count": 2204, + "execution_count": null, "id": "a3596991", "metadata": {}, "outputs": [], @@ -2403,7 +2403,7 @@ }, { "cell_type": "code", - "execution_count": 2205, + "execution_count": null, "id": "fdb9239c", "metadata": {}, "outputs": [], @@ -2418,7 +2418,7 @@ }, { "cell_type": "code", - "execution_count": 2206, + "execution_count": null, "id": "3366d88f", "metadata": {}, "outputs": [], @@ -2455,7 +2455,7 @@ }, { "cell_type": "code", - "execution_count": 2207, + "execution_count": null, "id": "fcc024e6", "metadata": {}, "outputs": [], @@ -2465,7 +2465,7 @@ }, { "cell_type": "code", - "execution_count": 2208, + "execution_count": null, "id": "48f0a45e", "metadata": {}, "outputs": [], @@ -2482,7 +2482,7 @@ }, { "cell_type": "code", - "execution_count": 2209, + "execution_count": null, "id": "73363a37", "metadata": {}, "outputs": [], @@ -2498,7 +2498,7 @@ }, { "cell_type": "code", - "execution_count": 2210, + "execution_count": null, "id": "0aeaac36", "metadata": {}, "outputs": [], @@ -2513,7 +2513,7 @@ }, { "cell_type": "code", - "execution_count": 2211, + "execution_count": null, "id": "4034ee37", "metadata": {}, "outputs": [], @@ -2524,7 +2524,7 @@ }, { "cell_type": "code", - "execution_count": 2212, + "execution_count": null, "id": "04881594", "metadata": {}, "outputs": [ @@ -2549,7 +2549,7 @@ }, { "cell_type": "code", - "execution_count": 2213, + "execution_count": null, "id": "28a99667", "metadata": {}, "outputs": [ @@ -2570,7 +2570,7 @@ }, { "cell_type": "code", - "execution_count": 2214, + "execution_count": null, "id": "5bce33f0", "metadata": {}, "outputs": [ @@ -2587,7 +2587,7 @@ "'Session time: 2025-03-20 09:00:21.844393'" ] }, - "execution_count": 2214, + "execution_count": null, "metadata": {}, "output_type": "execute_result" } @@ -2610,7 +2610,7 @@ }, { "cell_type": "code", - "execution_count": 2215, + "execution_count": null, "id": "249fce31", "metadata": {}, "outputs": [], @@ -2630,7 +2630,7 @@ }, { "cell_type": "code", - "execution_count": 2216, + "execution_count": null, "id": "4d17ab9c", "metadata": {}, "outputs": [ @@ -2652,7 +2652,7 @@ }, { "cell_type": "code", - "execution_count": 2217, + "execution_count": null, "id": "102c17ab", "metadata": {}, "outputs": [], @@ -2669,7 +2669,7 @@ }, { "cell_type": "code", - "execution_count": 2218, + "execution_count": null, "id": "85bf2984", "metadata": {}, "outputs": [], @@ -2680,7 +2680,7 @@ }, { "cell_type": "code", - "execution_count": 2219, + "execution_count": null, "id": "e4fc13b0", "metadata": {}, "outputs": [], @@ -2690,7 +2690,7 @@ }, { "cell_type": "code", - "execution_count": 2220, + "execution_count": null, "id": "1f11eab1", "metadata": {}, "outputs": [], @@ -2706,7 +2706,7 @@ }, { "cell_type": "code", - "execution_count": 2221, + "execution_count": null, "id": "063b7f43", "metadata": {}, "outputs": [], @@ -2723,7 +2723,7 @@ }, { "cell_type": "code", - "execution_count": 2222, + "execution_count": null, "id": "efd6fc6c", "metadata": {}, "outputs": [], @@ -2740,7 +2740,7 @@ }, { "cell_type": "code", - "execution_count": 2223, + "execution_count": null, "id": "32520197", "metadata": {}, "outputs": [], @@ -2765,7 +2765,7 @@ }, { "cell_type": "code", - "execution_count": 2224, + "execution_count": null, "id": "d5223a9a", "metadata": {}, "outputs": [], @@ -2783,7 +2783,7 @@ }, { "cell_type": "code", - "execution_count": 2225, + "execution_count": null, "id": "0f08cb5e", "metadata": {}, "outputs": [], @@ -2831,7 +2831,7 @@ }, { "cell_type": "code", - "execution_count": 2226, + "execution_count": null, "id": "22e17ec8", "metadata": {}, "outputs": [], @@ -2841,7 +2841,7 @@ }, { "cell_type": "code", - "execution_count": 2227, + "execution_count": null, "id": "61030ee4", "metadata": {}, "outputs": [], @@ -2865,7 +2865,7 @@ }, { "cell_type": "code", - "execution_count": 2228, + "execution_count": null, "id": "cd413b0d", "metadata": {}, "outputs": [], @@ -2876,7 +2876,7 @@ }, { "cell_type": "code", - "execution_count": 2229, + "execution_count": null, "id": "8c265ff8", "metadata": {}, "outputs": [], @@ -2894,7 +2894,7 @@ }, { "cell_type": "code", - "execution_count": 2230, + "execution_count": null, "id": "8f7c5710", "metadata": {}, "outputs": [], @@ -2910,7 +2910,7 @@ }, { "cell_type": "code", - "execution_count": 2231, + "execution_count": null, "id": "1236de78", "metadata": {}, "outputs": [], @@ -2923,7 +2923,7 @@ }, { "cell_type": "code", - "execution_count": 2232, + "execution_count": null, "id": "02a4e649", "metadata": {}, "outputs": [], @@ -2933,7 +2933,7 @@ }, { "cell_type": "code", - "execution_count": 2233, + "execution_count": null, "id": "151b9e3c", "metadata": {}, "outputs": [], @@ -2957,7 +2957,7 @@ }, { "cell_type": "code", - "execution_count": 2234, + "execution_count": null, "id": "77ce8548", "metadata": {}, "outputs": [], @@ -2968,7 +2968,7 @@ }, { "cell_type": "code", - "execution_count": 2235, + "execution_count": null, "id": "f1fc8425", "metadata": {}, "outputs": [], @@ -2985,7 +2985,7 @@ }, { "cell_type": "code", - "execution_count": 2236, + "execution_count": null, "id": "f265860d", "metadata": {}, "outputs": [], @@ -3001,7 +3001,7 @@ }, { "cell_type": "code", - "execution_count": 2237, + "execution_count": null, "id": "e38d99cf", "metadata": {}, "outputs": [], @@ -3014,7 +3014,7 @@ }, { "cell_type": "code", - "execution_count": 2238, + "execution_count": null, "id": "259a0f53", "metadata": {}, "outputs": [], @@ -3025,7 +3025,7 @@ }, { "cell_type": "code", - "execution_count": 2239, + "execution_count": null, "id": "f61d110c", "metadata": {}, "outputs": [], @@ -3055,7 +3055,7 @@ }, { "cell_type": "code", - "execution_count": 2240, + "execution_count": null, "id": "7438435e", "metadata": {}, "outputs": [], @@ -3065,7 +3065,7 @@ }, { "cell_type": "code", - "execution_count": 2241, + "execution_count": null, "id": "e518de83", "metadata": {}, "outputs": [], @@ -3091,7 +3091,7 @@ }, { "cell_type": "code", - "execution_count": 2242, + "execution_count": null, "id": "919e600a", "metadata": {}, "outputs": [ @@ -3108,7 +3108,7 @@ "'Cookie was set at time 09:00:22.051066'" ] }, - "execution_count": 2242, + "execution_count": null, "metadata": {}, "output_type": "execute_result" } @@ -3127,7 +3127,7 @@ }, { "cell_type": "code", - "execution_count": 2243, + "execution_count": null, "id": "8816f277", "metadata": {}, "outputs": [], @@ -3140,7 +3140,7 @@ }, { "cell_type": "code", - "execution_count": 2244, + "execution_count": null, "id": "e9535978", "metadata": {}, "outputs": [], @@ -3160,7 +3160,7 @@ }, { "cell_type": "code", - "execution_count": 2245, + "execution_count": null, "id": "9a70842e", "metadata": {}, "outputs": [], @@ -3175,7 +3175,7 @@ }, { "cell_type": "code", - "execution_count": 2246, + "execution_count": null, "id": "8bb51383", "metadata": {}, "outputs": [], @@ -3186,7 +3186,7 @@ }, { "cell_type": "code", - "execution_count": 2247, + "execution_count": null, "id": "b31de65a", "metadata": {}, "outputs": [], @@ -3201,7 +3201,7 @@ }, { "cell_type": "code", - "execution_count": 2248, + "execution_count": null, "id": "d4ff5919", "metadata": {}, "outputs": [], @@ -3212,7 +3212,7 @@ }, { "cell_type": "code", - "execution_count": 2249, + "execution_count": null, "id": "1960d7ff", "metadata": {}, "outputs": [], @@ -3228,7 +3228,7 @@ }, { "cell_type": "code", - "execution_count": 2250, + "execution_count": null, "id": "83a20f93", "metadata": {}, "outputs": [], @@ -3249,7 +3249,7 @@ }, { "cell_type": "code", - "execution_count": 2251, + "execution_count": null, "id": "a9e66b7d", "metadata": {}, "outputs": [], @@ -3277,7 +3277,7 @@ }, { "cell_type": "code", - "execution_count": 2260, + "execution_count": null, "id": "4ea66093", "metadata": {}, "outputs": [ @@ -3315,7 +3315,7 @@ }, { "cell_type": "code", - "execution_count": 2261, + "execution_count": null, "id": "1764a6aa", "metadata": {}, "outputs": [ @@ -3351,7 +3351,7 @@ }, { "cell_type": "code", - "execution_count": 2254, + "execution_count": null, "id": "9dc1025e", "metadata": {}, "outputs": [], @@ -3364,7 +3364,7 @@ }, { "cell_type": "code", - "execution_count": 2255, + "execution_count": null, "id": "5b67e014", "metadata": {}, "outputs": [], @@ -3378,7 +3378,7 @@ }, { "cell_type": "code", - "execution_count": 2256, + "execution_count": null, "id": "1f590f25", "metadata": {}, "outputs": [], @@ -3405,7 +3405,7 @@ }, { "cell_type": "code", - "execution_count": 2257, + "execution_count": null, "id": "d211e8e2", "metadata": {}, "outputs": [], @@ -3425,21 +3425,9 @@ ], "metadata": { "kernelspec": { - "display_name": ".venv", + "display_name": "python3", "language": "python", "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.12.6" } }, "nbformat": 4, diff --git a/nbs/explains/background_tasks.ipynb b/nbs/explains/background_tasks.ipynb index 059752b2..c16123ea 100644 --- a/nbs/explains/background_tasks.ipynb +++ b/nbs/explains/background_tasks.ipynb @@ -70,8 +70,8 @@ "```\n", "\n", "1. `counter` is our task function. There is nothing special about it, although it is a good practice for its arguments to be serializable as JSON\n", - "2. We use `starlette.background.BackgroundTask` to turn the task_counter function into a background task\n", - "3. `FtResponse` is called explicitly so we can attack the task to its background. Normally we don't need to call `FtResponse` explicitly, setting background tasks is the exception." + "2. We use `starlette.background.BackgroundTask` to turn `counter()` into a background task\n", + "3. `FtResponse` is called explicitly so we can attach `task`. Normally we don't need to call `FtResponse` explicitly; setting background tasks is the exception." ] }, { @@ -85,8 +85,20 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Let's imagine that we are accessing a slow-to-process critical service. We don't want our users to have to wait. While we could set up SSE to notify on completion, instead we decide to periodically check to see if the status of their record has changed.\n", - "\n", + "Let's imagine that we are accessing a slow-to-process critical service. We don't want our users to have to wait. While we could set up SSE to notify on completion, instead we decide to periodically check to see if the status of their record has changed." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Simulated Slow API Service" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ "First, create a very simple slow timestamp API. All it does is stall requests for a few seconds before returning JSON containing timestamps." ] }, @@ -103,18 +115,28 @@ "\n", "@rt('/slow/{ts}')\n", "def slow(ts: int):\n", - " sleep(3)\n", - " return dict(ts=ts, response_ts=int(time()))\n", + " sleep(3) # <1>\n", + " return dict(request_time=ts, response_time=int(time())) # <2>\n", "\n", "serve(port=8123)\n", - "```" + "```\n", + "\n", + "1. This represents slow processing.\n", + "2. Returns both the task's original timestamp and the time after completion" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Now let's create a website that uses this API to fetch the timestamp from the glacially slow service." + "### Main FastHTML app" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now let's create a user-facing app that uses this API to fetch the timestamp from the glacially slow service." ] }, { @@ -133,44 +155,53 @@ "\n", "db = database(':memory:')\n", "\n", - "class Moment: ts: int; response_ts: int\n", - "moments = db.create(Moment, pk='ts')\n", + "class TStamp: # <1>\n", + " request_time: int # <2> \n", + " response_time: int # <3>\n", + "\n", + "tstamps = db.create(TStamp, pk='request_time')\n", "\n", - "def task_submit(ts:int): # <1>\n", - " # This task function calls the slow service, representing a slow process that\n", - " # we've encapsulated in a function.\n", + "def task_submit(request_time: int): # <4>\n", " client = httpx.Client()\n", - " r = client.post(f'http://127.0.0.1:8123/slow/{ts}')\n", - " moments.insert(**r.json())\n", + " response = client.post(f'http://127.0.0.1:8123/slow/{request_time}') # <5>\n", + " tstamps.insert(**response.json()) # <6>\n", "\n", "@rt\n", "def submit():\n", - " ts = int(time.time())\n", - " task = BackgroundTask(task_submit, ts=ts) # <2> \n", - " cts = P(f'Submitted: {ts}')\n", - " return FtResponse(cts, background=task) # <3> \n", + " \"\"\"Route that initiates a background task and returns immediately.\"\"\"\n", + " request_time = int(time.time())\n", + " resp_content = P(f'Request submitted at: {request_time}') \n", + " task = BackgroundTask(task_submit, request_time=request_time) # <7>\n", + " return FtResponse(resp_content, background=task) # <8>\n", "\n", "@rt\n", - "def showmoments(): return Ul(*[Li(m) for m in moments()])\n", + "def show_tstamps(): return Ul(*[Li(t) for t in tstamps()]) # <9> \n", "\n", "@rt\n", "def index():\n", " return Titled('Background Task Dashboard',\n", - " P(Button('Press to call slow service', # <4> \n", + " P(Button('Press to call slow service', # <10> \n", " hx_post='/submit', hx_target='#res')),\n", + " H2('Responses from Tasks'),\n", " P('', id='res'),\n", - " Div(Ul(*[Li(m) for m in moments()]),\n", - " hx_get=showmoments, hx_trigger='every 5s'), # <5>\n", + " Div(Ul(*[Li(t) for t in tstamps()]),\n", + " hx_get=show_tstamps, hx_trigger='every 5s'), # <11>\n", " )\n", "\n", "serve()\n", "```\n", "\n", - "1. It is common, but not necessary to prefix task functions with 'task_'\n", - "2. Create a background task by passing in the function to a BackgroundTask object, followed by any arguments.\n", - "3. In FtResponse, use the background keyword argument to set the task to be run after the HTTP Response is generated.\n", - "4. When this button is pressed, the 'submit' handler will respond instantly. The task_submit function will insert the slow API response into the db later. \n", - "5. Every 5 seconds get the moments stored in the DB." + "1. Tracks when requests are sent and responses received\n", + "2. When the request was initiated\n", + "3. When the response was received\n", + "4. Task function calling slow service to be run in the background of a route handler. It is common but not necessary to prefix task functions with 'task_'\n", + "5. Call the slow API service (simulating a time-consuming operation)\n", + "6. Store both timestamps in our database\n", + "7. Create a background task by passing in the function to a BackgroundTask object, followed by any arguments.\n", + "8. In FtResponse, use the background keyword argument to set the task to be run after the HTTP response is generated.\n", + "9. Endpoint that displays all recorded timestamp pairs.\n", + "10. When this button is pressed, the 'submit' handler will respond instantly. The task_submit function will insert the slow API response into the db later. \n", + "11. Every 5 seconds get the tstamps stored in the DB." ] }, {