Salta ai contenuti

FastAPI

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 con 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#fast-api
  5. Compila i parametri richiesti
    • Clicca su Generate
    Parametro Tipo Predefinito Descrizione
    name Obbligatorio string - Name of the API project to generate
    computeType string ServerlessApiGatewayRestApi The type of compute to use to deploy this API. Choose between ServerlessApiGatewayRestApi (default) or ServerlessApiGatewayHttpApi.
    auth string IAM The method used to authenticate with your API. Choose between IAM (default), Cognito or None.
    directory string packages The directory to store the application in.
    iacProvider string CDK The preferred IaC provider
    moduleName string - Python module name

    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
    • 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 lo 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’implementazione principale dell’API si trova in main.py. Qui definisci le route API e le relative implementazioni. Ecco un esempio:

    from .init import app, tracer
    from pydantic import BaseModel
    class Item(BaseModel):
    name: str
    @app.get("/items/{item_id}")
    def get_item(item_id: int) -> Item:
    return Item(name=...)
    @app.post("/items")
    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 tra richieste e risposte
    4. Raccolta delle metriche
    5. Handler AWS Lambda utilizzando Mangum

    Il generatore configura il logging strutturato usando 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 con AWS X-Ray è configurato automaticamente. Puoi aggiungere subsegment 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 subsegment
    with tracer.provider.in_subsegment("fetch-item-details"):
    # 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 richieste
    • Conteggio successi/fallimenti
    • Metriche di cold start
    • Metriche per route specifiche

    Il generatore include una gestione completa degli errori:

    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 catturate 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

    Con FastAPI, puoi inviare una risposta in streaming al chiamante usando il tipo di risposta StreamingResponse.

    Poiché AWS API Gateway non supporta risposte in streaming, dovrai distribuire la tua FastAPI su una piattaforma che supporti questa funzionalità. L’opzione più semplice è utilizzare un AWS Lambda Function URL.

    Per ottenere questo, puoi sostituire il costrutto generato common/constructs/src/app/apis/<name>-api.ts con uno che distribuisce una Function URL.

    Esempio di Costrutto FunctionURL per Streaming
    import { Duration, Stack, CfnOutput } from 'aws-cdk-lib';
    import { IGrantable, Grant } from 'aws-cdk-lib/aws-iam';
    import {
    Runtime,
    Code,
    Tracing,
    LayerVersion,
    FunctionUrlAuthType,
    InvokeMode,
    Function,
    } from 'aws-cdk-lib/aws-lambda';
    import { Construct } from 'constructs';
    import url from 'url';
    import { RuntimeConfig } from '../../core/runtime-config.js';
    export class MyApi extends Construct {
    public readonly handler: Function;
    constructor(scope: Construct, id: string) {
    super(scope, id);
    this.handler = new Function(this, 'Handler', {
    runtime: Runtime.PYTHON_3_12,
    handler: 'run.sh',
    code: Code.fromAsset(
    url.fileURLToPath(
    new URL(
    '../../../../../../dist/packages/my_api/bundle',
    import.meta.url,
    ),
    ),
    ),
    timeout: Duration.seconds(30),
    tracing: Tracing.ACTIVE,
    environment: {
    AWS_CONNECTION_REUSE_ENABLED: '1',
    },
    });
    const stack = Stack.of(this);
    this.handler.addLayers(
    LayerVersion.fromLayerVersionArn(
    this,
    'LWALayer',
    `arn:aws:lambda:${stack.region}:753240598075:layer:LambdaAdapterLayerX86:24`,
    ),
    );
    this.handler.addEnvironment('PORT', '8000');
    this.handler.addEnvironment('AWS_LWA_INVOKE_MODE', 'response_stream');
    this.handler.addEnvironment('AWS_LAMBDA_EXEC_WRAPPER', '/opt/bootstrap');
    const functionUrl = this.handler.addFunctionUrl({
    authType: FunctionUrlAuthType.AWS_IAM,
    invokeMode: InvokeMode.RESPONSE_STREAM,
    cors: {
    allowedOrigins: ['*'],
    allowedHeaders: [
    'authorization',
    'content-type',
    'x-amz-content-sha256',
    'x-amz-date',
    'x-amz-security-token',
    ],
    },
    });
    new CfnOutput(this, 'MyApiUrl', { value: functionUrl.url });
    // Registra l'URL API nella configurazione runtime per il client discovery
    RuntimeConfig.ensure(this).config.apis = {
    ...RuntimeConfig.ensure(this).config.apis!,
    MyApi: functionUrl.url,
    };
    }
    public grantInvokeAccess(grantee: IGrantable) {
    Grant.addToPrincipal({
    grantee,
    actions: ['lambda:InvokeFunctionUrl'],
    resourceArns: [this.handler.functionArn],
    conditions: {
    StringEquals: {
    'lambda:FunctionUrlAuthType': 'AWS_IAM',
    },
    },
    });
    }
    }

    Dopo aver aggiornato l’infrastruttura per supportare lo streaming, puoi implementare un’API in streaming con FastAPI. L’API dovrebbe:

    • Restituire un StreamingResponse
    • Dichiarare il tipo di ritorno per ogni chunk della risposta
    • Aggiungere l’estensione vendor OpenAPI x-streaming: true se intendi utilizzare il API Connection.

    Ad esempio, se vuoi inviare in streaming una serie di oggetti JSON dalla tua API, puoi implementarlo così:

    from pydantic import BaseModel
    from fastapi.responses import StreamingResponse
    class Chunk(BaseModel):
    message: str
    timestamp: datetime
    async def stream_chunks():
    for i in range(0, 100):
    yield Chunk(message=f"This is chunk {i}", timestamp=datetime.now())
    @app.get("/stream", openapi_extra={'x-streaming': True})
    def my_stream() -> Chunk:
    return StreamingResponse(stream_chunks(), media_type="application/json")

    Per consumare uno stream di risposte, puoi utilizzare il Generatore API Connection che fornirà un metodo type-safe per iterare sui chunk in streaming.

    Il generatore FastAPI crea codice infrastrutturale CDK o Terraform in base al iacProvider selezionato. Puoi usarlo per distribuire la tua FastAPI.

    Il costrutto CDK per distribuire l’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. CloudWatch log group
    5. Configurazione del tracciamento X-Ray
    6. Namespace per le metriche CloudWatch

    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 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(),
    });

    Puoi sovrascrivere le integrazioni per operazioni specifiche usando il metodo withOverrides. Ogni override deve specificare una proprietà integration tipizzata correttamente. Esempio per reindirizzare un’API getDocumentation:

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

    Le integrazioni sovrascritte non avranno più la proprietà handler quando accedute tramite api.integrations.getDocumentation.

    Puoi aggiungere proprietà personalizzate alle integrazioni mantenendo il type-safety. Esempio con integrazione S3:

    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(),
    });
    // Accesso type-safe alla proprietà bucket definita
    api.integrations.getFile.bucket.grantRead(...);

    Puoi sovrascrivere gli authorizer specificando options nelle integrazioni. Esempio con autenticazione Cognito:

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

    Puoi definire manualmente ogni integrazione. Utile per usare tipi diversi per ogni operazione:

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

    Per usare una singola Lambda per tutte le richieste, modifica defaultIntegrations:

    export class MyApi<...> extends ... {
    public static defaultIntegrations = (scope: Construct) => {
    const router = new Function(scope, 'RouterHandler', { ... });
    return IntegrationBuilder.rest({
    buildDefaultIntegration: (op) => ({
    integration: new LambdaIntegration(router),
    }),
    });
    };
    }

    Poiché le operazioni in FastAPI sono definite in Python e l’infrastruttura CDK in TypeScript, utilizziamo la generazione di codice per fornire metadati al costrutto CDK e creare 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 dal version control.

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

    api.grantInvokeAccess(myIdentityPool.authenticatedRole);

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

    Terminal window
    pnpm nx run my-api:serve

    Questo avvia un server di sviluppo FastAPI locale con:

    • Ricarica automatica 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 usare il generatore api-connection.