前言 本文為 Python 工作坊的基礎教材,以實作「短網址產生器」應用程式為例。
建立專案 建立專案。
1 2 mkdir shortener-pythoncd shortener-python
建立虛擬環境 建立虛擬環境。
啟動虛擬環境。
1 source .venv/bin/activate
將 .venv
資料夾排除在版本控制之外。
1 echo "*" > .venv/.gitignore
建立 main.py
檔。
初始化版本控制 初始化版本控制。
將所有修改添加到暫存區。
提交修改。
1 git commit -m "Initial commit"
指定遠端儲存庫位址。
推送程式碼到遠端儲存庫。
安裝 Ruff 格式化工具 新增 requirements.txt
檔。
修改 requirements.txt
檔,添加 ruff
依賴套件。
安裝依賴套件。
1 pip install -r requirements.txt
新增 ruff.toml
檔。
1 2 3 4 5 line-length = 120 indent-width = 4 [format] quote-style = "double"
新增 .vscode/settings.json
檔。
1 2 3 4 5 6 7 8 { "editor.formatOnSave" : true , "editor.codeActionsOnSave" : { "source.fixAll" : "explicit" , "source.organizeImports" : "explicit" } , "editor.defaultFormatter" : "charliermarsh.ruff" }
提交修改。
1 2 git add . git commit -m "Add ruff dependency"
安裝 FastAPI 框架 修改 requirements.txt
檔,添加 fastapi[standard]
依賴套件。
安裝依賴套件。
1 pip install -r requirements.txt
修改 main.py
檔。
1 2 3 4 5 6 7 8 from fastapi import FastAPIapp = FastAPI() @app.get("/" ) def read_root (): return {"Hello" : "World" }
啟動伺服器。
新增 .gitignore
檔,將 __pycache__
資料夾排除在版本控制之外。
提交修改。
1 2 git add . git commit -m "Add fastapi dependency"
安裝 nanoid 套件 修改 requirements.txt
檔,添加 nanoid
依賴套件。
安裝依賴套件。
1 pip install -r requirements.txt
修改 main.py
檔。
1 2 3 4 5 6 7 8 9 from fastapi import FastAPIfrom nanoid import generateapp = FastAPI() @app.get("/" ) def read_root (): return {"code" : generate(size=8 )}
提交修改。
1 2 git add . git commit -m "Add nanoid dependency"
實作後端 修改 main.py
檔。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 from fastapi import FastAPI, HTTPExceptionfrom fastapi.responses import RedirectResponsefrom nanoid import generatefrom pydantic import BaseModelapp = FastAPI() links = dict () class LinkCreateRequest (BaseModel ): link: str class LinkCreateResponse (BaseModel ): code: str @app.post("/api/links" , tags=["Link" ], response_model=LinkCreateResponse ) def create_link (body: LinkCreateRequest ): code = generate(size=8 ) links[code] = body.link return LinkCreateResponse(code=code) @app.get("/{code}" , tags=["Link" ] ) def redirect (code: str ): link = links.get(code) if link is None : raise HTTPException(status_code=404 , detail="Link not found" ) return RedirectResponse(url=link)
重構 建立 schema/link.py
檔,將請求與回應的結構存放在單獨的檔案。
1 2 3 4 5 6 7 8 9 from pydantic import BaseModelclass LinkCreateRequest (BaseModel ): link: str class LinkCreateResponse (BaseModel ): code: str
建立 schema/__init__.py
檔。
修改 main.py
檔,引入請求與回應的結構。
1 2 3 4 5 6 7 from fastapi import FastAPI, HTTPExceptionfrom fastapi.responses import RedirectResponsefrom nanoid import generatefrom schema.link import LinkCreateRequest, LinkCreateResponse
提交修改。
1 2 git add . git commit -m "Add link creation and redirection functionality"
欄位驗證 修改 schema/link.py
檔。
1 2 3 4 5 6 7 8 9 from pydantic import BaseModel, HttpUrlclass LinkCreateRequest (BaseModel ): link: HttpUrl class LinkCreateResponse (BaseModel ): code: str
提交修改。
1 2 git add . git commit -m "Add link validation"
實作前端 首先,修改 main.py
檔,試著新增一個會回傳 HTML 內容的端點。
1 2 3 4 5 6 7 8 9 10 11 12 @app.get("/" , tags=["Static" ], response_class=HTMLResponse ) def read_root (): return """ <html> <head> <title>URL Shortener</title> </head> <body> <h1>Welcome to the URL Shortener</h1> </body> </html> """
重構 新增一個獨立的 static/index.html
檔。
1 2 3 4 5 6 7 8 <html > <head > <title > URL Shortener</title > </head > <body > <h1 > Welcome to the URL Shortener</h1 > </body > </html >
修改 main.py
檔,改成讀取 index.html
檔。
1 2 3 4 @app.get("/" , tags=["Static" ], response_class=HTMLResponse ) def read_root (): with open (os.path.join("static" , "index.html" ), "r" ) as file: return HTMLResponse(content=file.read())
實作功能 修改 static/index.html
檔。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > URL Shortener</title > <style > .container { display : flex; margin-bottom : 16px ; max-width : 100% ; width : 400px ; } #input , #output { margin-right : 8px ; overflow : auto; width : 100% ; } button { width : 100px ; } </style > </head > <body > <h1 > Welcome to the URL Shortener</h1 > <div class ="container" > <input id ="input" type ="text" > <button id ="shorten" > Shorten</button > </div > <div class ="container" > <span id ="output" > </span > <button id ="copy" hidden > Copy</button > </div > <script > const input = document .querySelector ('#input' ); const output = document .querySelector ('#output' ); const shortenButton = document .querySelector ('#shorten' ); const copyButton = document .querySelector ('#copy' ); shortenButton.addEventListener ('click' , async () => { const link = input.value ; try { const response = await fetch ('/api/links' , { method : 'POST' , headers : { 'Content-Type' : 'application/json' }, body : JSON .stringify ({ link }) }); const data = await response.json (); if (!response.ok ) { throw new Error (data.detail [0 ].msg ); } const url = `${window .location.origin} /${data.code} ` ; output.innerHTML = `<a href="${url} " target="_blank" rel="noopener noreferrer">${url} </a>` ; copyButton.hidden = false ; } catch (err) { console .error (err); output.textContent = 'An error occurred. Please try again.' ; copyButton.hidden = true ; } }); copyButton.addEventListener ('click' , () => { const url = output.querySelector ('a' ).href ; navigator.clipboard .writeText (url); }); </script > </body > </html >
提交修改。
1 2 git add . git commit -m "Add static page"
實現持久化 首先,到 Supabase 建立一個新專案,並取得 API URL 和 API 金鑰。
建立一個名為 links
的資料表,欄位設置如下:
Column Name
Data Type
id
int8
created_at
timestamptz
code
varchar
link
text
新增 .env.example
檔。
1 2 SUPABASE_API_URL= SUPABASE_API_KEY=
新增 .env
檔。
1 2 SUPABASE_API_URL=your-supabase-api-url SUPABASE_API_KEY=your-supabase-api-key
修改 .gitignore
檔,忽略 .env
檔。
安裝依賴套件 修改 requirements.txt
檔,添加 supabase
和 python-dotenv
依賴套件。
1 2 3 # ... supabase python-dotenv
安裝依賴套件。
1 pip install -r requirements.txt
操作資料庫 修改 main.py
檔,將資料儲存在 Supabase 資料庫中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 import osimport supabasefrom dotenv import load_dotenvfrom fastapi import FastAPI, HTTPException, statusfrom fastapi.responses import HTMLResponse, RedirectResponsefrom nanoid import generatefrom schema.link import LinkCreateRequest, LinkCreateResponseload_dotenv() app = FastAPI() supabase_client = supabase.create_client( os.getenv("SUPABASE_API_URL" ), os.getenv("SUPABASE_API_KEY" ), ) @app.get("/" , tags=["Static" ], response_class=HTMLResponse ) def read_root (): with open (os.path.join("static" , "index.html" ), "r" ) as file: return HTMLResponse(content=file.read()) @app.post("/api/links" , tags=["Link" ], response_model=LinkCreateResponse ) def create_link (body: LinkCreateRequest ): code = generate(size=8 ) result = ( supabase_client.from_("links" ) .insert( { "code" : code, "link" : str (body.link), } ) .execute() ) return LinkCreateResponse(**result.data[0 ]) @app.get("/{code}" , tags=["Link" ] ) def redirect (code: str ): result = supabase_client.from_("links" ).select("*" ).eq("code" , code).execute() if not result.data: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Thread not found" ) url = result.data[0 ]["link" ] return RedirectResponse(url=url)
提交修改。
1 2 git add . git commit -m "Add Supabase integration"
參考資料