presentation.py 3.9 KB

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