FastAPI-fastkit 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. fastapi_fastkit/__init__.py +10 -0
  2. fastapi_fastkit/__main__.py +8 -0
  3. fastapi_fastkit/backend.py +112 -0
  4. fastapi_fastkit/cli.py +432 -0
  5. fastapi_fastkit/core/__init__.py +0 -0
  6. fastapi_fastkit/core/exceptions.py +27 -0
  7. fastapi_fastkit/core/settings.py +107 -0
  8. fastapi_fastkit/fastapi_project_template/PROJECT_README_TEMPLATE.md +66 -0
  9. fastapi_fastkit/fastapi_project_template/README.md +20 -0
  10. fastapi_fastkit/fastapi_project_template/__init__.py +0 -0
  11. fastapi_fastkit/fastapi_project_template/fastapi-default/.env.test-tpl +3 -0
  12. fastapi_fastkit/fastapi_project_template/fastapi-default/.gitignore-tpl +191 -0
  13. fastapi_fastkit/fastapi_project_template/fastapi-default/README.md-tpl +17 -0
  14. fastapi_fastkit/fastapi_project_template/fastapi-default/main.py-tpl +21 -0
  15. fastapi_fastkit/fastapi_project_template/fastapi-default/requirements.txt-tpl +47 -0
  16. fastapi_fastkit/fastapi_project_template/fastapi-default/script/run-server.sh-tpl +2 -0
  17. fastapi_fastkit/fastapi_project_template/fastapi-default/script/run-test.sh-tpl +2 -0
  18. fastapi_fastkit/fastapi_project_template/fastapi-default/setup.cfg-tpl +13 -0
  19. fastapi_fastkit/fastapi_project_template/fastapi-default/setup.py-tpl +35 -0
  20. fastapi_fastkit/fastapi_project_template/fastapi-default/src/.DS_Store +0 -0
  21. fastapi_fastkit/fastapi_project_template/fastapi-default/src/__init__.py-tpl +88 -0
  22. fastapi_fastkit/fastapi_project_template/fastapi-default/src/core/__init__.py-tpl +0 -0
  23. fastapi_fastkit/fastapi_project_template/fastapi-default/src/core/settings.py-tpl +96 -0
  24. fastapi_fastkit/fastapi_project_template/fastapi-default/src/crud/__init__.py-tpl +0 -0
  25. fastapi_fastkit/fastapi_project_template/fastapi-default/src/crud/_base.py-tpl +92 -0
  26. fastapi_fastkit/fastapi_project_template/fastapi-default/src/crud/user.py-tpl +44 -0
  27. fastapi_fastkit/fastapi_project_template/fastapi-default/src/helper/__init__.py-tpl +0 -0
  28. fastapi_fastkit/fastapi_project_template/fastapi-default/src/helper/exceptions.py-tpl +94 -0
  29. fastapi_fastkit/fastapi_project_template/fastapi-default/src/helper/global_data.py-tpl +33 -0
  30. fastapi_fastkit/fastapi_project_template/fastapi-default/src/helper/logging.py-tpl +25 -0
  31. fastapi_fastkit/fastapi_project_template/fastapi-default/src/helper/pagination.py-tpl +71 -0
  32. fastapi_fastkit/fastapi_project_template/fastapi-default/src/mocks/__init__.py-tpl +0 -0
  33. fastapi_fastkit/fastapi_project_template/fastapi-default/src/mocks/mock_users.json-tpl +17 -0
  34. fastapi_fastkit/fastapi_project_template/fastapi-default/src/router/__init__.py-tpl +48 -0
  35. fastapi_fastkit/fastapi_project_template/fastapi-default/src/router/user.py-tpl +126 -0
  36. fastapi_fastkit/fastapi_project_template/fastapi-default/src/schemas/__init__.py-tpl +48 -0
  37. fastapi_fastkit/fastapi_project_template/fastapi-default/src/schemas/user.py-tpl +81 -0
  38. fastapi_fastkit/fastapi_project_template/fastapi-default/src/templates/index.html-tpl +27 -0
  39. fastapi_fastkit/fastapi_project_template/fastapi-default/src/utils/__init__.py-tpl +0 -0
  40. fastapi_fastkit/fastapi_project_template/fastapi-default/src/utils/documents.py-tpl +20 -0
  41. fastapi_fastkit/fastapi_project_template/fastapi-default/test/__init__.py-tpl +0 -0
  42. fastapi_fastkit/fastapi_project_template/fastapi-default/test/conftest.py-tpl +22 -0
  43. fastapi_fastkit/fastapi_project_template/fastapi-default/test/routes/__init__.py-tpl +0 -0
  44. fastapi_fastkit/fastapi_project_template/fastapi-default/test/routes/test_user.py-tpl +112 -0
  45. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/__init__.py-tpl +0 -0
  46. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/__init__.py-tpl +0 -0
  47. fastapi_fastkit/py.typed +0 -0
  48. fastapi_fastkit/utils/__init__.py +0 -0
  49. fastapi_fastkit/utils/inspector.py +15 -0
  50. fastapi_fastkit/utils/logging.py +33 -0
  51. fastapi_fastkit/utils/transducer.py +70 -0
  52. fastapi_fastkit-0.1.0.dist-info/METADATA +46 -0
  53. fastapi_fastkit-0.1.0.dist-info/RECORD +56 -0
  54. fastapi_fastkit-0.1.0.dist-info/WHEEL +4 -0
  55. fastapi_fastkit-0.1.0.dist-info/entry_points.txt +5 -0
  56. fastapi_fastkit-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,92 @@
