Salta ai contenuti

FastAPI

Filter this guide Pick generator option values to hide sections that don't apply.

FastAPI è un framework per la creazione di API in Python.

Il generatore FastAPI crea una nuova applicazione FastAPI con configurazione dell’infrastruttura AWS CDK o Terraform. Il backend generato utilizza AWS Lambda per il deployment serverless, esposto tramite un’API AWS API Gateway. Configura AWS Lambda Powertools per l’osservabilità, inclusi logging, tracciamento AWS X-Ray e metriche Cloudwatch.

Puoi generare una nuova FastAPI in due modi:

  1. Installa il Nx Console VSCode Plugin se non l'hai già fatto
  2. Apri la console Nx in VSCode
  3. Clicca su Generate (UI) nella sezione "Common Nx Commands"
  4. Cerca @aws/nx-plugin - py#api
  5. Compila i parametri richiesti
    • framework: fastapi
  6. Clicca su Generate
Parametro Tipo Predefinito Descrizione
name Obbligatorio string - Nome del progetto API da generare
framework fastapi fastapi Il framework API da utilizzare.
integrationPattern isolated | shared isolated Come vengono generate le integrazioni API Gateway per l'API. Scegli tra isolated (predefinito) e shared.
auth iam | cognito | custom iam Il metodo utilizzato per autenticare con la tua API. Scegli tra iam (predefinito), cognito o custom.
directory string packages La directory in cui memorizzare l'applicazione.
subDirectory string - La sottodirectory in cui viene posizionato il progetto. Per impostazione predefinita corrisponde al nome del progetto.
iac inherit | cdk | terraform inherit Il provider IaC preferito. Per impostazione predefinita viene ereditato dalla selezione iniziale.
moduleName string - Nome del modulo Python
infra rest-lambda | http-lambda | none rest-lambda Il tipo di infrastruttura da utilizzare per distribuire questa API.
preferInstallDependencies boolean true Se preferire l'installazione delle dipendenze dopo l'esecuzione del generatore. Impostare su false per rimandare l'installazione quando si eseguono più generatori in batch (l'installazione viene comunque eseguita se necessaria affinché i generatori successivi possano calcolare il grafo dei progetti Nx); installare una volta alla fine.

Il generatore creerà la seguente struttura del progetto nella directory <directory>/<api-name>:

  • project.json Configurazione del progetto e target di build
  • pyproject.toml Configurazione del progetto Python e dipendenze
  • run.sh Script di bootstrap Lambda Web Adapter per avviare l’app FastAPI tramite uvicorn
  • Directory<module_name>
    • __init__.py Inizializzazione del modulo
    • init.py Configura l’app FastAPI e il middleware powertools
    • main.py Implementazione dell’API
  • Directoryscripts
    • generate_open_api.py Script per generare uno schema OpenAPI dall’app FastAPI

Poiché questo generatore fornisce infrastruttura come codice basata sul tuo iacProvider selezionato, creerà un progetto in packages/common che include i relativi costrutti CDK o moduli Terraform.

Il progetto comune di infrastruttura come codice è strutturato come segue:

  • Directorypackages/common/constructs
    • Directorysrc
      • Directoryapp/ Construct per l’infrastruttura specifica di un progetto/generatore
      • Directorycore/ Construct generici riutilizzati dai construct in app
      • index.ts Punto di ingresso che esporta i construct da app
    • project.json Target di build e configurazione del progetto

Per la distribuzione della tua API, vengono generati i seguenti file:

  • Directorypackages/common/constructs/src
    • Directoryapp
      • Directoryapis
        • <project-name>.ts Costrutto CDK per distribuire la tua API
    • Directorycore
      • Directoryapi
        • http-api.ts Costrutto CDK per distribuire un’API HTTP (se hai scelto di distribuire un’API HTTP)
        • rest-api.ts Costrutto CDK per distribuire un’API REST (se hai scelto di distribuire un’API REST)
        • utils.ts Utilities per i costrutti API

L’applicazione distribuita ha la seguente architettura:

ClientWAFAPI Gateway(REST API)LambdaCloudWatch(Logs, Metrics)X-Ray(Traces)

Le REST API includono un Web ACL AWS WAFv2 davanti allo stage di API Gateway con il set di regole predefinito gestito da AWS abilitato.

L’implementazione principale dell’API si trova in main.py. Qui è dove definisci le route API e le relative implementazioni. Ecco un esempio:

from pydantic import BaseModel
from .init import app, tracer
class Item(BaseModel):
name: str
@app.get("/items/{item_id}")
@tracer.capture_method
def get_item(item_id: int) -> Item:
return Item(name=...)
@app.post("/items")
@tracer.capture_method
def create_item(item: Item):
return ...

Il generatore configura automaticamente diverse funzionalità:

  1. Integrazione con AWS Lambda Powertools per l’osservabilità
  2. Middleware per la gestione degli errori
  3. Correlazione richiesta/risposta
  4. Raccolta delle metriche
  5. Deployment AWS Lambda tramite Lambda Web Adapter con uvicorn
  6. Streaming type-safe (solo REST API)

Il generatore configura il logging strutturato utilizzando AWS Lambda Powertools. Puoi accedere al logger nei tuoi route handler:

from .init import app, logger
@app.get("/items/{item_id}")
def read_item(item_id: int):
logger.info("Fetching item", extra={"item_id": item_id})
return {"item_id": item_id}

Il logger include automaticamente:

  • ID di correlazione per il tracciamento delle richieste
  • Percorso e metodo della richiesta
  • Informazioni sul contesto Lambda
  • Indicatori di cold start

Il tracciamento AWS X-Ray è configurato automaticamente. Puoi aggiungere sottosegmenti personalizzati ai tuoi trace:

from .init import app, tracer
@app.get("/items/{item_id}")
@tracer.capture_method
def read_item(item_id: int):
# Crea un nuovo sottosegmento
with tracer.provider.in_subsegment("fetch-item-details"):
# La tua logica qui
return {"item_id": item_id}

Le metriche CloudWatch vengono raccolte automaticamente per ogni richiesta. Puoi aggiungere metriche personalizzate:

from .init import app, metrics
from aws_lambda_powertools.metrics import MetricUnit
@app.get("/items/{item_id}")
def read_item(item_id: int):
metrics.add_metric(name="ItemViewed", unit=MetricUnit.Count, value=1)
return {"item_id": item_id}

Le metriche predefinite includono:

  • Conteggio delle richieste
  • Conteggio successi/fallimenti
  • Metriche sui cold start
  • Metriche per route specifica

Il generatore include una gestione degli errori completa:

from fastapi import HTTPException
@app.get("/items/{item_id}")
def read_item(item_id: int):
if item_id < 0:
raise HTTPException(status_code=400, detail="Item ID must be positive")
return {"item_id": item_id}

Le eccezioni non gestite vengono intercettate dal middleware e:

  1. Registrano l’eccezione completa con stack trace
  2. Registrano una metrica di fallimento
  3. Restituiscono una risposta sicura 500 al client
  4. Mantengono l’ID di correlazione

Quando la tua API è protetta da autenticazione, i tuoi route handler spesso devono sapere chi sta chiamando. La FastAPI generata viene eseguita all’interno di AWS Lambda tramite il Lambda Web Adapter, che inoltra il contesto della richiesta API Gateway come JSON nell’header x-amzn-request-context. Puoi leggerlo dalla Request di FastAPI per estrarre l’identità del chiamante.

Come esempio, aggiungiamo un endpoint /me che restituisce dettagli sull’utente chiamante. Implementeremo l’estrazione come una dipendenza FastAPI in modo che possa essere riutilizzata tra le route. La forma del contesto della richiesta — e quindi come estrai l’identità — dipende sia dal metodo auth selezionato che dal fatto che tu abbia distribuito una REST API o HTTP API.

auth = iam

Per l’autenticazione IAM, cerchiamo il chiamante in Cognito utilizzando il sub estratto dal contesto della richiesta API Gateway. Crea identity.py accanto a main.py:

