presentation.py 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. import base64
  2. import json
  3. import time
  4. from http import HTTPStatus
  5. from typing import Optional
  6. from fastapi import Depends
  7. from fastapi import Form
  8. from fastapi import Response
  9. from fastapi import UploadFile
  10. from fastapi.responses import JSONResponse
  11. from fastapi.security import HTTPBasic
  12. from fastapi.security import HTTPBasicCredentials
  13. from clean_python import DoesNotExist
  14. from clean_python import Page
  15. from clean_python import ValueObject
  16. from clean_python.fastapi import delete
  17. from clean_python.fastapi import get
  18. from clean_python.fastapi import patch
  19. from clean_python.fastapi import post
  20. from clean_python.fastapi import put
  21. from clean_python.fastapi import RequestQuery
  22. from clean_python.fastapi import Resource
  23. from clean_python.fastapi import v
  24. from .application import ManageBook
  25. from .domain import Author
  26. from .domain import Book
  27. class BookCreate(ValueObject):
  28. author: Author
  29. title: str
  30. class BookUpdate(ValueObject):
  31. author: Optional[Author] = None
  32. title: Optional[str] = None
  33. basic = HTTPBasic()
  34. class V1Books(Resource, version=v(1), name="books"):
  35. def __init__(self):
  36. self.manager = ManageBook()
  37. @get("/books", response_model=Page[Book])
  38. async def list(self, q: RequestQuery = Depends()):
  39. return await self.manager.filter([], q.as_page_options())
  40. @post("/books", status_code=HTTPStatus.CREATED, response_model=Book)
  41. async def create(self, obj: BookCreate):
  42. return await self.manager.create(obj.model_dump())
  43. @get("/books/{id}", response_model=Book)
  44. async def retrieve(self, id: int):
  45. return await self.manager.retrieve(id)
  46. @patch("/books/{id}", response_model=Book)
  47. async def update(self, id: int, obj: BookUpdate):
  48. return await self.manager.update(id, obj.model_dump(exclude_unset=True))
  49. @delete("/books/{id}", status_code=HTTPStatus.NO_CONTENT, response_class=Response)
  50. async def destroy(self, id: int):
  51. if not await self.manager.destroy(id):
  52. raise DoesNotExist("object", id)
  53. @get("/text")
  54. async def text(self):
  55. return Response("foo", media_type="text/plain")
  56. @post("/form", response_model=Author)
  57. async def form(self, name: str = Form()):
  58. return {"name": name}
  59. @post("/file")
  60. async def file(self, file: UploadFile):
  61. return {file.filename: (await file.read()).decode()}
  62. @put("/urlencode/{name}", response_model=Author)
  63. async def urlencode(self, name: str):
  64. return {"name": name}
  65. @post("/token")
  66. def token(
  67. self,
  68. grant_type: str = Form(),
  69. scope: str = Form(),
  70. credentials: HTTPBasicCredentials = Depends(basic),
  71. ):
  72. """For testing client credentials grant"""
  73. if grant_type != "client_credentials":
  74. return JSONResponse({"error": "invalid_grant"})
  75. if credentials.username != "testclient":
  76. return JSONResponse({"error": "invalid_client"})
  77. if credentials.password != "supersecret":
  78. return JSONResponse({"error": "invalid_client"})
  79. if scope != "all":
  80. return JSONResponse({"error": "invalid_grant"})
  81. claims = {"user": "foo", "exp": int(time.time()) + 3600}
  82. payload = base64.b64encode(json.dumps(claims).encode()).decode()
  83. return {
  84. "access_token": f"header.{payload}.signature",
  85. "token_type": "Bearer",
  86. "expires_in": 3600,
  87. }