From d416fcd9103ee0494c798575c1a2eb973d468e43 Mon Sep 17 00:00:00 2001 From: Daniel Liden Date: Thu, 12 Sep 2024 09:27:37 -0500 Subject: [PATCH] adds fasthtml section --- notebooks/_toc.yml | 10 +- .../web_dev/fasthtml/1_introduction.ipynb | 506 ++++++++++++++++++ notebooks/web_dev/fasthtml/2_styling.ipynb | 435 +++++++++++++++ notebooks/web_dev/fasthtml/3_htmx.ipynb | 187 +++++++ notebooks/web_dev/fasthtml/4_databases.ipynb | 338 ++++++++++++ notebooks/web_dev/fasthtml/5_forms.ipynb | 81 +++ notebooks/web_dev/intro.md | 8 + 7 files changed, 1564 insertions(+), 1 deletion(-) create mode 100644 notebooks/web_dev/fasthtml/1_introduction.ipynb create mode 100644 notebooks/web_dev/fasthtml/2_styling.ipynb create mode 100644 notebooks/web_dev/fasthtml/3_htmx.ipynb create mode 100644 notebooks/web_dev/fasthtml/4_databases.ipynb create mode 100644 notebooks/web_dev/fasthtml/5_forms.ipynb create mode 100644 notebooks/web_dev/intro.md diff --git a/notebooks/_toc.yml b/notebooks/_toc.yml index e7c570d..13845f7 100644 --- a/notebooks/_toc.yml +++ b/notebooks/_toc.yml @@ -15,4 +15,12 @@ parts: - file: ai_training/fine_tuning/5_gemma_2b_axolotl/gemma_2b_axolotl - file: ai_training/appendix sections: - - file: ai_training/fine_tuning/3_tinyllama_instruction_tune/data_preprocessing \ No newline at end of file + - file: ai_training/fine_tuning/3_tinyllama_instruction_tune/data_preprocessing +- caption: FastHTML + chapters: + - file: web_dev/FastHTML Tutorial + sections: + - file: web_dev/fasthtml/1_introduction + - file: web_dev/fasthtml/2_styling + - file: web_dev/fasthtml/3_htmx + - file: web_dev/fasthtml/4_databases diff --git a/notebooks/web_dev/fasthtml/1_introduction.ipynb b/notebooks/web_dev/fasthtml/1_introduction.ipynb new file mode 100644 index 0000000..631731b --- /dev/null +++ b/notebooks/web_dev/fasthtml/1_introduction.ipynb @@ -0,0 +1,506 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Introduction\n", + "\n", + "In this guide, we will develop an intuition for the basic principles and concepts of web development. We will do so using the FastHTML library, which enables us to create websites and web applications using Python. Crucially, it still requires considerable understanding of how web pages and web apps are built and how they work. It won't make it easy to build web pages without understanding HTML/CSS or HTTP requests. But it might mean that there is now a \"Pythonic\" way to learn it and to get started quickly.\n", + "\n", + "FastHTML is a bit different from other web development frameworks insofar as you can write Python functions to handle server logic without having to write a separate HTML template and Javascript for client-side interactivity. This is because FastHTML is designed to be a Pythonic way to generate HTML content, and it uses htmx for client-side interactivity. This approach is somewhat controversial, as exemplified by this Hacker News exchange with the creator of FastHTML:\n", + "\n", + "![Hacker News exchange with FastHTML creator](./images/jh_soc_hn.png)\n", + "\n", + "The key thing to remember here is that we return HTML content directly from our Python functions rather than populating a template and then rendering it.\n", + "\n", + "The goal of this guide is to start as simply as possible and to add more and more complexity to that simple start, explaining what is happening at each step and mapping the FastHTML code to a broader understanding of web development concepts.\n", + "\n", + "## A Simple Start\n", + "As always...let's start with a Hello, World. In this case, we will develop a web page that says \"Hello, World\" that you can serve and see in your browser." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "from fasthtml.common import *\n", + "\n", + "app, rt = fast_app()\n", + "\n", + "@rt(\"/\")\n", + "def get():\n", + " return Titled(\"FastHTML\", P(\"Hello, World!\"))\n", + "\n", + "serve()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To see the results, save this as a `.py` file and run it with `python .py`. You can save a cell as a file with the `%%writefile .py` magic command. There are a few things happening here that are immediately worth pointing out:\n", + "1. The `@rt(\"/\")` decorator tells us the *route* with which the decorated function will be associated. `/` is just the index page or main page.\n", + "2. `get()` isn't just an arbitrary function name—it is an HTTP verb or [HTTP request method](hhttps://developer.mozilla.org/en-US/docs/Web/HTTP/Methods). HTTP methods specify the type of action to be performed; `get` means that we want to retrieve some data. Note that FastHTML gives a few different ways of specifying routes and methods. We could have decorated a differently-named function with `@app.get(\"/\")`. The following cell does the same thing; replace `app.py` with its contents and run it again to see." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [], + "source": [ + "#%%writefile app_1.py\n", + "from fasthtml.common import *\n", + "\n", + "app, rt = fast_app()\n", + "\n", + "@app.get(\"/\")\n", + "def hello():\n", + " return Titled(\"Hello, World!\")\n", + "\n", + "serve()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To recap what happens: when a user goes to the home page (`/`), a `get()` http request is triggered, which, in this case, calls the `hello()` function, which generates the resulting \"Hello, World\" page.\n", + "\n", + "But what does \"generates the resulting \"Hello, World\" page actually mean? What FastHTML does is generate the HTML structure for the page. This HTML is then sent back to the user's browser, which renders it as a web page displaying \"Hello, World\". We can actually see this raw HTML using the Starlette `TestClient`. (Starlette is an \"Asynchronous Server Gateway Interface\" or ASGI framework and one of the core components of FastHTML. Don't worry about it for now.)\n", + "\n", + "Here's how you can see the generated HTML (e.g. in a Jupyter notebook)." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n", + "\n", + " \n", + " Hello, World!\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "

Hello, World!

\n", + "
\n", + "\n", + "\n", + "\n" + ] + } + ], + "source": [ + "from starlette.testclient import TestClient\n", + "client = TestClient(app)\n", + "r = client.get(\"/\")\n", + "print(r.text)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You'll notice a few things:\n", + "- We ended up with the `title` Hello, World (`Hello, World`)\n", + "- In the page body, `Hello, World` is wrapped in `h1` tags.\n", + "\n", + "## Modifying our First Webpage\n", + "`Titled` is an FT (FastTags) component that combines a Title and H1 HTML tag. What happens if we use something simpler, like [`

`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/p)? \n" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [], + "source": [ + "#%%writefile app_1.py\n", + "from fasthtml.common import *\n", + "\n", + "app, rt = fast_app()\n", + "\n", + "@app.get(\"/\")\n", + "def hello():\n", + " return P(\"Hello, World!\")\n", + "\n", + "serve()" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n", + "\n", + " \n", + " FastHTML page\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "

Hello, World!

\n", + "\n", + "\n", + "\n" + ] + } + ], + "source": [ + "client = TestClient(app)\n", + "r = client.get(\"/\")\n", + "print(r.text)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "When we make this change, the `title` reverts to the default `FastHTML page` and the Hello, World text is no longer formatted as a header. But let's add the title and formatting back in, *without* using `Titled`:" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": {}, + "outputs": [], + "source": [ + "# %%writefile app_1.py\n", + "from fasthtml.common import *\n", + "\n", + "app, rt = fast_app()\n", + "\n", + "\n", + "@app.get(\"/\")\n", + "def hello():\n", + " return (Title(\"Hello, World!\"), Main(H1(\"Hello, World!\"), cls=\"container\"))\n", + "\n", + "\n", + "serve()" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n", + "\n", + " \n", + " Hello, World!\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "

Hello, World!

\n", + "
\n", + "\n", + "\n", + "\n" + ] + } + ], + "source": [ + "client = TestClient(app)\n", + "r = client.get(\"/\")\n", + "print(r.text)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Our `hello` function can return multiple components, some of which might be nested. In this case, we returned a `Title` and the \"Hello, World\" text nested in `main` and `h1` tags. Furthermore, we specified the `container` class for our `main` element. By default, FastHTML uses PicoCSS for styling; the default [PicoCSS container](https://picocss.com/docs/container) format is centered and fixed width. If you omit the `container` specification, you'll see the \"Hello, World\" text pushed to the left margin.\n", + "\n", + "Let's add one more element: some simple text under the Hello, World header." + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "metadata": {}, + "outputs": [], + "source": [ + "#%%writefile app_1.py\n", + "from fasthtml.common import *\n", + "\n", + "app, rt = fast_app()\n", + "\n", + "\n", + "@app.get(\"/\")\n", + "def hello():\n", + " return (Title(\"Hello, World!\"), Main(H1(\"Hello, World!\"), cls=\"container\"),\n", + " Div(P(\"Goodbye, World!\"), cls=\"container\"))\n", + "\n", + "\n", + "serve()" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n", + "\n", + " \n", + " Hello, World!\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "

Hello, World!

\n", + "
\n", + "

Goodbye, World!

\n", + "
\n", + " \n", + "\n", + "\n" + ] + } + ], + "source": [ + "client = TestClient(app)\n", + "r = client.get(\"/\")\n", + "print(r.text)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Adding Another Page\n", + "\n", + "Right now, our web page is just one page. Let's add another page, and links for navigating back and forth between them.\n", + "\n", + "***Note**: At this point, I'm going to stop showing the html after each change. But do check it yourself, either with the Starlette TestClient or by viewing the source in your browser!*" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting app_0.py\n" + ] + } + ], + "source": [ + "%%writefile app_1.py\n", + "from fasthtml.common import *\n", + "\n", + "app, rt = fast_app()\n", + "\n", + "\n", + "@app.get(\"/\")\n", + "def hello():\n", + " return (Title(\"Hello, World!\"), Main(H1(\"Hello, World!\"), cls=\"container\"),\n", + " Div(P(\"Goodbye, World!\"), cls=\"container\"))\n", + "\n", + "@rt(\"/about\")\n", + "def get():\n", + " return(Titled(\"About this Site\",\n", + " P(\"This is an example site built with FastHTML!\"),\n", + " A(\"Return Home\", href=\"/\")))\n", + "\n", + "\n", + "serve()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This is missing something—but let's take a look before we finish it anyway. Save and run the script again. It will take you to the home page, but there's no way to get to the new \"about\" page. But you can still get there manually: just add \"about\" to the end of the url (e.g. `http://0.0.0.0:5001/about`). By navigating to that URL, you are manually invoking the `get` request we defined on that route.\n", + "\n", + "Of course, we'd rather have a link on the home page. Let's add it. Just like in the \"about\" route, we use the `A` FT component." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting app_0.py\n" + ] + } + ], + "source": [ + "%%writefile app_1.py\n", + "from fasthtml.common import *\n", + "\n", + "app, rt = fast_app()\n", + "\n", + "\n", + "@app.get(\"/\")\n", + "def hello():\n", + " return (Title(\"Hello, World!\"), Main(H1(\"Hello, World!\"), cls=\"container\"),\n", + " Div(P(\"Goodbye, World!\"), A(\"About\", href=\"/about\"), cls=\"container\"))\n", + "\n", + "@rt(\"/about\")\n", + "def get():\n", + " return(Titled(\"About this Site\",\n", + " P(\"This is an example site built with FastHTML!\"),\n", + " A(\"Return Home\", href=\"/\")))\n", + "\n", + "\n", + "serve()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "One last thing in this part of the tutorial—you might want to inspect the html of *specific components* as you're developing the site. You don't need the TestClient or your browser's developer tools to do this. If you're working in a Jupyter notebook, simply running the cell with a component will print out the HTML. And calling `show()` on that component will actually display the rendered HTML." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "data": { + "text/markdown": [ + "```html\n", + "Return Home\n", + "\n", + "```" + ], + "text/plain": [ + "a(('Return Home',),{'href': '/'})" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "A(\"Return Home\", href=\"/\")" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "Return Home\n" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "show(A(\"Return Home\", href=\"/\"))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Review\n", + "This concludes the first part of our FastHTML tutorial. At this point, you should have a very basic understanding of:\n", + "1. How to locally serve a simple FastHTML website\n", + "2. How to use the Starlette TestCLient to view the HTML generated by http `get` requests on specific routes\n", + "3. How FT components map Python to HTML\n", + "4. How to add multiple pages and links between them to a site\n", + "5. How to preview components in a Jupyter notebook\n", + "\n", + "There's already a lot you can do at this point. I recommend stopping here and taking some time to experiment. Add more pages, try out different layouts. Pick a few HTML elements from [this list](https://developer.mozilla.org/en-US/docs/Web/HTML/Element) and figure out how the FT version works (hint: FT components start with capital letters). You might, for example, try adding a `Blockquote` or an `Hr` component.\n", + "\n", + "In the next section, we will focus on customizing our website. After that, we will start exploring how to add more interesting and sophisticated capabilities using Python code and http methods other than `get`." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv", + "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.4" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/notebooks/web_dev/fasthtml/2_styling.ipynb b/notebooks/web_dev/fasthtml/2_styling.ipynb new file mode 100644 index 0000000..36fc148 --- /dev/null +++ b/notebooks/web_dev/fasthtml/2_styling.ipynb @@ -0,0 +1,435 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Changing the Appearance of FastHTML Applications and Components\n", + "\n", + "How do you change the appearance of your FastHTML application? There are a few different ways. Be aware: they require you to know at least some CSS. We'll cover the following:\n", + "1. PicoCSS: the default CSS framework used by FastHTML\n", + "2. Using a different CSS framework\n", + "3. Applying inline styles\n", + "4. Using your own custom CSS stylesheet\n", + "\n", + "Understanding there approaches—and where to find more details on CSS—will give you a great deal of control ovet the appearance of your site.\n", + "\n", + "## CSS Basics\n", + "Let's start by answering a few basic questions about CSS and how it affects a webpage. We're not going to provide any examples of CSS outside the context of FastHTML; for a more comprehensive overview, see the [CSS Basics MDN Docs](https://developer.mozilla.org/en-US/docs/Learn/Getting_started_with_the_web/CSS_basics)\n", + "1. What is CSS? *CSS (Cascading Style Sheets) is a style sheet language that lets you selectively apply style options to HTML elements. With CSS, you specify the types of HTML components you want to be affected, and how you want them displayed on the page.*\n", + "2. What is a CSS framework? *A [CSS framework](https://en.wikipedia.org/wiki/CSS_framework) like PicoCSS is an opinionated library of styles/templates that provides pre-defined classes and components to help you quickly build and style web pages.*\n", + "3. How is CSS applied? *CSS can be applied in three ways: inline styles (directly in the HTML element), internal styles (within a `