import json
import os
from typing import Annotated
from boto3 import client
from fastapi import Depends, HTTPException, Request
from pydantic import BaseModel
cognito = client("cognito-idp")
class Identity(BaseModel):
sub: str
username: str
def get_identity(request: Request) -> Identity:
# Il Lambda Web Adapter inoltra il contesto della richiesta API Gateway come JSON
request_context_header = request.headers.get("x-amzn-request-context")
if not request_context_header:
raise HTTPException(status_code=403, detail="Unable to determine calling user")
request_context = json.loads(request_context_header)
provider = request_context.get("identity", {}).get("cognitoAuthenticationProvider")
sub = provider.split(":")[-1] if provider else None
if not sub:
raise HTTPException(status_code=403, detail="Unable to determine calling user")
users = cognito.list_users(
# Assume che l'id del user pool sia configurato nell'ambiente lambda
UserPoolId=os.environ["USER_POOL_ID"],
Limit=1,
Filter=f'sub="{sub}"',
).get("Users", [])
if len(users) != 1:
raise HTTPException(status_code=403, detail=f"No user found with subjectId {sub}")
return Identity(sub=sub, username=users[0]["Username"])
CurrentUser = Annotated[Identity, Depends(get_identity)]
auth = cognito

Con auth: 'cognito', l’authorizer Cognito User Pools di API Gateway verifica il JWT che il chiamante fornisce nell’header Authorization e posiziona i claim verificati sul contesto della richiesta.

Crea identity.py accanto a main.py:

import json
from typing import Annotated
from fastapi import Depends, HTTPException, Request
from pydantic import BaseModel
class Identity(BaseModel):
sub: str
username: str
def get_identity(request: Request) -> Identity:
# Il Lambda Web Adapter inoltra il contesto della richiesta API Gateway come JSON
request_context_header = request.headers.get("x-amzn-request-context")
if not request_context_header:
raise HTTPException(status_code=403, detail="Unable to determine calling user")
request_context = json.loads(request_context_header)
claims = request_context.get("authorizer", {}).get("claims", {})
sub = claims.get("sub")
username = claims.get("username")
if not sub or not username:
raise HTTPException(status_code=403, detail="Unable to determine calling user")
return Identity(sub=sub, username=username)
CurrentUser = Annotated[Identity, Depends(get_identity)]

Puoi quindi iniettare la dipendenza CurrentUser in qualsiasi route che necessita dell’identità del chiamante:

from .identity import CurrentUser, Identity
from .init import app, tracer
@app.get("/me")
@tracer.capture_method
def me(identity: CurrentUser) -> Identity:
return identity
infra = rest-lambda

La FastAPI generata supporta risposte in streaming out of the box quando si utilizza una REST API. L’infrastruttura è configurata per utilizzare AWS Lambda Web Adapter per eseguire la tua FastAPI tramite uvicorn all’interno di Lambda, con ResponseTransferMode.STREAM in API Gateway per tutte le operazioni REST API, il che consente allo streaming di funzionare insieme alle operazioni non in streaming.

Il file init.py generato esporta una classe JsonStreamingResponse che fornisce streaming type-safe con una corretta generazione dello schema OpenAPI. Ciò garantisce che il generatore connection possa produrre metodi client di streaming correttamente tipizzati.

from pydantic import BaseModel
from .init import app, JsonStreamingResponse
class Chunk(BaseModel):
message: str
async def generate_chunks():
for i in range(100):
yield Chunk(message=f"This is chunk {i}")
@app.post(
"/stream",
response_class=JsonStreamingResponse,
responses={200: JsonStreamingResponse.openapi_response(Chunk, "Stream of chunks")},
)
async def my_stream() -> JsonStreamingResponse:
return JsonStreamingResponse(generate_chunks())

La classe JsonStreamingResponse:

  1. Serializza i modelli Pydantic nel formato JSON Lines (application/jsonl)
  2. Fornisce un helper openapi_response che genera lo schema OpenAPI corretto con itemSchema, consentendo al generatore connection di produrre metodi client di streaming type-safe

Per consumare uno stream di risposte, puoi utilizzare il generatore connection che fornirà un metodo type-safe per iterare sui chunk dello stream.

Il generatore FastAPI crea infrastruttura come codice CDK o Terraform in base al iacProvider selezionato. Puoi utilizzarlo per distribuire la tua FastAPI.

Il costrutto CDK per distribuire la tua API si trova nella cartella common/constructs. Puoi usarlo in un’applicazione CDK:

import { MyApi } from ':my-scope/common-constructs';
export class ExampleStack extends Stack {
constructor(scope: Construct, id: string) {
// Aggiungi l'API allo stack
const api = new MyApi(this, 'MyApi', {
integrations: MyApi.defaultIntegrations(this).build(),
});
}
}

Questo configura:

  1. Una funzione AWS Lambda per ogni operazione nell’applicazione FastAPI
  2. API Gateway HTTP/REST API come trigger della funzione
  3. Ruoli e permessi IAM
  4. Log group CloudWatch
  5. Configurazione del tracciamento X-Ray
  6. Namespace per le metriche CloudWatch
auth = cognito
auth = custom
infra = rest-lambda

Per le API REST, il costrutto generato associa di default una Web ACL AWS WAFv2 allo stage di API Gateway. La Web ACL utilizza il ruleset predefinito gestito da AWS (AWSManagedRulesCommonRuleSet e AWSManagedRulesKnownBadInputsRuleSet), fornendo protezione contro exploit web comuni inclusa la OWASP Top 10. I log delle richieste WAF vengono scritti in un gruppo CloudWatch Logs.

Puoi modificare il costrutto rest-api generato per aggiungere, rimuovere o regolare le regole (ad esempio, per aggiungere regole basate sulla frequenza o gruppi di regole gestite aggiuntivi).

Per disattivare (ad esempio, per collegare la tua Web ACL), imposta enableWaf su false:

const api = new MyApi(this, 'MyApi', {
integrations: MyApi.defaultIntegrations(this).build(),
enableWaf: false,
});
infra = rest-lambda

Per le REST API, l’infrastruttura generata abilita la registrazione degli accessi per impostazione predefinita, scrivendo una riga JSON strutturata per richiesta in un gruppo CloudWatch Logs dedicato. Il gruppo di log è crittografato con una chiave KMS gestita dal cliente e conservato per un anno.

API Gateway scrive i log di accesso utilizzando un ruolo CloudWatch Logs a livello di account. Questo ruolo è configurato nell’impostazione AWS::ApiGateway::Account, che è un singleton per regione per account — esiste un solo ruolo per ogni REST API nella regione. Per gestire questo in modo sicuro tra più stack distribuiti indipendentemente, l’infrastruttura generata:

  • Crea un ruolo CloudWatch Logs condiviso e lo configura sull’account solo quando non è già impostato un ruolo funzionante, in modo che le distribuzioni non sovrascrivano mai un ruolo di proprietà di un altro stack.
  • Lascia l’impostazione dell’account intatta durante la rimozione, in modo che la distruzione di uno stack non disabiliti mai la registrazione per altre REST API nella regione.

Il ruolo dell’account è gestito dal costrutto ApiGatewayAccount, un singleton con ambito stack risolto tramite ApiGatewayAccount.ensure(scope). Ogni stage della REST API dipende da esso e il ruolo è configurato da una risorsa personalizzata supportata da Lambda.

Puoi personalizzare il formato del log di accesso passando deployOptions durante la costruzione della tua API:

const api = new MyApi(this, 'MyApi', {
integrations: MyApi.defaultIntegrations(this).build(),
deployOptions: {
accessLogFormat: AccessLogFormat.clf(),
},
});

I costrutti CDK per le API REST/HTTP sono configurati per fornire un’interfaccia type-safe per definire le integrazioni per ciascuna delle tue operazioni.

I costrutti CDK forniscono supporto completo per l’integrazione type-safe come descritto di seguito.

Puoi utilizzare il metodo statico defaultIntegrations per sfruttare il pattern predefinito, che definisce una singola funzione AWS Lambda per ogni operazione:

new MyApi(this, 'MyApi', {
integrations: MyApi.defaultIntegrations(this).build(),
});

