Modelo do encurtador em FastAPI

Gerado a partir de deploy-templates/fastapi (dado.cloud 57aa2a2).
This commit is contained in:
dado.cloud
2026-10-21 12:43:22 +00:00
parent 239d0dee0a
commit 179c4eed10
+49 -13
View File
@@ -4,6 +4,7 @@ Rodar local: uvicorn main:app --reload (ou: python main.py)
""" """
import html import html
import json
import os import os
import platform import platform
import time import time
@@ -12,9 +13,7 @@ from pathlib import Path
import fastapi import fastapi
from fastapi import FastAPI, Request from fastapi import FastAPI, Request
from fastapi.exceptions import RequestValidationError
from fastapi.responses import HTMLResponse, JSONResponse, RedirectResponse, Response from fastapi.responses import HTMLResponse, JSONResponse, RedirectResponse, Response
from pydantic import BaseModel
from links import FORMATO_CODIGO, ErroDeValidacao, criar_link, listar_links, registrar_clique from links import FORMATO_CODIGO, ErroDeValidacao, criar_link, listar_links, registrar_clique
@@ -95,16 +94,33 @@ def para_json(link: dict, request: Request) -> dict:
} }
class NovoLink(BaseModel): CORPO_INVALIDO = 'Corpo inválido. Envie JSON no formato {"url": "https://..."}.'
url: str | None = None # Um pedido de encurtar link cabe com folga em 10 KB; mais que isso é recusado
# sem ler o resto (o app é público: ninguém deve conseguir mandar megabytes).
TAMANHO_MAXIMO_CORPO = 10 * 1024
@app.exception_handler(RequestValidationError) class CorpoInvalido(Exception):
async def corpo_invalido(request: Request, erro: RequestValidationError): pass
return JSONResponse(
status_code=400,
content={"erro": 'Corpo inválido. Envie JSON no formato {"url": "https://..."}.'}, async def ler_json_limitado(request: Request) -> dict:
) """Lê o corpo em pedaços e para assim que passar do limite (não confia só no Content-Length)."""
declarado = request.headers.get("content-length")
if declarado is not None and (not declarado.isdigit() or int(declarado) > TAMANHO_MAXIMO_CORPO):
raise CorpoInvalido()
corpo = bytearray()
async for pedaco in request.stream():
corpo += pedaco
if len(corpo) > TAMANHO_MAXIMO_CORPO:
raise CorpoInvalido()
try:
dados = json.loads(corpo)
except ValueError:
raise CorpoInvalido() from None
if not isinstance(dados, dict):
raise CorpoInvalido()
return dados
@app.get("/", response_class=HTMLResponse, include_in_schema=False) @app.get("/", response_class=HTMLResponse, include_in_schema=False)
@@ -120,8 +136,24 @@ def listar(request: Request):
) )
@app.post("/api/links", status_code=201, summary="Encurta um link") @app.post(
def criar(novo: NovoLink, request: Request): "/api/links",
status_code=201,
summary="Encurta um link",
# O corpo é lido à mão (com limite de tamanho); isto só descreve o formato no /docs.
openapi_extra={
"requestBody": {
"required": True,
"content": {
"application/json": {
"schema": {"type": "object", "properties": {"url": {"type": "string"}}, "required": ["url"]},
"example": {"url": "https://exemplo.com.br/uma-pagina"},
}
},
}
},
)
async def criar(request: Request):
ip = request.headers.get("cf-connecting-ip") or (request.client.host if request.client else "?") ip = request.headers.get("cf-connecting-ip") or (request.client.host if request.client else "?")
if passou_do_limite(ip): if passou_do_limite(ip):
return JSONResponse( return JSONResponse(
@@ -129,7 +161,11 @@ def criar(novo: NovoLink, request: Request):
content={"erro": "Muitos links em pouco tempo. Espere alguns minutos e tente de novo."}, content={"erro": "Muitos links em pouco tempo. Espere alguns minutos e tente de novo."},
) )
try: try:
link = criar_link(novo.url) dados = await ler_json_limitado(request)
except CorpoInvalido:
return JSONResponse(status_code=400, content={"erro": CORPO_INVALIDO})
try:
link = criar_link(dados.get("url"))
except ErroDeValidacao as erro: except ErroDeValidacao as erro:
return JSONResponse(status_code=400, content={"erro": str(erro)}) return JSONResponse(status_code=400, content={"erro": str(erro)})
return JSONResponse(status_code=201, content=para_json(link, request)) return JSONResponse(status_code=201, content=para_json(link, request))