1
+ # --------------------------------------------------------------------------
2
+ # The module defines basic Model CRUD methods.
3
+ # --------------------------------------------------------------------------
4
+ from __future__ import annotations
5
+
6
+ import json
7
+
8
+ from typing import Any, Type, List, Optional
9
+
10
+ from pydantic import BaseModel
11
+ from sqlalchemy import select, text
12
+ from sqlalchemy.ext.asyncio import AsyncSession
13
+
14
+
15
+ def load_json(file_path: str):
16
+ with open(file_path, "r", encoding="utf-8") as f:
17
+ return json.load(f)
18
+
19
+
20
+ async def get_object(
21
+ db: AsyncSession, model: Any, model_id: int | str, response_model: Type[BaseModel]
22
+ ) -> Optional[Any]:
23
+ result = await db.get(model, model_id)
24
+ return response_model.model_validate(result.__dict__)
25
+
26
+
27
+ async def get_object_with_uuid(
28
+ db: AsyncSession, model: Any, model_uid: str, response_model: Type[BaseModel]
29
+ ) -> Optional[Any]:
30
+ result = await db.get(model, model_uid)
31
+ return response_model.model_validate(result.__dict__)
32
+
33
+
34
+ async def get_objects(
35
+ db: AsyncSession,
36
+ model: Any,
37
+ response_model: Type[BaseModel],
38
+ condition: Optional[Any] = None,
39
+ skip: int = 0,
40
+ limit: int = 100,
41
+ ) -> List[Any]:
42
+ query = select(model).offset(skip).limit(limit)
43
+ if condition is not None:
44
+ query = query.where(text(condition))
45
+ result = await db.execute(query)
46
+ result_list = result.scalars().all()
47
+ return [response_model.model_validate(item.__dict__) for item in result_list]
48
+
49
+
50
+ async def create_object(
51
+ db: AsyncSession, model: Any, obj: BaseModel, response_model: Type[BaseModel]
52
+ ) -> Any:
53
+ obj_data = obj.model_dump()
54
+ db_obj = model(**obj_data)
55
+
56
+ db.add(db_obj)
57
+ await db.commit()
58
+ await db.refresh(db_obj)
59
+ return response_model.model_validate(db_obj.__dict__)
60
+
61
+
62
+ async def update_object(
63
+ db: AsyncSession,
64
+ model: Any,
65
+ model_id: int | str,
66
+ obj: BaseModel,
67
+ response_model: Type[BaseModel],
68
+ ) -> Optional[Any]:
69
+ query = select(model).filter(model.id == model_id)
70
+ db_obj = (await db.execute(query)).scalar_one_or_none()
71
+ if db_obj is None:
72
+ return None
73
+ update_data = obj.model_dump(exclude_unset=True)
74
+ for key, value in update_data.items():
75
+ setattr(db_obj, key, value)
76
+ db.add(db_obj)
77
+ await db.commit()
78
+ await db.refresh(db_obj)
79
+ return response_model.model_validate(db_obj.__dict__)
80
+
81
+
82
+ async def delete_object(
83
+ db: AsyncSession, model: Any, model_id: int | str
84
+ ) -> Optional[int]:
85
+ query = select(model).filter(model.id == model_id)
86
+ db_obj = (await db.execute(query)).scalar_one_or_none()
87
+ if db_obj:
88
+ await db.delete(db_obj)
89
+ await db.commit()
90
+ else:
91
+ return None
92
+ return model_id
@@ -0,0 +1,44 @@
1
+ # --------------------------------------------------------------------------
2
+ # The module defines User model's CRUD methods.
3
+ # --------------------------------------------------------------------------
4
+ import os
5
+
6
+ from typing import Type
7
+ from uuid import UUID
8
+
9
+ from ._base import load_json
10
+ from src.helper.exceptions import InternalException, ErrorCode
11
+ from src.schemas.user import UserSchema, UserCreate, UserUpdate
12
+ from src.helper.global_data import mock_user_data
13
+
14
+
15
+ def get_mock_user_data(id: UUID) -> dict:
16
+ user_data = next((user for user in mock_user_data if user["id"] == str(id)), None)
17
+ if user_data is None:
18
+ raise InternalException(
19
+ message="User not found.", error_code=ErrorCode.NOT_FOUND
20
+ )
21
+ return user_data
22
+
23
+
24
+ def create_mock_user(user: UserCreate) -> dict:
25
+ new_user = user.model_dump()
26
+ if any(u["email"] == new_user["email"] for u in mock_user_data):
27
+ raise InternalException(
28
+ error_code=ErrorCode.CONFLICT,
29
+ message="This user is already exist.",
30
+ )
31
+ mock_user_data.append(new_user)
32
+ return new_user
33
+
34
+
35
+ def update_mock_user(id: UUID, user: UserUpdate) -> dict:
36
+ user_data = get_mock_user_data(id)
37
+ update_data = user.model_dump(exclude_unset=True)
38
+ user_data.update(update_data)
39
+ return user_data
40
+
41
+
42
+ def delete_mock_user(id: UUID) -> None:
43
+ user_data = get_mock_user_data(id)
44
+ mock_user_data.remove(user_data)
@@ -0,0 +1,94 @@
1
+ # --------------------------------------------------------------------------
2
+ # The module defines custom Backend Application Exception class, overrides basic Exception.
3
+ # --------------------------------------------------------------------------
4
+ from enum import Enum
5
+ from datetime import datetime
6
+
7
+ from pydantic import BaseModel, Field
8
+
9
+ from src.schemas import ResponseSchema
10
+
11
+
12
+ class ErrorCode(Enum):
13
+ """
14
+ ErrorCode is designed to easily categorize errors into code in the log of the server application.
15
+
16
+ It is designed for custom code-based log filtering in third-party log monitoring systems
17
+ by matching with key HTTP status codes.
18
+ """
19
+ # HTTP
20
+ BAD_REQUEST = ("BAD_REQUEST", "HTTP-001", 400)
21
+ NOT_FOUND = ("NOT_FOUND", "HTTP-002", 404)
22
+ METHOD_NOT_ALLOWED = ("METHOD_NOT_ALLOWED", "HTTP-003", 405)
23
+ NOT_ACCESSABLE = ("NOT_ACCESSABLE", "HTTP-004", 406)
24
+ TIMEOUT = ("TIMEOUT", "HTTP-005", 408)
25
+ UNPROCESSABLE = ("UNPROCESSABLE", "HTTP-006", 422)
26
+ TOO_MANY_REQUEST = ("TOO_MANY_REQUEST", "HTTP-007", 429)
27
+
28
+ # DATA
29
+ CONFLICT = ("CONFLICT", "DATA-001", 409)
30
+
31
+ # AUTH
32
+ UNAUTHORIZED = ("UNAUTHORIZED", "AUTH-001", 401)
33
+ FORBIDDEN = ("FORBIDDEN", "AUTH-002", 403)
34
+
35
+ # SERVER
36
+ UNKNOWN_ERROR = ("UNKNOWN_ERROR", "SEVR-001", 500)
37
+ BAD_GATEWAY = ("BAD_GATEWAY", "SEVR-002", 502)
38
+ SERVICE_UNAVAILABLE = ("SERVICE_UNAVAILABLE", "SEVR-003", 503)
39
+ GATEWAY_TIMEOUT = ("GATEWAY_TIMEOUT", "SEVR-004", 504)
40
+
41
+ def __init__(self, error: str, code: str, status_code: int):
42
+ self.error = error
43
+ self.code = code
44
+ self.status_code = status_code
45
+
46
+
47
+ class ExceptionSchema(BaseModel):
48
+ timestamp: str = Field(
49
+ ...,
50
+ description="Timestamp of the error occurred.",
51
+ )
52
+ status: int = Field(..., description="HTTP status code of the error")
53
+ code: str = Field(
54
+ ...,
55
+ description="Server identification code of the error.",
56
+ )
57
+ message: str = Field(
58
+ ...,
59
+ description="Message content of the error.",
60
+ )
61
+ path: str = Field(
62
+ ...,
63
+ description="Path where the error occurred.",
64
+ )
65
+
66
+ class ConfigDict:
67
+ json_schema_extra = {
68
+ "example": {
69
+ "default": {
70
+ "timestamp": "2023-02-10T01:00:00.000Z",
71
+ "status": 500,
72
+ "code": "SEVR-000",
73
+ "path": "/v1/<some/endpoint>",
74
+ "message": "ERROR : Unknown error occurred in server logic.",
75
+ }
76
+ }
77
+ }
78
+
79
+
80
+ class InternalException(Exception):
81
+ def __init__(self, message: str, error_code: ErrorCode):
82
+ self.timestamp = datetime.utcnow().isoformat() + "Z"
83
+ self.status = error_code.status_code
84
+ self.error_code = error_code.code
85
+ self.message = f"ERROR : {message}"
86
+
87
+ def to_response(self, path: str) -> ResponseSchema[str]:
88
+ return ResponseSchema(
89
+ timestamp=self.timestamp,
90
+ status=self.status,
91
+ code=self.error_code,
92
+ path=path,
93
+ message=self.message,
94
+ )
@@ -0,0 +1,33 @@
1
+ # --------------------------------------------------------------------------
2
+ # The module initialize Mocking Data storage at FastAPI main coroutine's
3
+ # memory space.
4
+ # --------------------------------------------------------------------------
5
+ import os
6
+ import json
7
+
8
+ # Global Mock Data storage (Stores : FastAPI app coroutine - In FastAPI thread memory)
9
+ mock_user_data = []
10
+
11
+
12
+ def initialize_mock_data() -> None:
13
+ base_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))
14
+ user_file_path = os.path.join(base_path, "src", "mocks", "mock_users.json")
15
+
16
+ with open(user_file_path, "r", encoding="utf-8") as f:
17
+ user_data = json.load(f)
18
+
19
+ # If the model has relations with other model,
20
+ # you can map it their respective columns like below:
21
+ item_data = [{'some': 'item', "user_id": "123e4567-e89b-12d3-a456-426614174000"}]
22
+ item_map = {}
23
+ for item in item_data:
24
+ user_id = item["user_id"]
25
+ if user_id not in item_map:
26
+ item_map[user_id] = []
27
+ item_map[user_id].append(item)
28
+
29
+ for user in user_data:
30
+ user_id = user["id"]
31
+ user["items"] = item_map.get(user_id, [])
32
+
33
+ mock_user_data.extend(user_data)
@@ -0,0 +1,25 @@
1
+ # --------------------------------------------------------------------------
2
+ # The module defines Backend Application's logger.
3
+ # --------------------------------------------------------------------------
4
+ import logging
5
+ import logging.handlers
6
+
7
+ from src.core.settings import AppSettings
8
+
9
+
10
+ LOGGING_FORMAT = (
11
+ "[%(levelname)1.1s "
12
+ "%(asctime)s "
13
+ "P%(process)d "
14
+ "%(threadName)s "
15
+ "%(module)s:%(lineno)d] "
16
+ "%(message)s"
17
+ )
18
+
19
+
20
+ def init_logger(root_logger_name: str, app_settings: AppSettings) -> logging.Logger:
21
+ app_logger_level = (
22
+ logging.DEBUG if app_settings.LOGGING_DEBUG_LEVEL else logging.INFO
23
+ )
24
+
25
+ logging.basicConfig(level=app_logger_level, format=LOGGING_FORMAT)
@@ -0,0 +1,71 @@
1
+ # --------------------------------------------------------------------------
2
+ # The module defines paginatable object schemas' helper methods.
3
+ # --------------------------------------------------------------------------
4
+ from typing import TypeVar, Generic, List, Optional
5
+ from pydantic import BaseModel, Field, AnyHttpUrl
6
+ from fastapi import Request
7
+
8
+ T = TypeVar("T")
9
+
10
+
11
+ class PaginatedResponse(BaseModel, Generic[T]):
12
+ count: int = Field(
13
+ ..., title="Count", description="Indicates the total number of imported items."
14
+ )
15
+ next: Optional[AnyHttpUrl] = Field(
16
+ None,
17
+ title="Next",
18
+ description="Indicates the URL of the next page to import the data.",
19
+ )
20
+ previous: Optional[AnyHttpUrl] = Field(
21
+ None,
22
+ title="Previous",
23
+ description="Indicates the URL of the previous page from which the data was imported.",
24
+ )
25
+ results: List[T] = Field(
26
+ ...,
27
+ title="Results",
28
+ description="Lists the information of the imported items.",
29
+ )
30
+
31
+
32
+ class Paginator:
33
+ def __init__(self, data: List[T], page: int, per_page: int, request: Request):
34
+ self.data = data
35
+ self.page = page
36
+ self.page_size = per_page
37
+ self.limit = per_page
38
+ self.offset = (page - 1) * per_page
39
+ self.request = request
40
+ self.number_of_pages = self._get_number_of_pages(len(data))
41
+
42
+ def _get_next_page(self) -> Optional[str]:
43
+ if self.page >= self.number_of_pages:
44
+ return None
45
+ url = self.request.url.include_query_params(page=self.page + 1)
46
+ return str(url)
47
+
48
+ def _get_previous_page(self) -> Optional[str]:
49
+ if self.page == 1:
50
+ return None
51
+ url = self.request.url.include_query_params(page=self.page - 1)
52
+ return str(url)
53
+
54
+ def get_response(self) -> dict:
55
+ paginated_data = self.data[self.offset : self.offset + self.limit]
56
+ return {
57
+ "count": len(self.data),
58
+ "next": self._get_next_page(),
59
+ "previous": self._get_previous_page(),
60
+ "results": paginated_data,
61
+ }
62
+
63
+ def _get_number_of_pages(self, count: int) -> int:
64
+ rest = count % self.page_size
65
+ quotient = count // self.page_size
66
+ return quotient if not rest else quotient + 1
67
+
68
+
69
+ def paginate(data: List[T], page: int, page_size: int, request: Request) -> dict:
70
+ paginator = Paginator(data, page, page_size, request)
71
+ return paginator.get_response()
@@ -0,0 +1,17 @@
1
+ [
2
+ {
3
+ "id": "123e4567-e89b-12d3-a456-426614174000",
4
+ "userId": "bnbong",
5
+ "email": "bbbong9@gmail.com",
6
+ "roles": ["NORMAL_USER"],
7
+ "profileImageUrl": null,
8
+ "bio": "hello, My name is JunHyeok Lee.",
9
+ "firstName": "JunHyeok",
10
+ "lastName": "Lee",
11
+ "gender": "M",
12
+ "country": "KOREA",
13
+ "isActive": true,
14
+ "createdDate": "2000-02-10T01:53:16.707426",
15
+ "password": "password123"
16
+ }
17
+ ]
@@ -0,0 +1,48 @@
1
+ # --------------------------------------------------------------------------
2
+ # The module connect routers to Backend Application.
3
+ # --------------------------------------------------------------------------
4
+ import os
5
+ import json
6
+
7
+ from uuid import UUID
8
+ from datetime import datetime
9
+
10
+ from fastapi import APIRouter, status, Request
11
+ from fastapi.responses import JSONResponse, HTMLResponse
12
+ from fastapi.templating import Jinja2Templates
13
+
14
+ from src.helper.exceptions import InternalException
15
+
16
+ from .user import router as user_router
17
+
18
+ router = APIRouter(prefix="/v1")
19
+
20
+ router.include_router(user_router, tags=["user"])
21
+
22
+ templates = Jinja2Templates(os.path.join(os.path.dirname(__file__), "../templates"))
23
+
24
+
25
+ @router.get("/", response_class=HTMLResponse, include_in_schema=False)
26
+ async def root(request: Request):
27
+ return templates.TemplateResponse("index.html", {"request": request})
28
+
29
+
30
+ @router.get(
31
+ "/ping",
32
+ summary="Server health check",
33
+ description="Checking FastAPI server's health.",
34
+ response_model=dict,
35
+ responses={
36
+ 200: {
37
+ "description": "Ping Success",
38
+ "content": {"application/json": {"example": {"ping": "pong"}}},
39
+ },
40
+ },
41
+ )
42
+ async def ping():
43
+ return {"ping": "pong"}
44
+
45
+
46
+ def load_json(file_path: str):
47
+ with open(file_path, "r", encoding="utf-8") as f:
48
+ return json.load(f)
@@ -0,0 +1,126 @@
1
+ # --------------------------------------------------------------------------
2
+ # The module defines User router.
3
+ # --------------------------------------------------------------------------
4
+ from . import *
5
+
6
+ from src.helper.global_data import mock_user_data
7
+ from src.crud.user import get_mock_user_data
8
+ from src.schemas import ResponseSchema
9
+ from src.schemas.user import UserSchema, UserCreate, UserUpdate
10
+
11
+
12
+ router = APIRouter(
13
+ prefix="/user",
14
+ )
15
+
16
+
17
+ def generate_new_user_id():
18
+ if mock_user_data:
19
+ return max(_user["id"] for _user in mock_user_data) + 1
20
+ return 1
21
+
22
+
23
+ @router.post(
24
+ "/",
25
+ summary="Create a new user.",
26
+ status_code=status.HTTP_201_CREATED,
27
+ response_model=UserSchema,
28
+ )
29
+ async def create_user_route(
30
+ data: UserCreate,
31
+ request: Request,
32
+ ):
33
+ try:
34
+ new_user = create_mock_user(data)
35
+ response = ResponseSchema(
36
+ timestamp=datetime.utcnow().isoformat() + "Z",
37
+ status=201,
38
+ code="HTTP-201",
39
+ path=str(request.url),
40
+ message=UserSchema(**new_user),
41
+ )
42
+ return response
43
+ except InternalException as e:
44
+ return JSONResponse(
45
+ status_code=e.status,
46
+ content=e.to_response(path=str(request.url)).model_dump(),
47
+ )
48
+
49
+
50
+ @router.get(
51
+ "/{id}",
52
+ summary="Get a user info",
53
+ description="Inquires information about a specific user.",
54
+ response_model=ResponseSchema[UserSchema],
55
+ )
56
+ async def get_user_route(id: UUID, request: Request):
57
+ try:
58
+ user = get_mock_user_data(id)
59
+ response = ResponseSchema(
60
+ timestamp=datetime.utcnow().isoformat() + "Z",
61
+ status=200,
62
+ code="HTTP-200",
63
+ path=str(request.url),
64
+ message=UserSchema(**user),
65
+ )
66
+ return response
67
+ except InternalException as e:
68
+ return JSONResponse(
69
+ status_code=e.status,
70
+ content=e.to_response(path=str(request.url)).model_dump(),
71
+ )
72
+
73
+
74
+ @router.patch(
75
+ "/{id}",
76
+ summary="Update a user.",
77
+ status_code=status.HTTP_200_OK,
78
+ response_model=UserSchema,
79
+ )
80
+ async def update_user_route(
81
+ id: UUID,
82
+ data: UserUpdate,
83
+ ):
84
+ try:
85
+ user = get_mock_user_data(id)
86
+ if user["username"] != request_user:
87
+ raise InternalException(
88
+ message="ERROR : You do not have permission to modify.", error_code=ErrorCode.UNAUTHORIZED
89
+ )
90
+ user.update(data.dict(exclude_unset=True))
91
+ user["modifiedAt"] = datetime.utcnow().isoformat() + "Z"
92
+ mock_user_data[mock_user_data.index(user)] = user
93
+ response = ResponseSchema(
94
+ timestamp=datetime.utcnow().isoformat() + "Z",
95
+ status=200,
96
+ code="HTTP-200",
97
+ path=str(request.url),
98
+ message=UserSchema(**user),
99
+ )
100
+ return response
101
+ except InternalException as e:
102
+ return JSONResponse(
103
+ status_code=e.status,
104
+ content=e.to_response(path=str(request.url)).model_dump(),
105
+ )
106
+
107
+
108
+ @router.delete(
109
+ "/{id}",
110
+ summary="Delete a user.",
111
+ status_code=status.HTTP_204_NO_CONTENT,
112
+ )
113
+ async def delete_user_route(id: UUID):
114
+ try:
115
+ user = get_mock_user_data(id)
116
+ if user["username"] != request_user:
117
+ raise InternalException(
118
+ message="ERROR : You do not have permission to delete.", error_code=ErrorCode.UNAUTHORIZED
119
+ )
120
+ mock_user_data.remove(user)
121
+ return JSONResponse(status_code=status.HTTP_204_NO_CONTENT, content="")
122
+ except InternalException as e:
123
+ return JSONResponse(
124
+ status_code=e.status,
125
+ content=e.to_response(path=str(request.url)).model_dump(),
126
+ )
@@ -0,0 +1,48 @@
1
+ # --------------------------------------------------------------------------
2
+ # The module defines Base response schemas.
3
+ # --------------------------------------------------------------------------
4
+ from pydantic import BaseModel, Field
5
+ from typing import Generic, TypeVar
6
+
7
+ T = TypeVar("T")
8
+
9
+
10
+ class ResponseSchema(BaseModel, Generic[T]):
11
+ timestamp: str = Field(
12
+ ...,
13
+ description="The timestamp when the response was generated.",
14
+ )
15
+ status: int = Field(..., description="HTTP status code.")
16
+ code: str = Field(
17
+ ...,
18
+ description="Server identification code.",
19
+ )
20
+ path: str = Field(
21
+ ...,
22
+ description="Request path.",
23
+ )
24
+ message: T = Field(
25
+ ...,
26
+ description="Data details or error messages requested.",
27
+ )
28
+
29
+ class ConfigDict:
30
+ json_schema_extra = {
31
+ "example": {
32
+ "timestamp": "2023-02-10T01:00:00.000Z",
33
+ "status": 200,
34
+ "code": "HTTP-200",
35
+ "path": "/v1/<some/endpoint>",
36
+ "message": {
37
+ "id": "123e4567-e89b-12d3-a456-426614174000",
38
+ "email": "example@example.com",
39
+ "password": "secret",
40
+ "nickname": "example_nick",
41
+ "create_at": "2023-02-10T01:00:00.000Z",
42
+ "bio": "example bio",
43
+ "profile_img": "https://example.com/profile.jpg",
44
+ "first_name": "John",
45
+ "last_name": "Doe",
46
+ },
47
+ }
48
+ }
@@ -0,0 +1,81 @@
1
+ # --------------------------------------------------------------------------
2
+ # The module defines User schemas.
3
+ # --------------------------------------------------------------------------
4
+ from datetime import datetime, date
5
+ from uuid import UUID
6
+ from enum import Enum
7
+
8
+ from pydantic import BaseModel, Field, EmailStr, SecretStr, field_serializer
9
+ from typing import Optional, List
10
+
11
+
12
+ class UserBase(BaseModel):
13
+ email: EmailStr = Field(
14
+ ..., title="User's Email", description="The email address of the user."
15
+ )
16
+ bio: str = Field(None, title="User's bio", description="Personal introduction of the user.")
17
+ firstName: str = Field(
18
+ ..., title="User's first name", description="The real name of the user."
19
+ )
20
+ lastName: str = Field(
21
+ ..., title="User's last name", description="The last name of the user."
22
+ )
23
+ gender: str = Field(..., title="User's gender", description="The gender of the user.")
24
+ country: str = Field(..., title="User's country", description="Country of the user.")
25
+ isActive: bool = Field(
26
+ ..., title="User's active status", description="The active state of the user."
27
+ )
28
+ createdDate: datetime = Field(
29
+ ...,
30
+ title="User's account created date",
31
+ description="Date of creation of the user account.",
32
+ )
33
+ password: Optional[SecretStr] = Field(
34
+ None, title="User's password", description="Password of the user account."
35
+ )
36
+
37
+ # items: Optional[List] = List[ItemSchema]
38
+
39
+ @field_serializer("password", when_used="json")
40
+ def dump_secret(self, v):
41
+ return v.get_secret_value()
42
+
43
+
44
+ class UserSchema(UserBase):
45
+ id: UUID = Field(
46
+ ..., title="User's ID (pk)", description="The unique database identifier for the user."
47
+ )
48
+ userId: str = Field(..., title="User's ID", description="ID of the user account.")
49
+ roles: List[str] = Field(
50
+ ..., title="User's roles", description="List of roles for the user."
51
+ )
52
+ profileImageUrl: Optional[str] = Field(
53
+ None,
54
+ title="User's profile image URL",
55
+ description="The URL of the user's profile image.",
56
+ )
57
+
58
+ class ConfigDict:
59
+ from_attributes = True
60
+
61
+
62
+ class UserCreate(UserBase):
63
+ firstName: str = Field(
64
+ ..., title="User's first name", description="The real name of the user."
65
+ )
66
+ lastName: str = Field(
67
+ ..., title="User's last name", description="The last name of the user."
68
+ )
69
+ email: EmailStr = Field(
70
+ ..., title="User's Email", description="The email address of the user."
71
+ )
72
+ password: str = Field(
73
+ ..., title="User's password", description="Password of the user account."
74
+ )
75
+
76
+
77
+ class UserUpdate(BaseModel):
78
+ bio: str = Field(None, title="User's bio", description="Personal introduction of the user.")
79
+ profileImg: str = Field(
80
+ None, title="User's profile image", description="The URL of the user's profile image."
81
+ )