Puoi accedere alle funzioni AWS Lambda sottostanti tramite la proprietà integrations del costrutto API in modo type-safe. Ad esempio, se la tua API definisce un’operazione chiamata sayHello e devi aggiungere dei permessi a questa funzione, puoi farlo come segue:

const api = new MyApi(this, 'MyApi', {
integrations: MyApi.defaultIntegrations(this).build(),
});
// sayHello è tipizzato in base alle operazioni definite nella tua API
api.integrations.sayHello.handler.addToRolePolicy(new PolicyStatement({
effect: Effect.ALLOW,
actions: [...],
resources: [...],
}));

Se la tua API utilizza il pattern shared, il router Lambda condiviso è esposto come api.integrations.$router:

const api = new MyApi(this, 'MyApi', {
integrations: MyApi.defaultIntegrations(this).build(),
});
api.integrations.$router.handler.addEnvironment('LOG_LEVEL', 'DEBUG');

Se desideri personalizzare le opzioni utilizzate durante la creazione delle funzioni Lambda per ogni integrazione predefinita, puoi usare il metodo withDefaultOptions. Ad esempio, per far risiedere tutte le funzioni Lambda in una VPC:

const vpc = new Vpc(this, 'Vpc', ...);
new MyApi(this, 'MyApi', {
integrations: MyApi.defaultIntegrations(this)
.withDefaultOptions({
vpc,
})
.build(),
});

Per personalizzare le opzioni utilizzate per creare l’integrazione predefinita per operazioni specifiche (senza influenzare le altre), puoi usare il metodo withOperationOptions. Ad esempio, se desideri aumentare il timeout della funzione Lambda per una sola operazione:

const api = new MyApi(this, 'MyApi', {
integrations: MyApi.defaultIntegrations(this)
.withOperationOptions({
sayHello: {
timeout: Duration.seconds(60),
},
})
.build(),
});
// Le operazioni selezionate rimangono integrazioni predefinite, quindi sono ancora tipizzate di conseguenza:
api.integrations.sayHello.handler.addToRolePolicy(new PolicyStatement({ ... }));

Le opzioni che specifichi vengono unite alle opzioni di integrazione predefinite (e a qualsiasi opzione impostata tramite withDefaultOptions). Nota che non puoi specificare opzioni per operazioni che hai sostituito tramite withOverrides, poiché queste non utilizzano più l’integrazione predefinita.

Incontrerai un errore di tipo se la stessa operazione è targetizzata sia da withOperationOptions che da withOverrides, indipendentemente dall’ordine in cui li chiami.

Puoi anche sovrascrivere le integrazioni per operazioni specifiche usando il metodo withOverrides. Ogni override deve specificare una proprietà integration tipizzata correttamente per il costrutto di integrazione CDK appropriato per l’API HTTP o REST. Il metodo withOverrides è anch’esso type-safe. Ad esempio, se desideri sovrascrivere un’API getDocumentation per puntare alla documentazione ospitata da un sito web esterno, puoi farlo come segue:

new MyApi(this, 'MyApi', {
integrations: MyApi.defaultIntegrations(this)
.withOverrides({
getDocumentation: {
integration: new HttpIntegration('https://example.com/documentation'),
},
})
.build(),
});

Noterai anche che l’integrazione sovrascritta non ha più una proprietà handler quando vi si accede tramite api.integrations.getDocumentation.

Puoi aggiungere proprietà aggiuntive a un’integrazione che saranno anche tipizzate di conseguenza, consentendo ad altri tipi di integrazione di essere astratti ma rimanere type-safe, ad esempio se hai creato un’integrazione S3 per un’API REST e successivamente desideri fare riferimento al bucket per una particolare operazione, puoi farlo come segue:

