Se você utilizar um bloco try em uma dependência com yield, você irá capturar qualquer exceção que for lançada enquanto a dependência é utilizada.
Por exemplo, se algum código em um certo momento no meio da operação, em outra dependência ou em uma operação de rota, fizer um "rollback" de uma transação de banco de dados ou causar qualquer outro erro, você irá capturar a exceção em sua dependência.
Então, você pode procurar por essa exceção específica dentro da dependência com except AlgumaExcecao.
Da mesma forma, você pode utilizar finally para garantir que os passos de saída são executados, com ou sem exceções.
Você viu que dependências podem ser utilizadas com yield e podem incluir blocos try para capturar exceções.
Da mesma forma, você pode lançar uma httpexception ou algo parecido no código de saída, após o yield
Dica
Essa é uma técnica relativamente avançada, e na maioria dos casos você não precisa dela totalmente, já que você pode lançar exceções (incluindo httpexception) dentro do resto do código da sua aplicação, por exemplo, em uma função de operação de rota.
Mas ela existe para ser utilizada caso você precise. 🤓
fromtypingimportAnnotatedfromfastapiimportDepends,FastAPI,HTTPExceptionapp=FastAPI()data={"plumbus":{"description":"Freshly pickled plumbus","owner":"Morty"},"portal-gun":{"description":"Gun to create portals","owner":"Rick"},}classOwnerError(Exception):passdefget_username():try:yield"Rick"exceptOwnerErrorase:raiseHTTPException(status_code=400,detail=f"Owner error: {e}")@app.get("/items/{item_id}")defget_item(item_id:str,username:Annotated[str,Depends(get_username)]):ifitem_idnotindata:raiseHTTPException(status_code=404,detail="Item not found")item=data[item_id]ifitem["owner"]!=username:raiseOwnerError(username)returnitem
fromfastapiimportDepends,FastAPI,HTTPExceptionfromtyping_extensionsimportAnnotatedapp=FastAPI()data={"plumbus":{"description":"Freshly pickled plumbus","owner":"Morty"},"portal-gun":{"description":"Gun to create portals","owner":"Rick"},}classOwnerError(Exception):passdefget_username():try:yield"Rick"exceptOwnerErrorase:raiseHTTPException(status_code=400,detail=f"Owner error: {e}")@app.get("/items/{item_id}")defget_item(item_id:str,username:Annotated[str,Depends(get_username)]):ifitem_idnotindata:raiseHTTPException(status_code=404,detail="Item not found")item=data[item_id]ifitem["owner"]!=username:raiseOwnerError(username)returnitem
Dica
Utilize a versão com Annotated se possível.
fromfastapiimportDepends,FastAPI,HTTPExceptionapp=FastAPI()data={"plumbus":{"description":"Freshly pickled plumbus","owner":"Morty"},"portal-gun":{"description":"Gun to create portals","owner":"Rick"},}classOwnerError(Exception):passdefget_username():try:yield"Rick"exceptOwnerErrorase:raiseHTTPException(status_code=400,detail=f"Owner error: {e}")@app.get("/items/{item_id}")defget_item(item_id:str,username:str=Depends(get_username)):ifitem_idnotindata:raiseHTTPException(status_code=404,detail="Item not found")item=data[item_id]ifitem["owner"]!=username:raiseOwnerError(username)returnitem
Uma alternativa que você pode utilizar para capturar exceções (e possivelmente lançar outra HTTPException) é criar um Manipulador de Exceções Customizado.
Se você capturar uma exceção com except em uma dependência que utilize yield e ela não for levantada novamente (ou uma nova exceção for levantada), o FastAPI não será capaz de identifcar que houve uma exceção, da mesma forma que aconteceria com Python puro:
fromtypingimportAnnotatedfromfastapiimportDepends,FastAPI,HTTPExceptionapp=FastAPI()classInternalError(Exception):passdefget_username():try:yield"Rick"exceptInternalError:print("Oops, we didn't raise again, Britney 😱")@app.get("/items/{item_id}")defget_item(item_id:str,username:Annotated[str,Depends(get_username)]):ifitem_id=="portal-gun":raiseInternalError(f"The portal gun is too dangerous to be owned by {username}")ifitem_id!="plumbus":raiseHTTPException(status_code=404,detail="Item not found, there's only a plumbus here")returnitem_id
🤓 Other versions and variants
fromfastapiimportDepends,FastAPI,HTTPExceptionfromtyping_extensionsimportAnnotatedapp=FastAPI()classInternalError(Exception):passdefget_username():try:yield"Rick"exceptInternalError:print("Oops, we didn't raise again, Britney 😱")@app.get("/items/{item_id}")defget_item(item_id:str,username:Annotated[str,Depends(get_username)]):ifitem_id=="portal-gun":raiseInternalError(f"The portal gun is too dangerous to be owned by {username}")ifitem_id!="plumbus":raiseHTTPException(status_code=404,detail="Item not found, there's only a plumbus here")returnitem_id
Tip
Prefer to use the Annotated version if possible.
fromfastapiimportDepends,FastAPI,HTTPExceptionapp=FastAPI()classInternalError(Exception):passdefget_username():try:yield"Rick"exceptInternalError:print("Oops, we didn't raise again, Britney 😱")@app.get("/items/{item_id}")defget_item(item_id:str,username:str=Depends(get_username)):ifitem_id=="portal-gun":raiseInternalError(f"The portal gun is too dangerous to be owned by {username}")ifitem_id!="plumbus":raiseHTTPException(status_code=404,detail="Item not found, there's only a plumbus here")returnitem_id
Neste caso, o cliente irá ver uma resposta HTTP 500 Internal Server Error como deveria acontecer, já que não estamos levantando nenhuma HTTPException ou coisa parecida, mas o servidor não terá nenhum log ou qualquer outra indicação de qual foi o erro. 😱
Sempre levante (raise) exceções em Dependências com yield e except¶
Se você capturar uma exceção em uma dependência com yield, a menos que você esteja levantando outra HTTPException ou coisa parecida, você deveria relançar a exceção original.
Você pode relançar a mesma exceção utilizando raise:
fromtypingimportAnnotatedfromfastapiimportDepends,FastAPI,HTTPExceptionapp=FastAPI()classInternalError(Exception):passdefget_username():try:yield"Rick"exceptInternalError:print("We don't swallow the internal error here, we raise again 😎")raise@app.get("/items/{item_id}")defget_item(item_id:str,username:Annotated[str,Depends(get_username)]):ifitem_id=="portal-gun":raiseInternalError(f"The portal gun is too dangerous to be owned by {username}")ifitem_id!="plumbus":raiseHTTPException(status_code=404,detail="Item not found, there's only a plumbus here")returnitem_id
🤓 Other versions and variants
fromfastapiimportDepends,FastAPI,HTTPExceptionfromtyping_extensionsimportAnnotatedapp=FastAPI()classInternalError(Exception):passdefget_username():try:yield"Rick"exceptInternalError:print("We don't swallow the internal error here, we raise again 😎")raise@app.get("/items/{item_id}")defget_item(item_id:str,username:Annotated[str,Depends(get_username)]):ifitem_id=="portal-gun":raiseInternalError(f"The portal gun is too dangerous to be owned by {username}")ifitem_id!="plumbus":raiseHTTPException(status_code=404,detail="Item not found, there's only a plumbus here")returnitem_id
Tip
Prefer to use the Annotated version if possible.
fromfastapiimportDepends,FastAPI,HTTPExceptionapp=FastAPI()classInternalError(Exception):passdefget_username():try:yield"Rick"exceptInternalError:print("We don't swallow the internal error here, we raise again 😎")raise@app.get("/items/{item_id}")defget_item(item_id:str,username:str=Depends(get_username)):ifitem_id=="portal-gun":raiseInternalError(f"The portal gun is too dangerous to be owned by {username}")ifitem_id!="plumbus":raiseHTTPException(status_code=404,detail="Item not found, there's only a plumbus here")returnitem_id
Dica
Utilize a versão com Annotated se possível.
fromfastapiimportDepends,FastAPI,HTTPExceptionapp=FastAPI()classInternalError(Exception):passdefget_username():try:yield"Rick"exceptInternalError:print("We don't swallow the internal error here, we raise again 😎")raise@app.get("/items/{item_id}")defget_item(item_id:str,username:str=Depends(get_username)):ifitem_id=="portal-gun":raiseInternalError(f"The portal gun is too dangerous to be owned by {username}")ifitem_id!="plumbus":raiseHTTPException(status_code=404,detail="Item not found, there's only a plumbus here")returnitem_id
🤓 Other versions and variants
fromtypingimportAnnotatedfromfastapiimportDepends,FastAPI,HTTPExceptionapp=FastAPI()classInternalError(Exception):passdefget_username():try:yield"Rick"exceptInternalError:print("We don't swallow the internal error here, we raise again 😎")raise@app.get("/items/{item_id}")defget_item(item_id:str,username:Annotated[str,Depends(get_username)]):ifitem_id=="portal-gun":raiseInternalError(f"The portal gun is too dangerous to be owned by {username}")ifitem_id!="plumbus":raiseHTTPException(status_code=404,detail="Item not found, there's only a plumbus here")returnitem_id
fromfastapiimportDepends,FastAPI,HTTPExceptionfromtyping_extensionsimportAnnotatedapp=FastAPI()classInternalError(Exception):passdefget_username():try:yield"Rick"exceptInternalError:print("We don't swallow the internal error here, we raise again 😎")raise@app.get("/items/{item_id}")defget_item(item_id:str,username:Annotated[str,Depends(get_username)]):ifitem_id=="portal-gun":raiseInternalError(f"The portal gun is too dangerous to be owned by {username}")ifitem_id!="plumbus":raiseHTTPException(status_code=404,detail="Item not found, there's only a plumbus here")returnitem_id
Agora o cliente irá receber a mesma resposta HTTP 500 Internal Server Error, mas o servidor terá nosso InternalError personalizado nos logs. 😎
A sequência de execução é mais ou menos como esse diagrama. O tempo passa do topo para baixo. E cada coluna é uma das partes interagindo ou executando código.
Informação
Apenas uma resposta será enviada para o cliente. Ela pode ser uma das respostas de erro, ou então a resposta da operação de rota.
Após uma dessas respostas ser enviada, nenhuma outra resposta pode ser enviada
Dica
Esse diagrama mostra HttpException, mas você pode levantar qualquer outra exceção que você capture em uma dependência com yield ou um Manipulador de exceções personalizado.
Se você lançar qualquer exceção, ela será passada para as dependências com yield, inlcuindo a HTTPException. Na maioria dos casos você vai querer relançar essa mesma exceção ou uma nova a partir da dependência com yield para garantir que ela seja tratada adequadamente.
Dependências com yield, HTTPException, except e Tarefas de Background¶
Aviso
Você provavelmente não precisa desses detalhes técnicos, você pode pular essa seção e continuar na próxima seção abaixo.
Esses detalhes são úteis principalmente se você estiver usando uma versão do FastAPI anterior à 0.106.0 e utilizando recursos de dependências com yield em tarefas de background.
Dependências com yield e except, Detalhes Técnicos¶
Antes do FastAPI 0.110.0, se você utilizasse uma dependência com yield, e então capturasse uma dependência com except nessa dependência, caso a exceção não fosse relançada, ela era automaticamente lançada para qualquer manipulador de exceções ou o manipulador de erros interno do servidor.
Isso foi modificado na versão 0.110.0 para consertar o consumo de memória não controlado das exceções relançadas automaticamente sem um manipulador (erros internos do servidor), e para manter o comportamento consistente com o código Python tradicional.
Tarefas de Background e Dependências com yield, Detalhes Técnicos¶
Antes do FastAPI 0.106.0, levantar exceções após um yield não era possível, o código de saída nas dependências com yield era executado após a resposta ser enviada, então os Manipuladores de Exceções já teriam executado.
Isso foi implementado dessa forma principalmente para permitir que os mesmos objetos fornecidos ("yielded") pelas dependências dentro de tarefas de background fossem reutilizados, por que o código de saída era executado antes das tarefas de background serem finalizadas.
Ainda assim, como isso exigiria esperar que a resposta navegasse pela rede enquanto mantia ativo um recurso desnecessário na dependência com yield (por exemplo, uma conexão com banco de dados), isso mudou na versão 0.106.0 do FastAPI.
Dica
Adicionalmente, uma tarefa de background é, normalmente, um conjunto de lógicas independentes que devem ser manipuladas separadamente, com seus próprios recursos (e.g. sua própria conexão com banco de dados).
Então, dessa forma você provavelmente terá um código mais limpo.
Se você costumava depender desse comportamento, agora você precisa criar os recursos para uma tarefa de background dentro dela mesma, e usar internamente apenas dados que não dependam de recursos de dependências com yield.
Por exemplo, em vez de utilizar a mesma sessão do banco de dados, você criaria uma nova sessão dentro da tarefa de background, e você obteria os objetos do banco de dados utilizando essa nova sessão. E então, em vez de passar o objeto obtido do banco de dados como um parâmetro para a função da tarefa de background, você passaria o ID desse objeto e buscaria ele novamente dentro da função da tarefa de background.
Por baixo dos panos, o código open("./somefile.txt") cria um objeto que é chamado de "Gerenciador de Contexto".
Quando o bloco with finaliza, ele se certifica de fechar o arquivo, mesmo que tenha ocorrido alguma exceção.
Quando você cria uma dependência com yield, o FastAPI irá criar um gerenciador de contexto internamente para ela, e combiná-lo com algumas outras ferramentas relacionadas.
Utilizando gerenciadores de contexto em dependências com yield¶
Aviso
Isso é uma ideia mais ou menos "avançada".
Se você está apenas iniciando com o FastAPI você pode querer pular isso por enquanto.