aboutsummaryrefslogtreecommitdiff
path: root/jupyter_files/creating_fastapi_static_blog.ipynb
diff options
context:
space:
mode:
Diffstat (limited to 'jupyter_files/creating_fastapi_static_blog.ipynb')
-rw-r--r--jupyter_files/creating_fastapi_static_blog.ipynb507
1 files changed, 507 insertions, 0 deletions
diff --git a/jupyter_files/creating_fastapi_static_blog.ipynb b/jupyter_files/creating_fastapi_static_blog.ipynb
new file mode 100644
index 0000000..b2c1851
--- /dev/null
+++ b/jupyter_files/creating_fastapi_static_blog.ipynb
@@ -0,0 +1,507 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "d745c9ae-31f8-47e5-83df-42c0a39e700e",
+ "metadata": {},
+ "source": [
+ "# Setting up FastAPI with Uvicorn and Openrc to serve Jupyter Notebook generated html files."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "5dec85d7-1ac2-4bdd-a5c4-40dc8ec80c1f",
+ "metadata": {},
+ "source": [
+ "## Prerequisites:\n",
+ "- system with openrc\n",
+ "- fastapi\n",
+ "- uvicorn\n",
+ "- jupyter notebook (optional)\n",
+ "- nginx"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "3d5a9177-1fcd-4bc6-97dd-f85c712e8a9e",
+ "metadata": {},
+ "source": [
+ "## Creating FastAPI application"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "6f2751d9-cdfe-417e-8a30-8768e2fd828e",
+ "metadata": {},
+ "source": [
+ "Create a directory for project and enter it:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "58fe2a08-afb0-400b-a8a2-063da88a2559",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "$ mkdir -p ~/Fastapi_blog/static && cd ~/Fastapi_blog"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "2ad52653-7f16-489f-8a4b-e2fd08f90d05",
+ "metadata": {},
+ "source": [
+ "Create a file containing code and html template folder and template for the main page:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "27e461b5-279f-4143-ac69-5ef61b75e6af",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "$ touch main.py\n",
+ "$ mkdir templates\n",
+ "$ touch templates/main_page.html"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "9c3d9c08-81ea-43b0-bea8-8486adb06d85",
+ "metadata": {},
+ "source": [
+ "main_page.html can be really minimal:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "e6a61c2c-708e-462d-b487-0ddd23b5e08a",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "<html>\n",
+ "<body>\n",
+ " <h1>List of articles</h1>\n",
+ " <h2>Number of posts: {{ files|length }}</h2>\n",
+ " {% if files %}\n",
+ " {% for item in files %}\n",
+ " <a href=\"static/html/{{ item }}\">{{ item }}</a><br>\n",
+ " {% endfor %}\n",
+ " {% endif %}\n",
+ "</body>\n",
+ "</html>"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "b15b4217-9343-4c73-aa44-f069f6d5e776",
+ "metadata": {},
+ "source": [
+ "Create basic FastAPI application serving static files at '/'. I want that as I'll be serving if from behind Nginx reverse proxy. Prefix will be set with Nginx conf file"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "90f95ae9-4079-440e-9f40-2889541cb727",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "#!/usr/bin/env python\n",
+ "\n",
+ "\"\"\"\n",
+ "FastAPI site serving static html files.\n",
+ "\"\"\"\n",
+ "\n",
+ "from os import listdir\n",
+ "from fastapi import FastAPI\n",
+ "from fastapi.staticfiles import StaticFiles\n",
+ "from fastapi import FastAPI, Request\n",
+ "from fastapi.staticfiles import StaticFiles\n",
+ "from fastapi.templating import Jinja2Templates\n",
+ "\n",
+ "\n",
+ "ARTICLES = 'static/html'\n",
+ "templates = Jinja2Templates(directory=\"templates\")\n",
+ "LIST_OF_ARTICLES = [i for i in listdir(ARTICLES) if i.endswith('.html')]\n",
+ "\n",
+ "app = FastAPI()\n",
+ "\n",
+ "app.mount(\"/\", StaticFiles(directory=\"static\", html=True), name=\"static\")\n",
+ "\n",
+ "\n",
+ "@app.get(\"/\")\n",
+ "async def main_page(request: Request):\n",
+ " \"\"\"Generate main page\"\"\"\n",
+ " return templates.TemplateResponse(\n",
+ " \"main_page.html\", {\n",
+ " \"request\": request,\n",
+ " \"files\": LIST_OF_ARTICLES,\n",
+ " \"static\": ARTICLES,\n",
+ " }\n",
+ " )\n",
+ "\n",
+ "\n",
+ "if __name__ == \"__main__\":\n",
+ " uvicorn.run(\n",
+ " app,\n",
+ " port=8000,\n",
+ " host='127.0.0.1',\n",
+ " log_level=\"info\",\n",
+ " # uds=\"/run/tech_blog.sock\", <- uncomment if you want Unix socket\n",
+ " )"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "0def2ab2-e037-4997-b472-59784554d12f",
+ "metadata": {},
+ "source": [
+ "Test run:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "7e4b78e2-9412-40a0-ad23-b1ebdc32b34d",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "$ uvicorn main:app --reload"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "id": "1ff45780-0f1a-4f8a-a50d-7c52ad08f52d",
+ "metadata": {},
+ "source": [
+ "If we try to run it as regular user with 'uds=\"/run/tech_blog.sock\"' line uncommented we should get permission denied error as we don't have rights to create files in /run.\n",
+ "\n",
+ "In browser, take a peek at http://localhost:8000 and you should already see something.\n",
+ "\n",
+ "Create a file with a test html code:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "f2cb8101-4914-494e-9c6e-222cd9cdb148",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "$ touch static/test.html"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "0496d679-15a2-4610-815b-9e1b57285ebf",
+ "metadata": {},
+ "source": [
+ "Put this in it:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "3db82cd2-3f9f-4c74-b98b-2cf1b19dd353",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "<html>\n",
+ "<body>\n",
+ "\n",
+ "<h1>Fun stuff comming up</h1>\n",
+ "<p>Bob's your uncle!</p>\n",
+ "\n",
+ "</body>\n",
+ "</html> "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "fd3b37dc-e3d3-48a0-a496-7f0060bc69c0",
+ "metadata": {},
+ "source": [
+ "Visit http://localhost:8000/\n",
+ "You should see the main page with test article and you should be able to navigate to the article clicking the link."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "cba92c27-c741-4959-88c5-b9790de7eddf",
+ "metadata": {},
+ "source": [
+ "## Jupyter side note"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "f7fefa06-9d13-4793-8bb6-3f96f2171f25",
+ "metadata": {},
+ "source": [
+ "To write stuff with Jupyter Notebook start it up:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "f7eadb7f-e811-4bee-a8d7-68c68ec9f9ca",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "$ jupyter-notebook"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "d0ce0f2c-e633-4974-8ed4-1f4b3a021d6a",
+ "metadata": {},
+ "source": [
+ "In browser a new tab with jupyter notebook should have started. Write your fun stuff. Save it. If you don't want to execute cell content convert to html with:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "7a4da315-d47b-4bec-8b17-f2cbf494f4d4",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "$ jupyter nbconvert --to html <path to your file>"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "80c327c4-d985-47d6-94fa-10e8e5b7d2b4",
+ "metadata": {},
+ "source": [
+ "If you want to execute cell content convert with:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "54af67cd-0d26-4373-9647-8943dec2f4ab",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "$ jupyter nbconvert --execute --to html <path to your file>"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "f8653115-4f64-40a7-9d72-0e088a607a07",
+ "metadata": {},
+ "source": [
+ "Want to change theme to dark?"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "52d5866b-813d-4ef3-9886-c775e1c67d5b",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "$ jupyter nbconvert --execute --to html <path to your file> --HTMLExporter.theme=dark"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "e73f69fc-6c8a-4617-90bd-a0e788b89756",
+ "metadata": {},
+ "source": [
+ "Now put it in your `static` folder. It will be served by your FastAPI application."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "c56ef398-9f2e-4253-bc37-22b5926b8c7c",
+ "metadata": {},
+ "source": [
+ "## Bash script"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "f37aba78-c09b-4807-ba02-c72f48b9dba5",
+ "metadata": {},
+ "source": [
+ "In order to run our python script with Openrc first we need to create a Bash script:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "6f4052ac-f582-47fa-b702-34f1341dec88",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "#!/usr/bin/env bash\n",
+ "\n",
+ "cd /home/discordia/Fastapi_blog || exit # If your login is different, change home directory name.\n",
+ "exec python main.py"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "dedf511e-f6fd-4c41-8b02-a2eb25e9604a",
+ "metadata": {},
+ "source": [
+ "Save it (here as fastapi_blog.sh) as a root user to /usr/local/bin and make executable:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "7c6e7b2d-33a3-4305-917b-c24155a648f2",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# chmod ugo+x /usr/local/bin/fastapi_blog.sh"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "1bf1a799-25b0-4008-b1d2-1a555cef79c5",
+ "metadata": {},
+ "source": [
+ "## Openrc service file"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "43794484-8e10-400d-a598-d1acefd455ee",
+ "metadata": {},
+ "source": [
+ "Now create a minimal service file"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "f14c6b71-3f1f-4dd8-b62d-d79721a8d6fe",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "#!/sbin/openrc-run\n",
+ "\n",
+ "supervisor=supervise-daemon\n",
+ "command=\"/usr/local/bin/fastapi_blog.sh\"\n",
+ "pidfile=\"/run/${RC_SVCNAME}.pid\"\n",
+ "command_args=\"-p ${pidfile}\"\n",
+ "command_background=True"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "8982eef9-a095-4b57-b0a6-5d59ac12be78",
+ "metadata": {},
+ "source": [
+ "Save it as a root user in /etc/init.d and make executable:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "6336a60f-6e8a-489b-8bc8-9b9e6f8e02fc",
+ "metadata": {},
+ "source": [
+ "Save it as a root user in /etc/init.d and make executable:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "445a5744-8e71-4104-8601-ad444ca0f9c1",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# chmod ugo+x /etc/init.d/fastapi_blog"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "0985e123-559c-4f9b-8bfc-7f75e9251756",
+ "metadata": {},
+ "source": [
+ "You should be able to start/restart/stop it normally:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "c856613b-8541-43e4-b7e0-1d0f2a4a6d22",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# rc-service fastapi_blog start\n",
+ "# rc-service fastapi_blog restart\n",
+ "# rc-service fastapi_blog stop"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "719552bc-b736-41db-939c-6a1010ca9557",
+ "metadata": {},
+ "source": [
+ "If you're using Unix socket, check for existence of /run/fastapi_blog.sock.\n",
+ "Else navigate to http://127.0.0.1:8000/test.html to see page is indeed there."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "b8f408bf-120c-4adf-8e8b-3dc019a015ae",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "$ ps aux | grep \"python main.py\""
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "id": "13c17523-3603-4b8a-b8a2-563ae6dc27d5",
+ "metadata": {},
+ "source": [
+ "You want to see only one 'python main.py'.\n",
+ "At first I did not use exec which resulted in lingering processess. You don't want that.\n",
+ "\n",
+ "Now you can plug it in to your preffered proxy server using either a unix socket or a TCP socket."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "3134cb86-be0f-456c-b755-cd4f43ef2810",
+ "metadata": {},
+ "source": [
+ "If you're using unix socket, check for existence of /run/fastapi_blog.sock, else either use lsof to see what's using port 8000 or navigate to http://127.0.0.1:8000/test.html"
+ ]
+ }
+ ],
+ "metadata": {
+ "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.12.7"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}