| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120 | import base64import jsonimport timefrom http import HTTPStatusfrom typing import Optionalfrom fastapi import Dependsfrom fastapi import Formfrom fastapi import Requestfrom fastapi import Responsefrom fastapi import UploadFilefrom fastapi.responses import JSONResponsefrom fastapi.security import HTTPBasicfrom fastapi.security import HTTPBasicCredentialsfrom clean_python import DoesNotExistfrom clean_python import Pagefrom clean_python import ValueObjectfrom clean_python.fastapi import deletefrom clean_python.fastapi import getfrom clean_python.fastapi import patchfrom clean_python.fastapi import postfrom clean_python.fastapi import putfrom clean_python.fastapi import RequestQueryfrom clean_python.fastapi import Resourcefrom clean_python.fastapi import vfrom .application import ManageBookfrom .domain import Authorfrom .domain import Bookclass BookCreate(ValueObject):    author: Author    title: strclass BookUpdate(ValueObject):    author: Optional[Author] = None    title: Optional[str] = Nonebasic = HTTPBasic()class V1Books(Resource, version=v(1), name="books"):    def __init__(self):        self.manager = ManageBook()    @get("/books", response_model=Page[Book])    async def list(self, q: RequestQuery = Depends()):        return await self.manager.filter([], q.as_page_options())    @post("/books", status_code=HTTPStatus.CREATED, response_model=Book)    async def create(self, obj: BookCreate):        return await self.manager.create(obj.model_dump())    @get("/books/{id}", response_model=Book)    async def retrieve(self, id: int):        return await self.manager.retrieve(id)    @patch("/books/{id}", response_model=Book)    async def update(self, id: int, obj: BookUpdate):        return await self.manager.update(id, obj.model_dump(exclude_unset=True))    @delete("/books/{id}", status_code=HTTPStatus.NO_CONTENT, response_class=Response)    async def destroy(self, id: int):        if not await self.manager.destroy(id):            raise DoesNotExist("object", id)    @get("/text")    async def text(self):        return Response("foo", media_type="text/plain")    @post("/form", response_model=Author)    async def form(self, name: str = Form()):        return {"name": name}    @post("/file")    async def file(self, file: UploadFile, description: str = Form()):        return {file.filename: (await file.read()).decode(), "description": description}    @put("/urlencode/{name}", response_model=Author)    async def urlencode(self, name: str):        return {"name": name}    @post("/token")    def token(        self,        request: Request,        grant_type: str = Form(),        scope: str = Form(),        credentials: HTTPBasicCredentials = Depends(basic),    ):        """For testing client credentials grant"""        if request.headers["Content-Type"] != "application/x-www-form-urlencoded":            return Response(status_code=HTTPStatus.METHOD_NOT_ALLOWED)        if grant_type != "client_credentials":            return JSONResponse(                {"error": "invalid_grant"}, status_code=HTTPStatus.BAD_REQUEST            )        if credentials.username != "testclient":            return JSONResponse(                {"error": "invalid_client"}, status_code=HTTPStatus.BAD_REQUEST            )        if credentials.password != "supersecret":            return JSONResponse(                {"error": "invalid_client"}, status_code=HTTPStatus.BAD_REQUEST            )        if scope != "all":            return JSONResponse(                {"error": "invalid_grant"}, status_code=HTTPStatus.BAD_REQUEST            )        claims = {"user": "foo", "exp": int(time.time()) + 3600}        payload = base64.b64encode(json.dumps(claims).encode()).decode()        return {            "access_token": f"header.{payload}.signature",            "token_type": "Bearer",            "expires_in": 3600,        }
 |