const storageBucket = new Bucket(this, 'Bucket', { ... });
const apiGatewayRole = new Role(this, 'ApiGatewayS3Role', {
assumedBy: new ServicePrincipal('apigateway.amazonaws.com'),
});
storageBucket.grantRead(apiGatewayRole);
const api = new MyApi(this, 'MyApi', {
integrations: MyApi.defaultIntegrations(this)
.withOverrides({
getFile: {
bucket: storageBucket,
integration: new AwsIntegration({
service: 's3',
integrationHttpMethod: 'GET',
path: `${storageBucket.bucketName}/{fileName}`,
options: {
credentialsRole: apiGatewayRole,
requestParameters: {
'integration.request.path.fileName': 'method.request.querystring.fileName',
},
integrationResponses: [{ statusCode: '200' }],
},
}),
options: {
requestParameters: {
'method.request.querystring.fileName': true,
},
methodResponses: [{
statusCode: '200',
}],
}
},
})
.build(),
});
// Successivamente, magari in un altro file, puoi accedere alla proprietà bucket che abbiamo definito
// in modo type-safe
api.integrations.getFile.bucket.grantRead(...);

Puoi anche fornire options nella tua integrazione per sovrascrivere particolari opzioni del metodo come gli authorizer, ad esempio se desideri utilizzare l’autenticazione Cognito per la tua operazione getDocumentation:

new MyApi(this, 'MyApi', {
integrations: MyApi.defaultIntegrations(this)
.withOverrides({
getDocumentation: {
integration: new HttpIntegration('https://example.com/documentation'),
options: {
authorizer: new CognitoUserPoolsAuthorizer(...) // per REST, o HttpUserPoolAuthorizer per un'API HTTP
}
},
})
.build(),
});

Se preferisci, puoi scegliere di non utilizzare le integrazioni predefinite e invece fornirne direttamente una per ogni operazione. Questo è utile se, ad esempio, ogni operazione deve utilizzare un tipo diverso di integrazione o desideri ricevere un errore di tipo quando aggiungi nuove operazioni:

new MyApi(this, 'MyApi', {
integrations: {
sayHello: {
integration: new LambdaIntegration(...),
},
getDocumentation: {
integration: new HttpIntegration(...),
},
},
});

I costrutti API CDK generati supportano due pattern di integrazione:

  • isolated crea una funzione Lambda per operazione. Questo è il pattern predefinito per le API generate.
  • shared crea un singolo router Lambda predefinito e lo riutilizza per ogni operazione a meno che non si sovrascrivano integrazioni specifiche.

isolated offre permessi e configurazione più granulari per operazione. shared riduce la proliferazione di Lambda e integrazioni API Gateway pur consentendo override selettivi.

Ad esempio, impostando pattern su 'shared' si crea una singola funzione invece di una per integrazione:

packages/common/constructs/src/app/apis/my-api.ts
export class MyApi<...> extends ... {
public static defaultIntegrations = (scope: Construct) => {
...
return IntegrationBuilder.rest({
pattern: 'shared',
...
});
};
}

Poiché le operazioni in FastAPI sono definite in Python e l’infrastruttura CDK in TypeScript, strumentiamo la generazione del codice per fornire metadati al costrutto CDK e garantire un’interfaccia type-safe per le integrazioni.

Un target generate:<ApiName>-metadata viene aggiunto al project.json dei costrutti comuni per facilitare questa generazione, producendo un file come packages/common/constructs/src/generated/my-api/metadata.gen.ts. Essendo generato al momento della build, viene ignorato nel version control.

auth = iam

Se hai selezionato l’autenticazione IAM, puoi usare il metodo grantInvokeAccess per concedere l’accesso alla tua API:

api.grantInvokeAccess(myIdentityPool.authenticatedRole);

Il generatore configura un server di sviluppo locale che puoi eseguire con:

Terminal window
pnpm nx serve my-api

Questo avvia un server di sviluppo FastAPI locale con:

  • Auto-reload alle modifiche del codice
  • Documentazione API interattiva su /docs o /redoc
  • Schema OpenAPI su /openapi.json

Per invocare la tua API da un sito React, puoi utilizzare il generatore connection.

Usa il generatore connection per integrare questo progetto con altri nel tuo workspace. Le seguenti connessioni coinvolgono questo progetto:

FastAPI
React to FastAPI Chiama un'API Python FastAPI da un sito React
FastAPI Amazon DynamoDB Python
FastAPI to Python DynamoDB Collega un FastAPI a una tabella DynamoDB