Skip to content

Commit 69c6699

Browse files
authored
Merge pull request #121 from singlestore-labs/bharath-templates
Add Create DashApp and create Singlestore Cloud Functions template to spaces
2 parents ed421af + 246cbef commit 69c6699

File tree

4 files changed

+642
-0
lines changed

4 files changed

+642
-0
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[meta]
2+
authors=["singlestore"]
3+
title="Publish your first SingleStore Cloud function"
4+
description="""\
5+
Learn how to connect to SingleStoreDB and perform basic\
6+
CRUD operations and finally deploy these functions as callable API endpoints.
7+
"""
8+
icon="browser"
9+
difficulty="beginner"
10+
tags=["starter", "notebooks", "python"]
11+
lesson_areas=[]
12+
destinations=["spaces"]
13+
minimum_tier="free-shared"
Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"id": "8ba141c2-e0c2-4723-b782-c924bc7b294c",
6+
"metadata": {},
7+
"source": [
8+
"<div id=\"singlestore-header\" style=\"display: flex; background-color: rgba(235, 249, 245, 0.25); padding: 5px;\">\n",
9+
" <div id=\"icon-image\" style=\"width: 90px; height: 90px;\">\n",
10+
" <img width=\"100%\" height=\"100%\" src=\"https://raw.githubusercontent.com/singlestore-labs/spaces-notebooks/master/common/images/header-icons/browser.png\" />\n",
11+
" </div>\n",
12+
" <div id=\"text\" style=\"padding: 5px; margin-left: 10px;\">\n",
13+
" <div id=\"badge\" style=\"display: inline-block; background-color: rgba(0, 0, 0, 0.15); border-radius: 4px; padding: 4px 8px; align-items: center; margin-top: 6px; margin-bottom: -2px; font-size: 80%\">SingleStore Notebooks</div>\n",
14+
" <h1 style=\"font-weight: 500; margin: 8px 0 0 4px;\">Publish your first SingleStore Cloud function</h1>\n",
15+
" </div>\n",
16+
"</div>"
17+
]
18+
},
19+
{
20+
"cell_type": "markdown",
21+
"id": "cd7deb95-c7bb-48eb-9cab-ed508b3be5ff",
22+
"metadata": {},
23+
"source": [
24+
"<div class=\"alert alert-block alert-warning\">\n",
25+
" <b class=\"fa fa-solid fa-exclamation-circle\"></b>\n",
26+
" <div>\n",
27+
" <p><b>Note</b></p>\n",
28+
" <p>This notebook can be run on a Free Starter Workspace. To create a Free Starter Workspace navigate to <tt>Start</tt> using the left nav. You can also use your existing Standard or Premium workspace with this Notebook.</p>\n",
29+
" </div>\n",
30+
"</div>"
31+
]
32+
},
33+
{
34+
"attachments": {},
35+
"cell_type": "markdown",
36+
"id": "a5564913-7ff8-41bf-b64b-b67971c63fae",
37+
"source": [
38+
"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"
39+
]
40+
},
41+
{
42+
"attachments": {},
43+
"cell_type": "markdown",
44+
"id": "1e394195-29b4-403c-9abf-5d7731349eb6",
45+
"source": [
46+
"## Create some simple tables\n",
47+
"\n",
48+
"This setup establishes a basic relational structure to store some items information."
49+
]
50+
},
51+
{
52+
"cell_type": "code",
53+
"execution_count": 1,
54+
"id": "a17bdd3a-16b3-4e19-8a56-6566a169eccb",
55+
"outputs": [],
56+
"source": [
57+
"%%sql\n",
58+
"DROP TABLE IF EXISTS items;\n",
59+
"\n",
60+
"CREATE TABLE IF NOT EXISTS\n",
61+
"items (\n",
62+
" id INT PRIMARY KEY,\n",
63+
" name VARCHAR(255),\n",
64+
" price FLOAT\n",
65+
");"
66+
]
67+
},
68+
{
69+
"attachments": {},
70+
"cell_type": "markdown",
71+
"id": "af6e2618-de97-4397-b0d2-23e4a4df1d83",
72+
"source": [
73+
"## Create a Connection Pool\n",
74+
"\n",
75+
"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."
76+
]
77+
},
78+
{
79+
"cell_type": "code",
80+
"execution_count": 2,
81+
"id": "f485e71b-2b05-4696-b22a-cf046fd83090",
82+
"outputs": [],
83+
"source": [
84+
"from sqlalchemy import create_engine, text\n",
85+
"import requests\n",
86+
"\n",
87+
"ca_cert_url = \"https://portal.singlestore.com/static/ca/singlestore_bundle.pem\"\n",
88+
"ca_cert_path = \"/tmp/singlestore_bundle.pem\"\n",
89+
"\n",
90+
"response = requests.get(ca_cert_url)\n",
91+
"with open(ca_cert_path, \"wb\") as f:\n",
92+
" f.write(response.content)\n",
93+
"\n",
94+
"engine = create_engine(\n",
95+
" f\"{connection_url}?ssl_ca={ca_cert_path}\",\n",
96+
" pool_size=10, # Maximum number of connections in the pool is 10\n",
97+
" max_overflow=5, # Allow up to 5 additional connections (temporary overflow)\n",
98+
" pool_timeout=30 # Wait up to 30 seconds for a connection from the pool\n",
99+
")\n",
100+
"\n",
101+
"def execute_query(query: str):\n",
102+
" with engine.connect() as connection:\n",
103+
" return connection.execute(text(query))\n",
104+
"\n",
105+
"def execute_transaction(transactional_query: str):\n",
106+
" with engine.connect() as connection:\n",
107+
" transaction = connection.begin()\n",
108+
" try:\n",
109+
" result = connection.execute(text(transactional_query))\n",
110+
" transaction.commit()\n",
111+
" return result\n",
112+
" except Exception as e:\n",
113+
" transaction.rollback()\n",
114+
" raise e"
115+
]
116+
},
117+
{
118+
"attachments": {},
119+
"cell_type": "markdown",
120+
"id": "ee9058a9-34a5-46fc-8b12-d30cbb8c3340",
121+
"source": [
122+
"## Setup Environment\n",
123+
"\n",
124+
"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"
125+
]
126+
},
127+
{
128+
"cell_type": "code",
129+
"execution_count": 3,
130+
"id": "66df8f0c-70c6-4f06-9e64-ef06961cca3a",
131+
"outputs": [],
132+
"source": [
133+
"from fastapi import FastAPI, HTTPException\n",
134+
"from pydantic import BaseModel\n",
135+
"from singlestoredb import connect\n",
136+
"from concurrent.futures import ThreadPoolExecutor\n",
137+
"import asyncio\n",
138+
"\n",
139+
"# Define the Type of the Data\n",
140+
"class Item(BaseModel):\n",
141+
" id: int\n",
142+
" name: str\n",
143+
" price: float\n",
144+
"\n",
145+
"# Create an executor that can execute queries on multiple threads simultaneously\n",
146+
"executor = ThreadPoolExecutor()\n",
147+
"def run_in_thread(fn, *args):\n",
148+
" loop = asyncio.get_event_loop()\n",
149+
" return loop.run_in_executor(executor, fn, *args)"
150+
]
151+
},
152+
{
153+
"attachments": {},
154+
"cell_type": "markdown",
155+
"id": "96760949-5ab2-474d-80ca-d23b5dcc52f7",
156+
"source": [
157+
"## Define FastAPI App\n",
158+
"\n",
159+
"Next, we will be defining a FastAPI app that can insert, query and delete data from your table"
160+
]
161+
},
162+
{
163+
"cell_type": "code",
164+
"execution_count": 4,
165+
"id": "3087dbe6-57ce-4410-a42f-5b0fe90add90",
166+
"outputs": [],
167+
"source": [
168+
"app = FastAPI()\n",
169+
"\n",
170+
"# Get all items\n",
171+
"@app.get(\"/items\", response_model=list[Item])\n",
172+
"async def get_items():\n",
173+
" def get_items_query():\n",
174+
" result = execute_query(\"SELECT * FROM items;\")\n",
175+
" rows = result.fetchall()\n",
176+
" return [{\"id\": row[0], \"name\": row[1], \"price\": row[2]} for row in rows]\n",
177+
"\n",
178+
" try:\n",
179+
" return await run_in_thread(get_items_query)\n",
180+
" except Exception as e:\n",
181+
"\n",
182+
" raise HTTPException(status_code=500, detail=f\"Error fetching all items: {str(e)}\")\n",
183+
"\n",
184+
"# Insert an item\n",
185+
"@app.post(\"/items\", response_model=dict)\n",
186+
"async def create_item(item: Item):\n",
187+
" def insert_item_query():\n",
188+
" result = execute_transaction(f\"INSERT INTO items (id, name, price) VALUES ({item.id}, '{item.name}', {item.price})\")\n",
189+
" return {\"message\": f\"Item with id {item.id} inserted successfully\"}\n",
190+
"\n",
191+
" try:\n",
192+
" return await run_in_thread(insert_item_query)\n",
193+
" except Exception as e:\n",
194+
" raise HTTPException(status_code=500, detail=f\"Error while inserting item with id {item.id}: {str(e)}\")\n",
195+
"\n",
196+
"# Get item by id\n",
197+
"@app.get(\"/items/{item_id}\", response_model=Item)\n",
198+
"async def get_item(item_id: int):\n",
199+
" def get_item_query():\n",
200+
" result = execute_query(f\"SELECT * FROM items WHERE id={item_id}\")\n",
201+
" row = result.fetchone()\n",
202+
" if not row:\n",
203+
" raise HTTPException(status_code=404, detail=\"Item not found\")\n",
204+
" return {\"id\": row[0], \"name\": row[1], \"price\": row[2]}\n",
205+
"\n",
206+
" try:\n",
207+
" return await run_in_thread(get_item_query)\n",
208+
" except HTTPException as e:\n",
209+
" raise e\n",
210+
" except Exception as e:\n",
211+
" raise HTTPException(status_code=500, detail=f\"Error fetching item with id {item_id}: {str(e)}\")\n",
212+
"\n",
213+
"# Delete item by id\n",
214+
"@app.delete(\"/items/{item_id}\", response_model=dict)\n",
215+
"async def delete_item(item_id: int):\n",
216+
" def delete_item_query():\n",
217+
" result = execute_transaction(f\"DELETE FROM items WHERE id={item_id}\")\n",
218+
" return {\"message\": f\"number of rows deleted: {result.rowcount}\"}\n",
219+
"\n",
220+
" try:\n",
221+
" return await run_in_thread(delete_item_query)\n",
222+
" except Exception as e:\n",
223+
" raise HTTPException(status_code=500, detail=f\"Error deleting item with id {item_id}: {str(e)}\")"
224+
]
225+
},
226+
{
227+
"attachments": {},
228+
"cell_type": "markdown",
229+
"id": "c3d9ed07-4b55-4d17-aabb-e11b399109d1",
230+
"source": [
231+
"## Start the FastAPI server\n",
232+
"\n",
233+
"The link at which the cloud function will be available interactively will be displayed."
234+
]
235+
},
236+
{
237+
"cell_type": "code",
238+
"execution_count": 5,
239+
"id": "ff002c7d-9f1c-40e5-b82a-c9176251dc99",
240+
"outputs": [],
241+
"source": [
242+
"import singlestoredb.apps as apps\n",
243+
"connection_info = await apps.run_function_app(app)"
244+
]
245+
},
246+
{
247+
"attachments": {},
248+
"cell_type": "markdown",
249+
"id": "fabe76b7-e6a0-43a0-8d9e-aa79bd7d3021",
250+
"source": [
251+
"## Publish Cloud Function\n",
252+
"\n",
253+
"After validating the Cloud Function interactively, you can publish it and use it as an API server for your data!"
254+
]
255+
},
256+
{
257+
"cell_type": "markdown",
258+
"id": "386f804b-f3a9-4452-9575-b87c917bbbf8",
259+
"metadata": {},
260+
"source": [
261+
"<div id=\"singlestore-footer\" style=\"background-color: rgba(194, 193, 199, 0.25); height:2px; margin-bottom:10px\"></div>\n",
262+
"<div><img src=\"https://raw.githubusercontent.com/singlestore-labs/spaces-notebooks/master/common/images/singlestore-logo-grey.png\" style=\"padding: 0px; margin: 0px; height: 24px\"/></div>"
263+
]
264+
}
265+
],
266+
"nbformat": 4,
267+
"nbformat_minor": 5
268+
}

notebooks/create-dash-app/meta.toml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[meta]
2+
authors=["singlestore"]
3+
title="Publish your first SingleStore DashApp"
4+
description="""\
5+
Learn how to connect to SingleStoreDB\
6+
and publish an interactive Dashboard.
7+
"""
8+
icon="browser"
9+
difficulty="beginner"
10+
tags=["starter", "notebooks", "python"]
11+
lesson_areas=[]
12+
destinations=["spaces"]
13+
minimum_tier="free-shared"

0 commit comments

Comments
 (0)