diff --git a/notebooks/cloud-functions-template/notebook.ipynb b/notebooks/cloud-functions-template/notebook.ipynb
index c9412c70..4cbc7123 100644
--- a/notebooks/cloud-functions-template/notebook.ipynb
+++ b/notebooks/cloud-functions-template/notebook.ipynb
@@ -1,383 +1,305 @@
{
- "cells": [
- {
- "attachments": {},
- "cell_type": "markdown",
- "id": "8ba141c2-e0c2-4723-b782-c924bc7b294c",
- "metadata": {
- "execution": {
- "iopub.execute_input": "2024-10-30T02:39:51.423430Z",
- "iopub.status.busy": "2024-10-30T02:39:51.423022Z",
- "iopub.status.idle": "2024-10-30T02:39:51.432609Z",
- "shell.execute_reply": "2024-10-30T02:39:51.431032Z",
- "shell.execute_reply.started": "2024-10-30T02:39:51.423395Z"
- },
- "language": "python"
- },
- "source": [
- "
"
- ]
- },
- {
- "attachments": {},
- "cell_type": "markdown",
- "id": "a5564913-7ff8-41bf-b64b-b67971c63fae",
- "metadata": {
- "language": "python"
- },
- "source": [
- "This Jupyter notebook will help you build your first Cloud Function, showcasing how to leverage the ultra-fast queries of SingleStore to build a responsive API server using FastAPI\n"
- ]
- },
- {
- "attachments": {},
- "cell_type": "markdown",
- "id": "1e394195-29b4-403c-9abf-5d7731349eb6",
- "metadata": {
- "language": "python"
- },
- "source": [
- "## Create some simple tables\n",
- "\n",
- "This setup establishes a basic relational structure to store some items information."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "a17bdd3a-16b3-4e19-8a56-6566a169eccb",
- "metadata": {
- "execution": {
- "iopub.execute_input": "2024-10-31T21:10:59.634455Z",
- "iopub.status.busy": "2024-10-31T21:10:59.634027Z",
- "iopub.status.idle": "2024-10-31T21:11:02.330513Z",
- "shell.execute_reply": "2024-10-31T21:11:02.329128Z",
- "shell.execute_reply.started": "2024-10-31T21:10:59.634415Z"
- },
- "language": "python",
- "trusted": true
- },
- "outputs": [],
- "source": [
- "%%sql \n",
- "DROP TABLE IF EXISTS items;\n",
- "\n",
- "CREATE TABLE IF NOT EXISTS \n",
- "items (\n",
- " id INT PRIMARY KEY,\n",
- " name VARCHAR(255),\n",
- " price FLOAT\n",
- ");"
- ]
- },
- {
- "attachments": {},
- "cell_type": "markdown",
- "id": "af6e2618-de97-4397-b0d2-23e4a4df1d83",
- "metadata": {
- "language": "python"
- },
- "source": [
- "## Create a Connection Pool\n",
- "\n",
- "To run multiple simultaneous queries, we use sqlalchemy to create a pool of sql connections to the workspace you have selected. We also define a method to execute queries and transactions using a connection from this pool."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 5,
- "id": "f485e71b-2b05-4696-b22a-cf046fd83090",
- "metadata": {
- "execution": {
- "iopub.execute_input": "2024-10-31T21:11:03.622937Z",
- "iopub.status.busy": "2024-10-31T21:11:03.622443Z",
- "iopub.status.idle": "2024-10-31T21:11:03.674633Z",
- "shell.execute_reply": "2024-10-31T21:11:03.674106Z",
- "shell.execute_reply.started": "2024-10-31T21:11:03.622908Z"
- },
- "language": "python",
- "trusted": true
- },
- "outputs": [],
- "source": [
- "from sqlalchemy import create_engine, text\n",
- "import requests\n",
- "\n",
- "ca_cert_url = \"https://portal.singlestore.com/static/ca/singlestore_bundle.pem\"\n",
- "ca_cert_path = \"/tmp/singlestore_bundle.pem\"\n",
- "\n",
- "response = requests.get(ca_cert_url)\n",
- "with open(ca_cert_path, \"wb\") as f:\n",
- " f.write(response.content)\n",
- "\n",
- "sql_connection_string = connection_url.replace(\"singlestoredb\", \"mysql+pymysql\")\n",
- "engine = create_engine(\n",
- " f\"{sql_connection_string}?ssl_ca={ca_cert_path}\",\n",
- " pool_size=10, # Maximum number of connections in the pool is 10\n",
- " max_overflow=5, # Allow up to 5 additional connections (temporary overflow)\n",
- " pool_timeout=30 # Wait up to 30 seconds for a connection from the pool\n",
- ")\n",
- "\n",
- "def execute_query(query: str):\n",
- " with engine.connect() as connection:\n",
- " return connection.execute(text(query))\n",
- "\n",
- "def execute_transaction(transactional_query: str):\n",
- " with engine.connect() as connection:\n",
- " transaction = connection.begin()\n",
- " try:\n",
- " result = connection.execute(text(transactional_query))\n",
- " transaction.commit()\n",
- " return result\n",
- " except Exception as e:\n",
- " transaction.rollback()\n",
- " raise e"
- ]
- },
- {
- "attachments": {},
- "cell_type": "markdown",
- "id": "ee9058a9-34a5-46fc-8b12-d30cbb8c3340",
- "metadata": {
- "execution": {
- "iopub.execute_input": "2024-10-30T02:43:23.166876Z",
- "iopub.status.busy": "2024-10-30T02:43:23.165872Z",
- "iopub.status.idle": "2024-10-30T02:43:23.251942Z",
- "shell.execute_reply": "2024-10-30T02:43:23.250339Z",
- "shell.execute_reply.started": "2024-10-30T02:43:23.166828Z"
- },
- "language": "python"
- },
- "source": [
- "## Setup Environment\n",
- "\n",
- "Lets setup the environment ro run a FastAPI app defining the Data Model and an executor to run the different requests in different threads simultaneously"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 6,
- "id": "66df8f0c-70c6-4f06-9e64-ef06961cca3a",
- "metadata": {
- "execution": {
- "iopub.execute_input": "2024-10-31T21:11:05.733684Z",
- "iopub.status.busy": "2024-10-31T21:11:05.733370Z",
- "iopub.status.idle": "2024-10-31T21:11:06.761268Z",
- "shell.execute_reply": "2024-10-31T21:11:06.760518Z",
- "shell.execute_reply.started": "2024-10-31T21:11:05.733658Z"
- },
- "language": "python",
- "trusted": true
- },
- "outputs": [],
- "source": [
- "from fastapi import FastAPI, HTTPException\n",
- "from pydantic import BaseModel\n",
- "from singlestoredb import connect\n",
- "from concurrent.futures import ThreadPoolExecutor\n",
- "import asyncio\n",
- "\n",
- "# Define the Type of the Data\n",
- "class Item(BaseModel):\n",
- " id: int\n",
- " name: str\n",
- " price: float\n",
- "\n",
- "# Create an executor that can execute queries on multiple threads simultaneously\n",
- "executor = ThreadPoolExecutor()\n",
- "def run_in_thread(fn, *args):\n",
- " loop = asyncio.get_event_loop()\n",
- " return loop.run_in_executor(executor, fn, *args)"
- ]
- },
- {
- "attachments": {},
- "cell_type": "markdown",
- "id": "96760949-5ab2-474d-80ca-d23b5dcc52f7",
- "metadata": {
- "language": "python"
- },
- "source": [
- "## Define FastAPI App\n",
- "\n",
- "Next, we will be defining a FastAPI app that can insert, query and delete data from your table"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 7,
- "id": "3087dbe6-57ce-4410-a42f-5b0fe90add90",
- "metadata": {
- "execution": {
- "iopub.execute_input": "2024-10-31T21:11:08.819261Z",
- "iopub.status.busy": "2024-10-31T21:11:08.818225Z",
- "iopub.status.idle": "2024-10-31T21:11:08.835578Z",
- "shell.execute_reply": "2024-10-31T21:11:08.834740Z",
- "shell.execute_reply.started": "2024-10-31T21:11:08.819223Z"
- },
- "language": "python",
- "trusted": true
- },
- "outputs": [],
- "source": [
- "app = FastAPI()\n",
- "\n",
- "# Get all items\n",
- "@app.get(\"/items\", response_model=list[Item])\n",
- "async def get_items():\n",
- " def get_items_query():\n",
- " result = execute_query(\"SELECT * FROM items;\")\n",
- " rows = result.fetchall()\n",
- " return [{\"id\": row[0], \"name\": row[1], \"price\": row[2]} for row in rows]\n",
- "\n",
- " try:\n",
- " return await run_in_thread(get_items_query)\n",
- " except Exception as e:\n",
- " raise HTTPException(status_code=500, detail=f\"Error fetching all items: {str(e)}\")\n",
- "\n",
- "# Insert an item\n",
- "@app.post(\"/items\", response_model=dict)\n",
- "async def create_item(item: Item):\n",
- " def insert_item_query():\n",
- " result = execute_transaction(f\"INSERT INTO items (id, name, price) VALUES ({item.id}, '{item.name}', {item.price})\")\n",
- " return {\"message\": f\"Item with id {item.id} inserted successfully\"}\n",
- "\n",
- " try:\n",
- " return await run_in_thread(insert_item_query)\n",
- " except Exception as e:\n",
- " raise HTTPException(status_code=500, detail=f\"Error while inserting item with id {item.id}: {str(e)}\")\n",
- "\n",
- "# Get item by id\n",
- "@app.get(\"/items/{item_id}\", response_model=Item)\n",
- "async def get_item(item_id: int):\n",
- " def get_item_query():\n",
- " result = execute_query(f\"SELECT * FROM items WHERE id={item_id}\")\n",
- " row = result.fetchone()\n",
- " if not row:\n",
- " raise HTTPException(status_code=404, detail=\"Item not found\")\n",
- " return {\"id\": row[0], \"name\": row[1], \"price\": row[2]}\n",
- "\n",
- " try:\n",
- " return await run_in_thread(get_item_query)\n",
- " except HTTPException as e:\n",
- " raise e\n",
- " except Exception as e:\n",
- " raise HTTPException(status_code=500, detail=f\"Error fetching item with id {item_id}: {str(e)}\")\n",
- "\n",
- "# Delete item by id\n",
- "@app.delete(\"/items/{item_id}\", response_model=dict)\n",
- "async def delete_item(item_id: int):\n",
- " def delete_item_query():\n",
- " result = execute_transaction(f\"DELETE FROM items WHERE id={item_id}\")\n",
- " return {\"message\": f\"number of rows deleted: {result.rowcount}\"}\n",
- "\n",
- " try:\n",
- " return await run_in_thread(delete_item_query)\n",
- " except Exception as e:\n",
- " raise HTTPException(status_code=500, detail=f\"Error deleting item with id {item_id}: {str(e)}\")\n"
- ]
- },
- {
- "attachments": {},
- "cell_type": "markdown",
- "id": "c3d9ed07-4b55-4d17-aabb-e11b399109d1",
- "metadata": {
- "execution": {
- "iopub.execute_input": "2024-10-30T06:46:49.037604Z",
- "iopub.status.busy": "2024-10-30T06:46:49.036914Z",
- "iopub.status.idle": "2024-10-30T06:46:49.040446Z",
- "shell.execute_reply": "2024-10-30T06:46:49.039675Z",
- "shell.execute_reply.started": "2024-10-30T06:46:49.037576Z"
- },
- "language": "python"
- },
- "source": [
- "## Start the FastAPI server\n",
- "\n",
- "The link at which the cloud function will be available interactively will be displayed."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "ff002c7d-9f1c-40e5-b82a-c9176251dc99",
- "metadata": {
- "execution": {
- "iopub.execute_input": "2024-10-31T21:11:11.019478Z",
- "iopub.status.busy": "2024-10-31T21:11:11.018896Z",
- "iopub.status.idle": "2024-10-31T21:11:11.092917Z",
- "shell.execute_reply": "2024-10-31T21:11:11.092175Z",
- "shell.execute_reply.started": "2024-10-31T21:11:11.019443Z"
- },
- "language": "python",
- "trusted": true
- },
- "outputs": [],
- "source": [
- "import singlestoredb.apps as apps\n",
- "connection_info = await apps.run_function_app(app)"
- ]
- },
- {
- "attachments": {},
- "cell_type": "markdown",
- "id": "fabe76b7-e6a0-43a0-8d9e-aa79bd7d3021",
- "metadata": {
- "execution": {
- "iopub.execute_input": "2024-10-30T06:48:34.463475Z",
- "iopub.status.busy": "2024-10-30T06:48:34.463090Z",
- "iopub.status.idle": "2024-10-30T06:48:34.469549Z",
- "shell.execute_reply": "2024-10-30T06:48:34.468268Z",
- "shell.execute_reply.started": "2024-10-30T06:48:34.463452Z"
- },
- "language": "python"
- },
- "source": [
- "## Publish Cloud Function\n",
- "\n",
- "After validating the Cloud Function interactively, you can publish it and use it as an API server for your data!"
- ]
- }
- ],
- "metadata": {
- "jupyterlab": {
- "notebooks": {
- "version_major": 6,
- "version_minor": 4
- }
- },
- "kernelspec": {
- "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.11.6"
- },
- "singlestore_cell_default_language": "python",
- "singlestore_connection": {
- "connectionID": "0ff5bb99-7f92-425a-9751-2c3d4b97a996",
- "defaultDatabase": ""
- },
- "singlestore_row_limit": 300
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "8ba141c2-e0c2-4723-b782-c924bc7b294c",
+ "metadata": {},
+ "source": [
+ ""
+ ]
},
- "nbformat": 4,
- "nbformat_minor": 5
+ {
+ "cell_type": "markdown",
+ "id": "e0f88a6f-2658-40b4-9356-935a09f5053e",
+ "metadata": {},
+ "source": [
+ "\n",
+ "
\n",
+ "
\n",
+ "
Note
\n",
+ "
This notebook can be run on a Free Starter Workspace. To create a Free Starter Workspace navigate to Start using the left nav. You can also use your existing Standard or Premium workspace with this Notebook.
\n",
+ "
\n",
+ "
"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "id": "a5564913-7ff8-41bf-b64b-b67971c63fae",
+ "metadata": {},
+ "source": [
+ "This Jupyter notebook will help you build your first Cloud Function, showcasing how to leverage the ultra-fast queries of SingleStore to build a responsive API server using FastAPI"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "id": "1e394195-29b4-403c-9abf-5d7731349eb6",
+ "metadata": {},
+ "source": [
+ "## Create some simple tables\n",
+ "\n",
+ "This setup establishes a basic relational structure to store some items information."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "id": "a17bdd3a-16b3-4e19-8a56-6566a169eccb",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "%%sql\n",
+ "DROP TABLE IF EXISTS items;\n",
+ "\n",
+ "CREATE TABLE IF NOT EXISTS\n",
+ "items (\n",
+ " id INT PRIMARY KEY,\n",
+ " name VARCHAR(255),\n",
+ " price FLOAT\n",
+ ");"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "id": "af6e2618-de97-4397-b0d2-23e4a4df1d83",
+ "metadata": {},
+ "source": [
+ "## Create a Connection Pool\n",
+ "\n",
+ "To run multiple simultaneous queries, we use sqlalchemy to create a pool of sql connections to the workspace you have selected. We also define a method to execute queries and transactions using a connection from this pool."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "id": "f485e71b-2b05-4696-b22a-cf046fd83090",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from sqlalchemy import create_engine, text\n",
+ "import requests\n",
+ "\n",
+ "ca_cert_url = \"https://portal.singlestore.com/static/ca/singlestore_bundle.pem\"\n",
+ "ca_cert_path = \"/tmp/singlestore_bundle.pem\"\n",
+ "\n",
+ "response = requests.get(ca_cert_url)\n",
+ "with open(ca_cert_path, \"wb\") as f:\n",
+ " f.write(response.content)\n",
+ "\n",
+ "sql_connection_string = connection_url.replace(\"singlestoredb\", \"mysql+pymysql\")\n",
+ "engine = create_engine(\n",
+ " f\"{sql_connection_string}?ssl_ca={ca_cert_path}\",\n",
+ " pool_size=10, # Maximum number of connections in the pool is 10\n",
+ " max_overflow=5, # Allow up to 5 additional connections (temporary overflow)\n",
+ " pool_timeout=30 # Wait up to 30 seconds for a connection from the pool\n",
+ ")\n",
+ "\n",
+ "def execute_query(query: str):\n",
+ " with engine.connect() as connection:\n",
+ " return connection.execute(text(query))\n",
+ "\n",
+ "def execute_transaction(transactional_query: str):\n",
+ " with engine.connect() as connection:\n",
+ " transaction = connection.begin()\n",
+ " try:\n",
+ " result = connection.execute(text(transactional_query))\n",
+ " transaction.commit()\n",
+ " return result\n",
+ " except Exception as e:\n",
+ " transaction.rollback()\n",
+ " raise e"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "id": "ee9058a9-34a5-46fc-8b12-d30cbb8c3340",
+ "metadata": {},
+ "source": [
+ "## Setup Environment\n",
+ "\n",
+ "Lets setup the environment ro run a FastAPI app defining the Data Model and an executor to run the different requests in different threads simultaneously"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "id": "66df8f0c-70c6-4f06-9e64-ef06961cca3a",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from fastapi import FastAPI, HTTPException\n",
+ "from pydantic import BaseModel\n",
+ "from singlestoredb import connect\n",
+ "from concurrent.futures import ThreadPoolExecutor\n",
+ "import asyncio\n",
+ "\n",
+ "# Define the Type of the Data\n",
+ "class Item(BaseModel):\n",
+ " id: int\n",
+ " name: str\n",
+ " price: float\n",
+ "\n",
+ "# Create an executor that can execute queries on multiple threads simultaneously\n",
+ "executor = ThreadPoolExecutor()\n",
+ "def run_in_thread(fn, *args):\n",
+ " loop = asyncio.get_event_loop()\n",
+ " return loop.run_in_executor(executor, fn, *args)"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "id": "96760949-5ab2-474d-80ca-d23b5dcc52f7",
+ "metadata": {},
+ "source": [
+ "## Define FastAPI App\n",
+ "\n",
+ "Next, we will be defining a FastAPI app that can insert, query and delete data from your table"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "id": "3087dbe6-57ce-4410-a42f-5b0fe90add90",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "app = FastAPI()\n",
+ "\n",
+ "# Get all items\n",
+ "@app.get(\"/items\", response_model=list[Item])\n",
+ "async def get_items():\n",
+ " def get_items_query():\n",
+ " result = execute_query(\"SELECT * FROM items;\")\n",
+ " rows = result.fetchall()\n",
+ " return [{\"id\": row[0], \"name\": row[1], \"price\": row[2]} for row in rows]\n",
+ "\n",
+ " try:\n",
+ " return await run_in_thread(get_items_query)\n",
+ " except Exception as e:\n",
+ " raise HTTPException(status_code=500, detail=f\"Error fetching all items: {str(e)}\")\n",
+ "\n",
+ "# Insert an item\n",
+ "@app.post(\"/items\", response_model=dict)\n",
+ "async def create_item(item: Item):\n",
+ " def insert_item_query():\n",
+ " result = execute_transaction(f\"INSERT INTO items (id, name, price) VALUES ({item.id}, '{item.name}', {item.price})\")\n",
+ " return {\"message\": f\"Item with id {item.id} inserted successfully\"}\n",
+ "\n",
+ " try:\n",
+ " return await run_in_thread(insert_item_query)\n",
+ " except Exception as e:\n",
+ " raise HTTPException(status_code=500, detail=f\"Error while inserting item with id {item.id}: {str(e)}\")\n",
+ "\n",
+ "# Get item by id\n",
+ "@app.get(\"/items/{item_id}\", response_model=Item)\n",
+ "async def get_item(item_id: int):\n",
+ " def get_item_query():\n",
+ " result = execute_query(f\"SELECT * FROM items WHERE id={item_id}\")\n",
+ " row = result.fetchone()\n",
+ " if not row:\n",
+ " raise HTTPException(status_code=404, detail=\"Item not found\")\n",
+ " return {\"id\": row[0], \"name\": row[1], \"price\": row[2]}\n",
+ "\n",
+ " try:\n",
+ " return await run_in_thread(get_item_query)\n",
+ " except HTTPException as e:\n",
+ " raise e\n",
+ " except Exception as e:\n",
+ " raise HTTPException(status_code=500, detail=f\"Error fetching item with id {item_id}: {str(e)}\")\n",
+ "\n",
+ "# Delete item by id\n",
+ "@app.delete(\"/items/{item_id}\", response_model=dict)\n",
+ "async def delete_item(item_id: int):\n",
+ " def delete_item_query():\n",
+ " result = execute_transaction(f\"DELETE FROM items WHERE id={item_id}\")\n",
+ " return {\"message\": f\"number of rows deleted: {result.rowcount}\"}\n",
+ "\n",
+ " try:\n",
+ " return await run_in_thread(delete_item_query)\n",
+ " except Exception as e:\n",
+ " raise HTTPException(status_code=500, detail=f\"Error deleting item with id {item_id}: {str(e)}\")"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "id": "c3d9ed07-4b55-4d17-aabb-e11b399109d1",
+ "metadata": {},
+ "source": [
+ "## Start the FastAPI server\n",
+ "\n",
+ "The link at which the cloud function will be available interactively will be displayed."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "id": "ff002c7d-9f1c-40e5-b82a-c9176251dc99",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import singlestoredb.apps as apps\n",
+ "connection_info = await apps.run_function_app(app)"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "id": "fabe76b7-e6a0-43a0-8d9e-aa79bd7d3021",
+ "metadata": {},
+ "source": [
+ "## Publish Cloud Function\n",
+ "\n",
+ "After validating the Cloud Function interactively, you can publish it and use it as an API server for your data!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "55513fb9-f288-4cf1-b371-a71439bb1a31",
+ "metadata": {},
+ "source": [
+ "\n",
+ ""
+ ]
+ }
+ ],
+ "metadata": {
+ "jupyterlab": {
+ "notebooks": {
+ "version_major": 6,
+ "version_minor": 4
+ }
+ },
+ "kernelspec": {
+ "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.11.6"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
}
diff --git a/notebooks/create-dash-app/notebook.ipynb b/notebooks/create-dash-app/notebook.ipynb
index e512ffa9..8aed0b8a 100644
--- a/notebooks/create-dash-app/notebook.ipynb
+++ b/notebooks/create-dash-app/notebook.ipynb
@@ -1,500 +1,389 @@
{
- "cells": [
- {
- "attachments": {},
- "cell_type": "markdown",
- "id": "24350541-570b-491c-be33-b32b46764cf0",
- "metadata": {
- "execution": {
- "iopub.execute_input": "2024-09-25T23:59:58.082056Z",
- "iopub.status.busy": "2024-09-25T23:59:58.081605Z",
- "iopub.status.idle": "2024-09-25T23:59:59.368148Z",
- "shell.execute_reply": "2024-09-25T23:59:59.361199Z",
- "shell.execute_reply.started": "2024-09-25T23:59:58.082036Z"
- },
- "language": "python"
- },
- "source": [
- ""
- ]
- },
- {
- "attachments": {},
- "cell_type": "markdown",
- "id": "df860ca4-6db8-4ded-a061-30be438c4add",
- "metadata": {
- "language": "python"
- },
- "source": [
- "This Jupyter notebook will help you build your first real time Dashboard, showcasing how to leverage the ultra-fast queries of SingleStore to build a great visual experience using Plotly's DashApps.\n"
- ]
- },
- {
- "attachments": {},
- "cell_type": "markdown",
- "id": "e0fd0d9c-fd75-453a-aac3-bf797949dcce",
- "metadata": {
- "language": "python"
- },
- "source": [
- "## Create some simple tables\n",
- "\n",
- "This setup establishes a basic relational structure to store some orders information."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "d218d020-b9dc-4419-961d-2232ca0893f8",
- "metadata": {
- "execution": {
- "iopub.execute_input": "2024-11-07T19:11:06.213823Z",
- "iopub.status.busy": "2024-11-07T19:11:06.213624Z",
- "iopub.status.idle": "2024-11-07T19:11:09.169748Z",
- "shell.execute_reply": "2024-11-07T19:11:09.169084Z",
- "shell.execute_reply.started": "2024-11-07T19:11:06.213798Z"
- },
- "language": "python",
- "scrolled": true,
- "trusted": true
- },
- "outputs": [],
- "source": [
- "%%sql \n",
- "DROP TABLE IF EXISTS orders;\n",
- "\n",
- "CREATE TABLE IF NOT EXISTS orders (\n",
- " order_id INT PRIMARY KEY,\n",
- " order_date DATE,\n",
- " amount DECIMAL(10, 2),\n",
- " name VARCHAR(50)\n",
- ");"
- ]
- },
- {
- "attachments": {},
- "cell_type": "markdown",
- "id": "c6e492c6-74c8-488f-a456-fae59af0c69d",
- "metadata": {
- "language": "sql"
- },
- "source": [
- "## Insert some data\n",
- "\n",
- "Lets now insert some time series data into the table."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "98e60d97-42ce-4600-8e35-556c70f9d4c2",
- "metadata": {
- "execution": {
- "iopub.execute_input": "2024-11-07T19:11:11.645399Z",
- "iopub.status.busy": "2024-11-07T19:11:11.644981Z",
- "iopub.status.idle": "2024-11-07T19:11:11.903309Z",
- "shell.execute_reply": "2024-11-07T19:11:11.902765Z",
- "shell.execute_reply.started": "2024-11-07T19:11:11.645357Z"
- },
- "language": "python",
- "trusted": true
- },
- "outputs": [],
- "source": [
- "%%sql\n",
- "INSERT INTO orders (order_id, order_date, amount, name) VALUES\n",
- "(1, '2024-01-01', 150.00, \"Laptop\"),\n",
- "(2, '2024-01-01', 20.00, \"Speaker\"),\n",
- "(3, '2024-01-01', 60.00, \"Monitor\"),\n",
- "(4, '2024-01-02', 300.00, \"Laptop\"),\n",
- "(5, '2024-01-02', 100.00, \"Laptop\"),\n",
- "(6, '2024-01-02', 100.00, \"Laptop\"),\n",
- "(7, '2024-01-02', 25.00, \"Speaker\"),\n",
- "(8, '2024-01-02', 20.00, \"Speaker\"),\n",
- "(9, '2024-01-02', 75.00, \"Monitor\"),\n",
- "(10, '2024-01-03', 350.00, \"Laptop\"),\n",
- "(11, '2024-01-03', 150.00, \"Laptop\"),\n",
- "(12, '2024-01-03', 25.00, \"Speaker\"),\n",
- "(13, '2024-01-03', 35.00, \"Speaker\"),\n",
- "(14, '2024-01-03', 55.00, \"Monitor\"),\n",
- "(15, '2024-01-04', 120.00, \"Laptop\"),\n",
- "(16, '2024-01-04', 120.00, \"Laptop\"),\n",
- "(17, '2024-01-04', 30.00, \"Speaker\"),\n",
- "(18, '2024-01-04', 40.00, \"Speaker\"),\n",
- "(19, '2024-01-04', 25.00, \"Speaker\"),\n",
- "(20, '2024-01-04', 50.00, \"Monitor\"),\n",
- "(21, '2024-01-04', 70.00, \"Monitor\");\n"
- ]
- },
- {
- "attachments": {},
- "cell_type": "markdown",
- "id": "beb57814-ad38-4065-a730-59576f6a72e3",
- "metadata": {
- "language": "python"
- },
- "source": [
- "## Create a Connection Pool\n",
- "\n",
- "Next, we use sqlalchemy to create a pool of sql connections to the workspace you have selected. We also define a method to execute queries using a connection from this pool."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 6,
- "id": "f030ce86-4940-4014-8227-6b8c9cb56246",
- "metadata": {
- "execution": {
- "iopub.execute_input": "2024-11-07T19:11:14.340239Z",
- "iopub.status.busy": "2024-11-07T19:11:14.339533Z",
- "iopub.status.idle": "2024-11-07T19:11:14.411315Z",
- "shell.execute_reply": "2024-11-07T19:11:14.410704Z",
- "shell.execute_reply.started": "2024-11-07T19:11:14.340200Z"
- },
- "language": "python",
- "trusted": true
- },
- "outputs": [],
- "source": [
- "from sqlalchemy import create_engine, text\n",
- "import requests\n",
- "\n",
- "ca_cert_url = \"https://portal.singlestore.com/static/ca/singlestore_bundle.pem\"\n",
- "ca_cert_path = \"/tmp/singlestore_bundle.pem\"\n",
- "\n",
- "response = requests.get(ca_cert_url)\n",
- "with open(ca_cert_path, \"wb\") as f:\n",
- " f.write(response.content)\n",
- "\n",
- "sql_connection_string = connection_url.replace(\"singlestoredb\", \"mysql+pymysql\")\n",
- "engine = create_engine(\n",
- " f\"{sql_connection_string}?ssl_ca={ca_cert_path}\",\n",
- " pool_size=10, # Maximum number of connections in the pool is 10\n",
- " max_overflow=5, # Allow up to 5 additional connections (temporary overflow)\n",
- " pool_timeout=30 # Wait up to 30 seconds for a connection from the pool\n",
- ")\n",
- "\n",
- "def execute_query(query: str):\n",
- " with engine.connect() as connection:\n",
- " return pd.read_sql_query(query, connection)\n"
- ]
- },
- {
- "attachments": {},
- "cell_type": "markdown",
- "id": "dd87d196-3d52-4f3a-8dd4-d5f3540b051f",
- "metadata": {
- "execution": {
- "iopub.execute_input": "2024-10-22T01:53:59.981517Z",
- "iopub.status.busy": "2024-10-22T01:53:59.981114Z",
- "iopub.status.idle": "2024-10-22T01:53:59.985859Z",
- "shell.execute_reply": "2024-10-22T01:53:59.984904Z",
- "shell.execute_reply.started": "2024-10-22T01:53:59.981498Z"
- },
- "language": "python"
- },
- "source": [
- "## Create a line chart \n",
- "\n",
- "You can create a line chart using plotly, to depict either of the following \n",
- "- Number of items sold\n",
- "- Total sales volume"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "712cd20d-6f2d-4c5a-9094-11b611ce622d",
- "metadata": {
- "execution": {
- "iopub.execute_input": "2024-11-07T19:11:17.256882Z",
- "iopub.status.busy": "2024-11-07T19:11:17.256478Z",
- "iopub.status.idle": "2024-11-07T19:11:18.554013Z",
- "shell.execute_reply": "2024-11-07T19:11:18.540098Z",
- "shell.execute_reply.started": "2024-11-07T19:11:17.256846Z"
- },
- "language": "python",
- "scrolled": true,
- "trusted": true
- },
- "outputs": [],
- "source": [
- "import pandas as pd\n",
- "import plotly.express as px\n",
- "import plotly.graph_objects as go\n",
- "\n",
- "def generate_line_chart(type):\n",
- " if type == 'Count':\n",
- " df = execute_query(\"SELECT order_date, name, COUNT(*) as sales from orders group by order_date, name order by order_date\")\n",
- " elif type == 'Total Value':\n",
- " df = execute_query(\"SELECT order_date, name, SUM(amount) as sales from orders group by order_date, name order by order_date\")\n",
- " fig = px.line(df, x='order_date', y='sales', color='name', markers=True,\n",
- " labels={'sales': 'Sales', 'date': 'Order Date'},\n",
- " title='Sales Over Time')\n",
- " fig.update_layout(\n",
- " font_family=\"Roboto\",\n",
- " font_color=\"gray\",\n",
- " title_font_family=\"Roboto\",\n",
- " title_font_color=\"Black\",\n",
- " legend_title_font_color=\"gray\"\n",
- " )\n",
- " return fig\n",
- "\n",
- "line_chart = generate_line_chart(\"Count\")\n",
- "line_chart.show()"
- ]
- },
- {
- "attachments": {},
- "cell_type": "markdown",
- "id": "cc363aa0-a8d5-4f7e-bdae-5a22d56e0bcf",
- "metadata": {
- "execution": {
- "iopub.execute_input": "2024-10-30T00:10:10.537361Z",
- "iopub.status.busy": "2024-10-30T00:10:10.537013Z",
- "iopub.status.idle": "2024-10-30T00:10:10.543543Z",
- "shell.execute_reply": "2024-10-30T00:10:10.542538Z",
- "shell.execute_reply.started": "2024-10-30T00:10:10.537344Z"
- },
- "language": "python"
- },
- "source": [
- "## Create a pie chart \n",
- "\n",
- "You can create a pie chart to see the contribution of each type of item to the daily sales volume"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "79aa80ef-4a49-4238-87fb-f90a16ba4e42",
- "metadata": {
- "execution": {
- "iopub.execute_input": "2024-11-07T19:11:31.511364Z",
- "iopub.status.busy": "2024-11-07T19:11:31.510962Z",
- "iopub.status.idle": "2024-11-07T19:11:31.763424Z",
- "shell.execute_reply": "2024-11-07T19:11:31.762726Z",
- "shell.execute_reply.started": "2024-11-07T19:11:31.511324Z"
- },
- "language": "python",
- "trusted": true
- },
- "outputs": [],
- "source": [
- "def generate_pie_chart(date):\n",
- " df = execute_query(f\"SELECT name, SUM(amount) as sales from orders where order_date = '{date}' group by name\")\n",
- " fig = px.pie(df, \n",
- " names='name', \n",
- " values='sales',\n",
- " hover_data=['sales'],\n",
- " labels={'sales': 'Total Sales', 'name': 'Type'},\n",
- " title='Total Cost by Item Type')\n",
- " return fig\n",
- "\n",
- "pie_chart = generate_pie_chart(\"2024-01-01\")\n",
- "pie_chart.show()"
- ]
- },
- {
- "attachments": {},
- "cell_type": "markdown",
- "id": "94586a2e-76b2-48f8-8dbd-ff7038443ae1",
- "metadata": {
- "execution": {
- "iopub.execute_input": "2024-10-30T00:17:14.522971Z",
- "iopub.status.busy": "2024-10-30T00:17:14.522430Z",
- "iopub.status.idle": "2024-10-30T00:17:14.527648Z",
- "shell.execute_reply": "2024-10-30T00:17:14.526811Z",
- "shell.execute_reply.started": "2024-10-30T00:17:14.522948Z"
- },
- "language": "python"
- },
- "source": [
- "## Define the Dash App Layout and Callbacks\n",
- "\n",
- "We can now define the [layout](https://dash.plotly.com/layout) and [callbacks](https://dash.plotly.com/basic-callbacks) of the Dash app.\n",
- "The Layout defines the UI elements of your Dashboard and the callbacks define the interactions between the UI elements and the sqlalchemy query engine we defined earlier"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 9,
- "id": "de733262-834b-48b6-b885-78dfc5ebb452",
- "metadata": {
- "execution": {
- "iopub.execute_input": "2024-11-07T19:11:34.637625Z",
- "iopub.status.busy": "2024-11-07T19:11:34.637270Z",
- "iopub.status.idle": "2024-11-07T19:11:35.257579Z",
- "shell.execute_reply": "2024-11-07T19:11:35.256787Z",
- "shell.execute_reply.started": "2024-11-07T19:11:34.637596Z"
- },
- "language": "python",
- "trusted": true
- },
- "outputs": [],
- "source": [
- "from singlestoredb import apps\n",
- "from dash import Dash, callback, html, dcc, Input, Output\n",
- "\n",
- "def get_order_dates():\n",
- " df = execute_query(\"select distinct order_date from orders order by order_date\")\n",
- " return df['order_date']\n",
- "\n",
- "initial_dates = get_order_dates()\n",
- "\n",
- "# Create the Dash App\n",
- "app = Dash(\"Sales Report\", requests_pathname_prefix=os.environ['SINGLESTOREDB_APP_BASE_PATH'])\n",
- "\n",
- "# Define the Layout of the Dash App. We will be defining \n",
- "# - A line chart depicting a time series of sales\n",
- "# - A dropdown that shows 'Count'/'Total Value' options, which is used to render different line charts\n",
- "# - An interval counter to keep pinging the Dash App server to get the latest dashboard\n",
- "# - A pie chart depicting the total proportion of sales for a day by item type\n",
- "# - A drop down showing the different dates, which is used to render different pie charts\n",
- "\n",
- "app.layout = html.Div([\n",
- " html.P('Sales Dashboard', style={'textAlign':'center', 'marginTop': 50, 'color': '#8800cc', 'fontSize': '32px', 'fontFamily':'Roboto'} ),\n",
- " html.Div([\n",
- " dcc.Interval(\n",
- " id='interval-component',\n",
- " interval=2 * 5000, # Update every second\n",
- " n_intervals=0 # Start at 0\n",
- " ),\n",
- " html.Div(\n",
- " dcc.Dropdown(['Count', 'Total Value'], 'Count', id='category-dropdown', style={'width': '200px', 'marginRight':'32px' }),\n",
- " style={'display': 'flex', 'justifyContent': 'flex-end'}\n",
- " ),\n",
- " dcc.Loading(\n",
- " id=\"loading-spinner\",\n",
- " type=\"circle\", # Type of spinner: 'circle', 'dot', 'cube', etc.\n",
- " children=[\n",
- " dcc.Graph(figure = line_chart, id='line-chart'),\n",
- " ]\n",
- " ), \n",
- " html.Div(\n",
- " dcc.Dropdown(initial_dates, initial_dates[0], id='date-dropdown', style={'width': '200px', 'marginRight':'32px' }),\n",
- " style={'display': 'flex', 'justifyContent': 'flex-end'}\n",
- " ),\n",
- " dcc.Graph(figure = pie_chart, id='pie-chart'),\n",
- " ], style={'margin': '32px'})\n",
- "])\n",
- "\n",
- "# Define a callback to update the bar chart based on the category dropdown selection\n",
- "@app.callback(\n",
- " Output(\"line-chart\", \"figure\"),\n",
- " Input(\"category-dropdown\", \"value\")) # Use the stored value\n",
- "def update_bar_chart(type):\n",
- " return generate_line_chart(type)\n",
- "\n",
- "# Define a callback to update the pie chart based on the date dropdown selection\n",
- "@app.callback(\n",
- " Output(\"pie-chart\", \"figure\"),\n",
- " Input(\"date-dropdown\", \"value\"),\n",
- " Input('interval-component', 'n_intervals'))\n",
- "def update_pie_chart(date, n_intervals):\n",
- " return generate_pie_chart(date)\n",
- "\n",
- "# Define a callback to update the date dropdown periodically\n",
- "@app.callback(\n",
- " Output('date-dropdown', 'options'),\n",
- " Input('interval-component', 'n_intervals'))\n",
- "def update_date_dropdown(n_intervals):\n",
- " return get_order_dates()\n"
- ]
- },
- {
- "attachments": {},
- "cell_type": "markdown",
- "id": "f287e202-704b-4eb5-8290-fb08ba9a493c",
- "metadata": {
- "execution": {
- "iopub.execute_input": "2024-10-30T06:58:36.988307Z",
- "iopub.status.busy": "2024-10-30T06:58:36.987976Z",
- "iopub.status.idle": "2024-10-30T06:58:36.993332Z",
- "shell.execute_reply": "2024-10-30T06:58:36.992256Z",
- "shell.execute_reply.started": "2024-10-30T06:58:36.988287Z"
- },
- "language": "python"
- },
- "source": [
- "## Start the Dash App server\n",
- "\n",
- "The link at which the Dash App will be available interactively will be displayed. You can also insert more data into the table and view the changes to the dashboard in real time."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "69632c1b-f981-4338-9f91-ca8ae746cd73",
- "metadata": {
- "execution": {
- "iopub.execute_input": "2024-11-07T19:11:39.367523Z",
- "iopub.status.busy": "2024-11-07T19:11:39.366233Z",
- "iopub.status.idle": "2024-11-07T19:11:39.388450Z",
- "shell.execute_reply": "2024-11-07T19:11:39.387705Z",
- "shell.execute_reply.started": "2024-11-07T19:11:39.367444Z"
- },
- "language": "python",
- "trusted": true
- },
- "outputs": [],
- "source": [
- "connectionInfo = await apps.run_dashboard_app(app)"
- ]
- },
- {
- "attachments": {},
- "cell_type": "markdown",
- "id": "4fe4abd0-d52f-475a-89a4-d518f2b37d0d",
- "metadata": {
- "language": "python"
- },
- "source": [
- "## Publish Dashboard\n",
- "\n",
- "After validating the Dashboard interactively, you can publish it and view the changes to your data in real time!"
- ]
- }
- ],
- "metadata": {
- "jupyterlab": {
- "notebooks": {
- "version_major": 6,
- "version_minor": 4
- }
- },
- "kernelspec": {
- "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.11.6"
- },
- "singlestore_cell_default_language": "python",
- "singlestore_connection": {
- "connectionID": "0ff5bb99-7f92-425a-9751-2c3d4b97a996",
- "defaultDatabase": "test"
- },
- "singlestore_row_limit": 300
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "24350541-570b-491c-be33-b32b46764cf0",
+ "metadata": {},
+ "source": [
+ ""
+ ]
},
- "nbformat": 4,
- "nbformat_minor": 5
-}
\ No newline at end of file
+ {
+ "cell_type": "markdown",
+ "id": "c5893325-17c9-495f-862f-049072c30806",
+ "metadata": {},
+ "source": [
+ "\n",
+ "
\n",
+ "
\n",
+ "
Note
\n",
+ "
This notebook can be run on a Free Starter Workspace. To create a Free Starter Workspace navigate to Start using the left nav. You can also use your existing Standard or Premium workspace with this Notebook.
\n",
+ "
\n",
+ "
"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "id": "df860ca4-6db8-4ded-a061-30be438c4add",
+ "metadata": {},
+ "source": [
+ "This Jupyter notebook will help you build your first real time Dashboard, showcasing how to leverage the ultra-fast queries of SingleStore to build a great visual experience using Plotly's DashApps."
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "id": "e0fd0d9c-fd75-453a-aac3-bf797949dcce",
+ "metadata": {},
+ "source": [
+ "## Create some simple tables\n",
+ "\n",
+ "This setup establishes a basic relational structure to store some orders information."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "id": "d218d020-b9dc-4419-961d-2232ca0893f8",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "%%sql\n",
+ "DROP TABLE IF EXISTS orders;\n",
+ "\n",
+ "CREATE TABLE IF NOT EXISTS orders (\n",
+ " order_id INT PRIMARY KEY,\n",
+ " order_date DATE,\n",
+ " amount DECIMAL(10, 2),\n",
+ " name VARCHAR(50)\n",
+ ");"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "id": "c6e492c6-74c8-488f-a456-fae59af0c69d",
+ "metadata": {},
+ "source": [
+ "## Insert some data\n",
+ "\n",
+ "Lets now insert some time series data into the table."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "id": "98e60d97-42ce-4600-8e35-556c70f9d4c2",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "%%sql\n",
+ "INSERT INTO orders (order_id, order_date, amount, name) VALUES\n",
+ "(1, '2024-01-01', 150.00, \"Laptop\"),\n",
+ "(2, '2024-01-01', 20.00, \"Speaker\"),\n",
+ "(3, '2024-01-01', 60.00, \"Monitor\"),\n",
+ "(4, '2024-01-02', 300.00, \"Laptop\"),\n",
+ "(5, '2024-01-02', 100.00, \"Laptop\"),\n",
+ "(6, '2024-01-02', 100.00, \"Laptop\"),\n",
+ "(7, '2024-01-02', 25.00, \"Speaker\"),\n",
+ "(8, '2024-01-02', 20.00, \"Speaker\"),\n",
+ "(9, '2024-01-02', 75.00, \"Monitor\"),\n",
+ "(10, '2024-01-03', 350.00, \"Laptop\"),\n",
+ "(11, '2024-01-03', 150.00, \"Laptop\"),\n",
+ "(12, '2024-01-03', 25.00, \"Speaker\"),\n",
+ "(13, '2024-01-03', 35.00, \"Speaker\"),\n",
+ "(14, '2024-01-03', 55.00, \"Monitor\"),\n",
+ "(15, '2024-01-04', 120.00, \"Laptop\"),\n",
+ "(16, '2024-01-04', 120.00, \"Laptop\"),\n",
+ "(17, '2024-01-04', 30.00, \"Speaker\"),\n",
+ "(18, '2024-01-04', 40.00, \"Speaker\"),\n",
+ "(19, '2024-01-04', 25.00, \"Speaker\"),\n",
+ "(20, '2024-01-04', 50.00, \"Monitor\"),\n",
+ "(21, '2024-01-04', 70.00, \"Monitor\");"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "id": "beb57814-ad38-4065-a730-59576f6a72e3",
+ "metadata": {},
+ "source": [
+ "## Create a Connection Pool\n",
+ "\n",
+ "Next, we use sqlalchemy to create a pool of sql connections to the workspace you have selected. We also define a method to execute queries using a connection from this pool."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "id": "f030ce86-4940-4014-8227-6b8c9cb56246",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from sqlalchemy import create_engine, text\n",
+ "import requests\n",
+ "\n",
+ "ca_cert_url = \"https://portal.singlestore.com/static/ca/singlestore_bundle.pem\"\n",
+ "ca_cert_path = \"/tmp/singlestore_bundle.pem\"\n",
+ "\n",
+ "response = requests.get(ca_cert_url)\n",
+ "with open(ca_cert_path, \"wb\") as f:\n",
+ " f.write(response.content)\n",
+ "\n",
+ "sql_connection_string = connection_url.replace(\"singlestoredb\", \"mysql+pymysql\")\n",
+ "engine = create_engine(\n",
+ " f\"{sql_connection_string}?ssl_ca={ca_cert_path}\",\n",
+ " pool_size=10, # Maximum number of connections in the pool is 10\n",
+ " max_overflow=5, # Allow up to 5 additional connections (temporary overflow)\n",
+ " pool_timeout=30 # Wait up to 30 seconds for a connection from the pool\n",
+ ")\n",
+ "\n",
+ "def execute_query(query: str):\n",
+ " with engine.connect() as connection:\n",
+ " return pd.read_sql_query(query, connection)"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "id": "dd87d196-3d52-4f3a-8dd4-d5f3540b051f",
+ "metadata": {},
+ "source": [
+ "## Create a line chart\n",
+ "\n",
+ "You can create a line chart using plotly, to depict either of the following\n",
+ "- Number of items sold\n",
+ "- Total sales volume"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "id": "712cd20d-6f2d-4c5a-9094-11b611ce622d",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import pandas as pd\n",
+ "import plotly.express as px\n",
+ "import plotly.graph_objects as go\n",
+ "\n",
+ "def generate_line_chart(type):\n",
+ " if type == 'Count':\n",
+ " df = execute_query(\"SELECT order_date, name, COUNT(*) as sales from orders group by order_date, name order by order_date\")\n",
+ " elif type == 'Total Value':\n",
+ " df = execute_query(\"SELECT order_date, name, SUM(amount) as sales from orders group by order_date, name order by order_date\")\n",
+ " fig = px.line(df, x='order_date', y='sales', color='name', markers=True,\n",
+ " labels={'sales': 'Sales', 'date': 'Order Date'},\n",
+ " title='Sales Over Time')\n",
+ " fig.update_layout(\n",
+ " font_family=\"Roboto\",\n",
+ " font_color=\"gray\",\n",
+ " title_font_family=\"Roboto\",\n",
+ " title_font_color=\"Black\",\n",
+ " legend_title_font_color=\"gray\"\n",
+ " )\n",
+ " return fig\n",
+ "\n",
+ "line_chart = generate_line_chart(\"Count\")\n",
+ "line_chart.show()"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "id": "cc363aa0-a8d5-4f7e-bdae-5a22d56e0bcf",
+ "metadata": {},
+ "source": [
+ "## Create a pie chart\n",
+ "\n",
+ "You can create a pie chart to see the contribution of each type of item to the daily sales volume"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "id": "79aa80ef-4a49-4238-87fb-f90a16ba4e42",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def generate_pie_chart(date):\n",
+ " df = execute_query(f\"SELECT name, SUM(amount) as sales from orders where order_date = '{date}' group by name\")\n",
+ " fig = px.pie(df,\n",
+ " names='name',\n",
+ " values='sales',\n",
+ " hover_data=['sales'],\n",
+ " labels={'sales': 'Total Sales', 'name': 'Type'},\n",
+ " title='Total Cost by Item Type')\n",
+ " return fig\n",
+ "\n",
+ "pie_chart = generate_pie_chart(\"2024-01-01\")\n",
+ "pie_chart.show()"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "id": "94586a2e-76b2-48f8-8dbd-ff7038443ae1",
+ "metadata": {},
+ "source": [
+ "## Define the Dash App Layout and Callbacks\n",
+ "\n",
+ "We can now define the [layout](https://dash.plotly.com/layout) and [callbacks](https://dash.plotly.com/basic-callbacks) of the Dash app.\n",
+ "The Layout defines the UI elements of your Dashboard and the callbacks define the interactions between the UI elements and the sqlalchemy query engine we defined earlier"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "id": "de733262-834b-48b6-b885-78dfc5ebb452",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from singlestoredb import apps\n",
+ "from dash import Dash, callback, html, dcc, Input, Output\n",
+ "\n",
+ "def get_order_dates():\n",
+ " df = execute_query(\"select distinct order_date from orders order by order_date\")\n",
+ " return df['order_date']\n",
+ "\n",
+ "initial_dates = get_order_dates()\n",
+ "\n",
+ "# Create the Dash App\n",
+ "app = Dash(\"Sales Report\", requests_pathname_prefix=os.environ['SINGLESTOREDB_APP_BASE_PATH'])\n",
+ "\n",
+ "# Define the Layout of the Dash App. We will be defining\n",
+ "# - A line chart depicting a time series of sales\n",
+ "# - A dropdown that shows 'Count'/'Total Value' options, which is used to render different line charts\n",
+ "# - An interval counter to keep pinging the Dash App server to get the latest dashboard\n",
+ "# - A pie chart depicting the total proportion of sales for a day by item type\n",
+ "# - A drop down showing the different dates, which is used to render different pie charts\n",
+ "\n",
+ "app.layout = html.Div([\n",
+ " html.P('Sales Dashboard', style={'textAlign':'center', 'marginTop': 50, 'color': '#8800cc', 'fontSize': '32px', 'fontFamily':'Roboto'} ),\n",
+ " html.Div([\n",
+ " dcc.Interval(\n",
+ " id='interval-component',\n",
+ " interval=2 * 5000, # Update every second\n",
+ " n_intervals=0 # Start at 0\n",
+ " ),\n",
+ " html.Div(\n",
+ " dcc.Dropdown(['Count', 'Total Value'], 'Count', id='category-dropdown', style={'width': '200px', 'marginRight':'32px' }),\n",
+ " style={'display': 'flex', 'justifyContent': 'flex-end'}\n",
+ " ),\n",
+ " dcc.Loading(\n",
+ " id=\"loading-spinner\",\n",
+ " type=\"circle\", # Type of spinner: 'circle', 'dot', 'cube', etc.\n",
+ " children=[\n",
+ " dcc.Graph(figure = line_chart, id='line-chart'),\n",
+ " ]\n",
+ " ),\n",
+ " html.Div(\n",
+ " dcc.Dropdown(initial_dates, initial_dates[0], id='date-dropdown', style={'width': '200px', 'marginRight':'32px' }),\n",
+ " style={'display': 'flex', 'justifyContent': 'flex-end'}\n",
+ " ),\n",
+ " dcc.Graph(figure = pie_chart, id='pie-chart'),\n",
+ " ], style={'margin': '32px'})\n",
+ "])\n",
+ "\n",
+ "# Define a callback to update the bar chart based on the category dropdown selection\n",
+ "@app.callback(\n",
+ " Output(\"line-chart\", \"figure\"),\n",
+ " Input(\"category-dropdown\", \"value\")) # Use the stored value\n",
+ "def update_bar_chart(type):\n",
+ " return generate_line_chart(type)\n",
+ "\n",
+ "# Define a callback to update the pie chart based on the date dropdown selection\n",
+ "@app.callback(\n",
+ " Output(\"pie-chart\", \"figure\"),\n",
+ " Input(\"date-dropdown\", \"value\"),\n",
+ " Input('interval-component', 'n_intervals'))\n",
+ "def update_pie_chart(date, n_intervals):\n",
+ " return generate_pie_chart(date)\n",
+ "\n",
+ "# Define a callback to update the date dropdown periodically\n",
+ "@app.callback(\n",
+ " Output('date-dropdown', 'options'),\n",
+ " Input('interval-component', 'n_intervals'))\n",
+ "def update_date_dropdown(n_intervals):\n",
+ " return get_order_dates()"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "id": "f287e202-704b-4eb5-8290-fb08ba9a493c",
+ "metadata": {},
+ "source": [
+ "## Start the Dash App server\n",
+ "\n",
+ "The link at which the Dash App will be available interactively will be displayed. You can also insert more data into the table and view the changes to the dashboard in real time."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "id": "69632c1b-f981-4338-9f91-ca8ae746cd73",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "connectionInfo = await apps.run_dashboard_app(app)"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "id": "4fe4abd0-d52f-475a-89a4-d518f2b37d0d",
+ "metadata": {},
+ "source": [
+ "## Publish Dashboard\n",
+ "\n",
+ "After validating the Dashboard interactively, you can publish it and view the changes to your data in real time!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "8eb7fab3-c714-4b3b-93a7-ce8a9836ded2",
+ "metadata": {},
+ "source": [
+ "\n",
+ ""
+ ]
+ }
+ ],
+ "metadata": {
+ "jupyterlab": {
+ "notebooks": {
+ "version_major": 6,
+ "version_minor": 4
+ }
+ },
+ "kernelspec": {
+ "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.11.6"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/resources/author-check.py b/resources/author-check.py
index f6fd6bcb..35f893ed 100755
--- a/resources/author-check.py
+++ b/resources/author-check.py
@@ -2,6 +2,7 @@
import os
import re
import sys
+
import tomllib
diff --git a/resources/lesson-check.py b/resources/lesson-check.py
index 2a8e2356..9693380a 100755
--- a/resources/lesson-check.py
+++ b/resources/lesson-check.py
@@ -1,6 +1,7 @@
#!/usr/bin/env python3
import os
import sys
+
import tomllib
diff --git a/resources/nb-check.py b/resources/nb-check.py
index b04c25ef..76942501 100755
--- a/resources/nb-check.py
+++ b/resources/nb-check.py
@@ -4,11 +4,12 @@
import json
import os
import sys
-import tomllib
import uuid
from typing import Any
from typing import List
+import tomllib
+
NOTEBOOK_HEADER = [
'