Notitie
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen u aan te melden of de directory te wijzigen.
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen de mappen te wijzigen.
FastAPI is een modern Python-webframework voor het bouwen van API's. In combinatie met mssql-python kun je high-performance REST API's bouwen die worden ondersteund door Microsoft SQL en Azure SQL Database.
Prerequisites
- Python 3.10 of hoger.
- Installeer eenmalige vereisten voor het besturingssysteem. Windows-gebruikers kunnen deze stap overslaan. Voor volledige platformdetails, zie Install mssql-python.
Een SQL-database maken
Maak een SQL-database aan of maak verbinding met een van de volgende platforms:
De voorbeelden in dit artikel gebruiken de voorbeelddatabase van AdventureWorksLT , specifiek de SalesLT.Product tabel. Als je AdventureWorksLT niet hebt geïnstalleerd, zie dan de voorbeelddatabases van AdventureWorks.
Projectopstelling
Een virtuele omgeving maken
Maak een virtuele omgeving aan en activeer deze zodat de pakketten van dit project geïsoleerd blijven van andere Python-installaties. Deze stap voorkomt ook het veelvoorkomende probleem van het installeren van pakketten in één interpreter tijdens het uitvoeren van je app of tests met een andere.
py -m venv .venv
.\.venv\Scripts\Activate.ps1
Nadat je de omgeving hebt geactiveerd, verwijzen python, pip en pytest allemaal naar dezelfde interpreter. Voer de resterende commando's uit dit artikel vanuit de geactiveerde omgeving.
Note
Op Windows on Arm maak je de omgeving met een Arm64-build van Python so mssql-python en de bijbehorende afhankelijkheden worden geïnstalleerd vanaf vooraf gebouwde wielen. Op een machine met meer dan één Python-versie kan py -m venv een andere versie of architectuur selecteren dan je verwacht, dus controleer dit met python -c "import sys, sysconfig; print(sys.version, sysconfig.get_platform())" nadat je hebt geactiveerd. Als pip probeert cryptography vanuit broncode te bouwen (een fout met de Rust- en OpenSSL-toolchain), installeer dan eerst een versie op basis van een wheel met pip install --only-binary=:all: cryptography en installeer daarna de rest.
Afhankelijkheden installeren
Installeer de benodigde pakketten met pip:
pip install fastapi uvicorn mssql-python pydantic
Projectstructuur
Organiseer je project met aparte modules voor database, schema's en CRUD-operaties:
my_api/
├── main.py
├── database.py
├── models.py
├── schemas.py
├── crud.py
└── routers/
└── products.py
Databaseverbindingsbeheer
FastAPI gebruikt afhankelijkheidsinjectie om middelen zoals databaseverbindingen aan routehandlers te leveren. Het patroon in deze sectie opent een verbinding, levert een cursor op en gebruikt de mssql-python verbindingscontextmanager om te committen bij succes, een uitzondering terug te rollen en de verbinding te sluiten.
Maak het bestand database.py
De get_connection_string() functie bouwt de ODBC-verbindingsreeks op basis van configuratiewaarden. FastAPI roept Depends()get_db_dependency() één keer per verzoek aan en beheert zijn levenscyclus.
# database.py
import mssql_python
from collections.abc import Generator
# Configuration
DATABASE_CONFIG = {
"server": "<server>.database.windows.net",
"database": "<database>",
}
def get_connection_string() -> str:
"""Build connection string from config."""
return (
f"Server={DATABASE_CONFIG['server']};"
f"Database={DATABASE_CONFIG['database']};"
"Authentication=ActiveDirectoryDefault;"
"Encrypt=yes"
)
Note
ActiveDirectoryDefault gebruikt DefaultAzureCredential, dat meerdere credentialproviders achter elkaar probeert. De eerste verbinding kan traag zijn omdat de SDK de keten doorloopt totdat hij een werkende provider vindt. In productie, als je weet welk type inloggegevens je omgeving gebruikt, specificeer het dan direct (bijvoorbeeld ActiveDirectoryMSI voor managed identity) om de chain walk te voorkomen. Zie Microsoft Entra-verificatie voor meer informatie.
def get_db_dependency() -> Generator:
"""FastAPI dependency for database cursor."""
with mssql_python.connect(get_connection_string()) as conn:
with conn.cursor() as cursor:
yield cursor
Pydantische modellen
Pydantische modellen definiëren de vorm- en validatieregels voor verzoek- en responsgegevens. FastAPI gebruikt deze modellen om binnenkomende JSON te analyseren, veldbeperkingen te valideren en OpenAPI-documentatie automatisch te genereren.
Maak het bestand schemas.py
Splits schema’s op in Base, Create, Update en antwoordvarianten. Het Base schema bevat gedeelde velden, Create erft ervan voor invoegoperaties en Update maakt alle velden optioneel voor gedeeltelijke updates.
# schemas.py
from pydantic import BaseModel, ConfigDict, EmailStr, Field
from typing import Optional
from datetime import datetime
# Product schemas
class ProductBase(BaseModel):
name: str = Field(..., min_length=1, max_length=100)
product_number: str = Field(..., min_length=1, max_length=25)
price: float = Field(..., gt=0)
color: Optional[str] = Field(None, max_length=50)
size: Optional[str] = Field(None, max_length=50)
category_id: Optional[int] = None
class ProductCreate(ProductBase):
pass
class ProductUpdate(BaseModel):
name: Optional[str] = Field(None, min_length=1, max_length=100)
product_number: Optional[str] = Field(None, min_length=1, max_length=25)
price: Optional[float] = Field(None, gt=0)
color: Optional[str] = Field(None, max_length=50)
size: Optional[str] = Field(None, max_length=50)
category_id: Optional[int] = None
class Product(ProductBase):
id: int
model_config = ConfigDict(from_attributes=True)
# Pagination
class PaginatedResponse(BaseModel):
items: list
total: int
page: int
page_size: int
pages: int
CRUD-bewerkingen
Breng databasequery's onder in een aparte klasse om route-handlers compact te houden. Elke statische methode neemt een cursor (geïnjecteerd door FastAPI) en verwerkt één bewerking met geparametriseerde queries (%(name)s placeholders met een woordenboek van waarden) om SQL-injectie te voorkomen. Deze scheiding maakt de bedrijfslogica gemakkelijker te testen en hergebruiken.
Maak crud.py aan
# crud.py
from typing import Optional, List
from schemas import ProductCreate, ProductUpdate, Product
class ProductCRUD:
"""CRUD operations for products."""
@staticmethod
def get(cursor, product_id: int) -> Optional[dict]:
cursor.execute("""
SELECT ProductID, Name, ProductNumber, ListPrice, Color, Size
FROM SalesLT.Product
WHERE ProductID = %(id)s
""", {"id": product_id})
row = cursor.fetchone()
if row:
return {
"id": row.ProductID,
"name": row.Name,
"product_number": row.ProductNumber,
"price": float(row.ListPrice),
"color": row.Color,
"size": row.Size
}
return None
@staticmethod
def get_all(cursor, skip: int = 0, limit: int = 100) -> List[dict]:
cursor.execute("""
SELECT ProductID, Name, ProductNumber, ListPrice, Color, Size
FROM SalesLT.Product
ORDER BY ProductID
OFFSET %(skip)s ROWS
FETCH NEXT %(limit)s ROWS ONLY
""", {"skip": skip, "limit": limit})
return [{
"id": row.ProductID,
"name": row.Name,
"product_number": row.ProductNumber,
"price": float(row.ListPrice),
"color": row.Color,
"size": row.Size
} for row in cursor.fetchall()]
@staticmethod
def count(cursor) -> int:
cursor.execute("SELECT COUNT(*) FROM SalesLT.Product")
return cursor.fetchval()
@staticmethod
def create(cursor, product: ProductCreate) -> dict:
cursor.execute("""
INSERT INTO SalesLT.Product (Name, ProductNumber, ListPrice, Color, Size, ProductCategoryID, StandardCost, SellStartDate)
OUTPUT INSERTED.ProductID, INSERTED.Name, INSERTED.ProductNumber,
INSERTED.ListPrice, INSERTED.Color, INSERTED.Size
VALUES (%(name)s, %(product_number)s, %(price)s, %(color)s, %(size)s, %(category_id)s, 0, GETDATE())
""", {
"name": product.name,
"product_number": product.product_number,
"price": product.price,
"color": product.color,
"size": product.size,
"category_id": product.category_id
})
row = cursor.fetchone()
return {
"id": row.ProductID,
"name": row.Name,
"product_number": row.ProductNumber,
"price": float(row.ListPrice),
"color": row.Color,
"size": row.Size
}
@staticmethod
def update(cursor, product_id: int, product: ProductUpdate) -> Optional[dict]:
# Build dynamic update
updates = []
params = {"id": product_id}
if product.name is not None:
updates.append("Name = %(name)s")
params["name"] = product.name
if product.product_number is not None:
updates.append("ProductNumber = %(product_number)s")
params["product_number"] = product.product_number
if product.price is not None:
updates.append("ListPrice = %(price)s")
params["price"] = product.price
if product.category_id is not None:
updates.append("ProductCategoryID = %(category_id)s")
params["category_id"] = product.category_id
if not updates:
return ProductCRUD.get(cursor, product_id)
cursor.execute(f"""
UPDATE SalesLT.Product SET {', '.join(updates)}
OUTPUT INSERTED.ProductID, INSERTED.Name, INSERTED.ProductNumber,
INSERTED.ListPrice, INSERTED.Color, INSERTED.Size
WHERE ProductID = %(id)s
""", params)
row = cursor.fetchone()
if row:
return {
"id": row.ProductID,
"name": row.Name,
"product_number": row.ProductNumber,
"price": float(row.ListPrice),
"color": row.Color,
"size": row.Size
}
return None
@staticmethod
def delete(cursor, product_id: int) -> bool:
cursor.execute("""
DELETE FROM SalesLT.Product WHERE ProductID = %(id)s
""", {"id": product_id})
return cursor.rowcount > 0
@staticmethod
def search(cursor, query: str, skip: int = 0, limit: int = 100) -> List[dict]:
cursor.execute("""
SELECT ProductID, Name, ProductNumber, ListPrice, Color, Size
FROM SalesLT.Product
WHERE Name LIKE %(query)s OR ProductNumber LIKE %(query)s
ORDER BY ProductID
OFFSET %(skip)s ROWS
FETCH NEXT %(limit)s ROWS ONLY
""", {"query": f"%{query}%", "skip": skip, "limit": limit})
return [{
"id": row.ProductID,
"name": row.Name,
"product_number": row.ProductNumber,
"price": float(row.ListPrice),
"color": row.Color,
"size": row.Size
} for row in cursor.fetchall()]
FastAPI-toepassing
Maak het bestand main.py aan
De hoofdmodule verbindt alles met elkaar. Elke route definieert cursor = Depends(get_db_dependency), waarmee FastAPI wordt verteld de generator aan te roepen, de opgeleverde cursor aan de handler door te geven en deze daarna op te ruimen. FastAPI valideert ook verzoeklichamen tegen je Pydantic-schema's voordat de handler draait.
# main.py
from fastapi import FastAPI, HTTPException, Depends, Query
from typing import List
from database import get_db_dependency
from schemas import Product, ProductCreate, ProductUpdate, PaginatedResponse
from crud import ProductCRUD
app = FastAPI(
title="Product API",
description="REST API for products using mssql-python",
version="1.0.0"
)
@app.get("/")
def root():
return {"message": "Product API", "docs": "/docs"}
@app.get("/products", response_model=PaginatedResponse)
def list_products(
page: int = Query(1, ge=1),
page_size: int = Query(10, ge=1, le=100),
cursor = Depends(get_db_dependency)
):
"""List all products with pagination."""
skip = (page - 1) * page_size
items = ProductCRUD.get_all(cursor, skip=skip, limit=page_size)
total = ProductCRUD.count(cursor)
return {
"items": items,
"total": total,
"page": page,
"page_size": page_size,
"pages": (total + page_size - 1) // page_size
}
@app.get("/products/{product_id}", response_model=Product)
def get_product(product_id: int, cursor = Depends(get_db_dependency)):
"""Get a specific product by ID."""
product = ProductCRUD.get(cursor, product_id)
if not product:
raise HTTPException(status_code=404, detail="Product not found")
return product
@app.post("/products", response_model=Product, status_code=201)
def create_product(product: ProductCreate, cursor = Depends(get_db_dependency)):
"""Create a new product."""
return ProductCRUD.create(cursor, product)
@app.put("/products/{product_id}", response_model=Product)
def update_product(
product_id: int,
product: ProductUpdate,
cursor = Depends(get_db_dependency)
):
"""Update an existing product."""
updated = ProductCRUD.update(cursor, product_id, product)
if not updated:
raise HTTPException(status_code=404, detail="Product not found")
return updated
@app.delete("/products/{product_id}", status_code=204)
def delete_product(product_id: int, cursor = Depends(get_db_dependency)):
"""Delete a product."""
if not ProductCRUD.delete(cursor, product_id):
raise HTTPException(status_code=404, detail="Product not found")
@app.get("/products/search/", response_model=List[Product])
def search_products(
q: str = Query(..., min_length=1),
page: int = Query(1, ge=1),
page_size: int = Query(10, ge=1, le=100),
cursor = Depends(get_db_dependency)
):
"""Search products by name or product number."""
skip = (page - 1) * page_size
return ProductCRUD.search(cursor, q, skip=skip, limit=page_size)
# Health check endpoint
@app.get("/health")
def health_check(cursor = Depends(get_db_dependency)):
"""Check database connectivity."""
try:
cursor.execute("SELECT 1")
return {"status": "healthy", "database": "connected"}
except Exception:
raise HTTPException(status_code=503, detail="Database unavailable")
De toepassing uitvoeren
uvicorn main:app --reload --host 0.0.0.0 --port 8000
Test en implementeer de applicatie
Gebruik het bijbehorende artikel om de aanvraag af te ronden:
Foutafhandeling
Het bijbehorende artikel behandelt de afhandeling van database-uitzonderingen.
Globale uitzonderingshandler
Zie Databasefouten behandelen.
Groepsgewijze verbindingen
Het bijbehorende artikel behandelt de configuratie van de verbindingspool.
Verbeterde databasemodule
Zie Verbindingspooling configureren.
middleware voor authenticatie
Zie Toevoegen van authenticatieafhankelijkheden.
Testing
Het bijbehorende artikel behandelt integratietesten.
Testopstelling
Zie Test de applicatie.
Implementatieconfiguratie
Het bijbehorende artikel behandelt de configuratie en operaties van de inzet.
Omgevingsvariabelen
Zie Configuratie-implementatieinstellingen en de implementatiechecklist.