From 04adca13006cb9a5f41da46a76ce3071204187c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Bompard?= Date: Wed, 21 Aug 2024 15:08:07 +0200 Subject: [PATCH] Return the webhook URL to the service creator/getter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I wish there was a cleaner way to do this: https://github.com/fastapi/fastapi/discussions/10864 Signed-off-by: Aurélien Bompard --- .../endpoints/models/service.py | 20 +++++++++++++++++-- .../endpoints/service.py | 19 ++++++++++++------ webhook_to_fedora_messaging/fasjson.py | 2 +- 3 files changed, 32 insertions(+), 9 deletions(-) diff --git a/webhook_to_fedora_messaging/endpoints/models/service.py b/webhook_to_fedora_messaging/endpoints/models/service.py index 2b60c95..aac2839 100644 --- a/webhook_to_fedora_messaging/endpoints/models/service.py +++ b/webhook_to_fedora_messaging/endpoints/models/service.py @@ -3,7 +3,15 @@ from enum import Enum from typing import List, Optional -from pydantic import BaseModel, ConfigDict +from pydantic import ( + BaseModel, + ConfigDict, + HttpUrl, + model_validator, + ValidationError, + ValidationInfo, +) +from starlette.requests import Request class ServiceType(str, Enum): @@ -25,7 +33,15 @@ class ServiceBase(BaseModel, ABC): class ServiceExternal(ServiceBase): - pass + webhook_url: HttpUrl | None = None + + @model_validator(mode="after") + def build_url(cls, data: "ServiceExternal", info: ValidationInfo): + if data.webhook_url is None and info.context is None: + raise ValidationError("The request must be set in the context of model_validate()") + request: Request = info.context["request"] + data.webhook_url = HttpUrl(str(request.url_for("create_message", uuid=data.uuid))) + return data class ServiceInternal(ServiceBase): diff --git a/webhook_to_fedora_messaging/endpoints/service.py b/webhook_to_fedora_messaging/endpoints/service.py index 426002e..cc533ae 100644 --- a/webhook_to_fedora_messaging/endpoints/service.py +++ b/webhook_to_fedora_messaging/endpoints/service.py @@ -5,6 +5,7 @@ from sqlalchemy import select from sqlalchemy.exc import IntegrityError, NoResultFound from sqlalchemy.ext.asyncio import AsyncSession +from starlette.requests import Request from starlette.status import ( HTTP_200_OK, HTTP_201_CREATED, @@ -32,6 +33,7 @@ @router.post("", status_code=HTTP_201_CREATED, response_model=ServiceResult, tags=["services"]) async def create_service( body: ServiceRequest, + request: Request, session: AsyncSession = Depends(get_session), # noqa : B008 user: User = Depends(current_user), # noqa : B008 ): @@ -51,11 +53,12 @@ async def create_service( logger.exception("Uniqueness constraint failed") raise HTTPException(HTTP_409_CONFLICT, "This service already exists") from expt (await made_service.awaitable_attrs.users).append(user) - return {"data": made_service} + return ServiceResult.model_validate({"data": made_service}, context={"request": request}) @router.get("", status_code=HTTP_200_OK, response_model=ServiceManyResult, tags=["services"]) async def list_services( + request: Request, session: AsyncSession = Depends(get_session), # noqa : B008 user: User = Depends(current_user), # noqa : B008 ): @@ -64,24 +67,26 @@ async def list_services( """ query = select(Service).where(Service.users.contains(user)) service_data = await session.scalars(query) - return {"data": service_data} + return ServiceManyResult.model_validate({"data": service_data}, context={"request": request}) @router.get("/{uuid}", status_code=HTTP_200_OK, response_model=ServiceResult, tags=["services"]) async def get_service( + request: Request, session: AsyncSession = Depends(get_session), # noqa : B008 service: Service = Depends(authorized_service_from_uuid), # noqa : B008 ): """ Return the service with the specified UUID """ - return {"data": service} + return ServiceResult.model_validate({"data": service}, context={"request": request}) @router.put( "/{uuid}/revoke", status_code=HTTP_202_ACCEPTED, response_model=ServiceResult, tags=["services"] ) async def revoke_service( + request: Request, session: AsyncSession = Depends(get_session), # noqa : B008 service: Service = Depends(authorized_service_from_uuid), # noqa : B008 ): @@ -90,7 +95,7 @@ async def revoke_service( """ service.disabled = True await session.flush() - return {"data": service} + return ServiceResult.model_validate({"data": service}, context={"request": request}) @router.put( @@ -98,6 +103,7 @@ async def revoke_service( ) async def update_service( body: ServiceUpdate, + request: Request, session: AsyncSession = Depends(get_session), # noqa : B008 service: Service = Depends(authorized_service_from_uuid), # noqa : B008 ): @@ -125,7 +131,7 @@ async def update_service( await session.flush() - return {"data": service} + return ServiceResult.model_validate({"data": service}, context={"request": request}) @router.put( @@ -135,6 +141,7 @@ async def update_service( tags=["services"], ) async def regenerate_token( + request: Request, session: AsyncSession = Depends(get_session), # noqa : B008 service: Service = Depends(authorized_service_from_uuid), # noqa : B008 ): @@ -143,4 +150,4 @@ async def regenerate_token( """ service.token = uuid4().hex await session.flush() - return {"data": service} + return ServiceResult.model_validate({"data": service}, context={"request": request}) diff --git a/webhook_to_fedora_messaging/fasjson.py b/webhook_to_fedora_messaging/fasjson.py index bc97f83..9f644cc 100644 --- a/webhook_to_fedora_messaging/fasjson.py +++ b/webhook_to_fedora_messaging/fasjson.py @@ -43,7 +43,7 @@ async def search_users( async def get_username_from_github(self, username): try: users = await self.search_users(github_username=username) - except httpx.ReadTimeout: + except httpx.TimeoutException: log.exception("Timeout fetching the FAS user with Github username %r", username) return None if len(users) == 1: