fastapi-scaff 0.0.4__py3-none-any.whl → 0.0.5__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.

Potentially problematic release.


This version of fastapi-scaff might be problematic. Click here for more details.

fastapi_scaff/__init__.py CHANGED
@@ -7,4 +7,4 @@
7
7
  @history
8
8
  """
9
9
 
10
- __version__ = "0.0.4"
10
+ __version__ = "0.0.5"
@@ -1,10 +1,10 @@
1
1
  {
2
- "abd_app_api.py": "import traceback\n\nfrom fastapi import APIRouter, Depends\n\nfrom app.api.response import Response, response_docs\nfrom app.business.tpl import (\n TplDetailBiz,\n)\nfrom app.api.status import Status\nfrom app.initializer import g\nfrom app.middleware.auth import JWTUser, get_current_user\n\ntpl_router = APIRouter()\n\n\n@tpl_router.get(\n path=\"/tpl/{tpl_id}\",\n summary=\"tplDetail\",\n responses=response_docs(\n model=TplDetailBiz,\n ),\n)\nasync def detail(\n tpl_id: str,\n current_user: JWTUser = Depends(get_current_user),\n):\n try:\n tpl_biz = TplDetailBiz(id=tpl_id)\n data = await tpl_biz.detail()\n if not data:\n return Response.failure(msg=\"\u672a\u5339\u914d\u5230\u8bb0\u5f55\", status=Status.RECORD_NOT_EXIST_ERROR)\n except Exception as e:\n g.logger.error(traceback.format_exc())\n return Response.failure(msg=\"tplDetail\u5931\u8d25\", error=e)\n return Response.success(data=data)\n",
2
+ "abd_app_api.py": "import traceback\n\nfrom fastapi import APIRouter, Depends\nfrom starlette.requests import Request\n\nfrom app.api.response import Response, response_docs\nfrom app.business.tpl import (\n TplDetailBiz,\n)\nfrom app.api.status import Status\nfrom app.initializer import g\nfrom app.middleware.auth import JWTUser, get_current_user\n\ntpl_router = APIRouter()\n\n\n@tpl_router.get(\n path=\"/tpl/{tpl_id}\",\n summary=\"tplDetail\",\n responses=response_docs(\n model=TplDetailBiz,\n ),\n)\nasync def detail(\n request: Request,\n tpl_id: str,\n current_user: JWTUser = Depends(get_current_user),\n):\n try:\n tpl_biz = TplDetailBiz(id=tpl_id)\n data = await tpl_biz.detail()\n if not data:\n return Response.failure(status=Status.RECORD_NOT_EXIST_ERROR, request=request)\n except Exception as e:\n g.logger.error(traceback.format_exc())\n return Response.failure(msg=\"tplDetail\u5931\u8d25\", error=e, request=request)\n return Response.success(data=data, request=request)\n",
3
3
  "abd_app_business.py": "from app.dschema.tpl import (\n TplDetail,\n)\n\n\nclass TplDetailBiz(TplDetail):\n\n async def detail(self):\n # TODO: \u4e1a\u52a1\u903b\u8f91\n pass\n",
4
4
  "abd_app_dschema.py": "from pydantic import BaseModel, Field\nfrom sqlalchemy import Column, String\n\nfrom app.dschema import DeclBase, filter_fields\nfrom app.initializer import g\n\n\nclass Tpl(DeclBase):\n __tablename__ = \"tpl\"\n\n id = Column(String(20), primary_key=True, default=g.snow_cli.gen_uid, comment=\"\u4e3b\u952e\")\n name = Column(String(50), nullable=False, comment=\"\u540d\u79f0\")\n\n\nclass TplDetail(BaseModel):\n id: str = Field(...)\n # #\n name: str = None\n\n @classmethod\n def response_fields(cls):\n return filter_fields(\n cls,\n exclude=[]\n )\n",
5
- "ab_app_api.py": "import traceback\n\nfrom fastapi import APIRouter, Depends\n\nfrom app.api.response import Response, response_docs\nfrom app.business.tpl import (\n TplDetailBiz,\n)\nfrom app.api.status import Status\nfrom app.initializer import g\nfrom app.middleware.auth import JWTUser, get_current_user\n\ntpl_router = APIRouter()\n\n\n@tpl_router.get(\n path=\"/tpl/{tpl_id}\",\n summary=\"tplDetail\",\n responses=response_docs(\n model=TplDetailBiz,\n ),\n)\nasync def detail(\n tpl_id: str,\n current_user: JWTUser = Depends(get_current_user),\n):\n try:\n tpl_biz = TplDetailBiz(id=tpl_id)\n data = await tpl_biz.detail()\n if not data:\n return Response.failure(msg=\"\u672a\u5339\u914d\u5230\u8bb0\u5f55\", status=Status.RECORD_NOT_EXIST_ERROR)\n except Exception as e:\n g.logger.error(traceback.format_exc())\n return Response.failure(msg=\"tplDetail\u5931\u8d25\", error=e)\n return Response.success(data=data)\n",
5
+ "ab_app_api.py": "import traceback\n\nfrom fastapi import APIRouter, Depends\nfrom starlette.requests import Request\n\nfrom app.api.response import Response, response_docs\nfrom app.business.tpl import (\n TplDetailBiz,\n)\nfrom app.api.status import Status\nfrom app.initializer import g\nfrom app.middleware.auth import JWTUser, get_current_user\n\ntpl_router = APIRouter()\n\n\n@tpl_router.get(\n path=\"/tpl/{tpl_id}\",\n summary=\"tplDetail\",\n responses=response_docs(\n model=TplDetailBiz,\n ),\n)\nasync def detail(\n request: Request,\n tpl_id: str,\n current_user: JWTUser = Depends(get_current_user),\n):\n try:\n tpl_biz = TplDetailBiz(id=tpl_id)\n data = await tpl_biz.detail()\n if not data:\n return Response.failure(status=Status.RECORD_NOT_EXIST_ERROR, request=request)\n except Exception as e:\n g.logger.error(traceback.format_exc())\n return Response.failure(msg=\"tplDetail\u5931\u8d25\", error=e, request=request)\n return Response.success(data=data, request=request)\n",
6
6
  "ab_app_business.py": "from pydantic import BaseModel, Field\n\n\nclass TplDetailBiz(BaseModel):\n id: str = Field(...)\n\n async def detail(self):\n # TODO: \u4e1a\u52a1\u903b\u8f91\n pass\n",
7
- "a_app_api.py": "import traceback\n\nfrom fastapi import APIRouter, Depends\n\nfrom app.api.response import Response, response_docs\nfrom app.api.status import Status\nfrom app.initializer import g\nfrom app.middleware.auth import JWTUser, get_current_user\n\ntpl_router = APIRouter()\n\n\n@tpl_router.get(\n path=\"/tpl/{tpl_id}\",\n summary=\"tplDetail\",\n responses=response_docs(),\n)\nasync def detail(\n tpl_id: str,\n current_user: JWTUser = Depends(get_current_user),\n):\n try:\n data = {} # TODO: \u5f85\u5904\u7406\n if not data:\n return Response.failure(msg=\"\u672a\u5339\u914d\u5230\u8bb0\u5f55\", status=Status.RECORD_NOT_EXIST_ERROR)\n except Exception as e:\n g.logger.error(traceback.format_exc())\n return Response.failure(msg=\"tplDetail\u5931\u8d25\", error=e)\n return Response.success(data=data)\n",
7
+ "a_app_api.py": "import traceback\n\nfrom fastapi import APIRouter, Depends\nfrom starlette.requests import Request\n\nfrom app.api.response import Response, response_docs\nfrom app.api.status import Status\nfrom app.initializer import g\nfrom app.middleware.auth import JWTUser, get_current_user\n\ntpl_router = APIRouter()\n\n\n@tpl_router.get(\n path=\"/tpl/{tpl_id}\",\n summary=\"tplDetail\",\n responses=response_docs(),\n)\nasync def detail(\n request: Request,\n tpl_id: str,\n current_user: JWTUser = Depends(get_current_user),\n):\n try:\n data = {} # TODO: \u5f85\u5904\u7406\n if not data:\n return Response.failure(status=Status.RECORD_NOT_EXIST_ERROR, request=request)\n except Exception as e:\n g.logger.error(traceback.format_exc())\n return Response.failure(msg=\"tplDetail\u5931\u8d25\", error=e, request=request)\n return Response.success(data=data, request=request)\n",
8
8
  "only_app_api.py": "import traceback\n\nfrom fastapi import APIRouter, Depends\n\nfrom app.api.response import Response, response_docs\nfrom app.api.status import Status\nfrom app.initializer import g\nfrom app.middleware.auth import JWTUser, get_current_user\n\ntpl_router = APIRouter()\n\n\n@tpl_router.get(\n path=\"/tpl/{tpl_id}\",\n summary=\"tplDetail\",\n responses=response_docs(),\n)\nasync def detail(\n tpl_id: str,\n current_user: JWTUser = Depends(get_current_user),\n):\n try:\n data = {} # TODO: \u5f85\u5904\u7406\n if not data:\n return Response.failure(msg=\"\u672a\u5339\u914d\u5230\u8bb0\u5f55\", status=Status.RECORD_NOT_EXIST_ERROR)\n except Exception as e:\n g.logger.error(traceback.format_exc())\n return Response.failure(msg=\"tplDetail\u5931\u8d25\", error=e)\n return Response.success(data=data)\n",
9
9
  "only_app_business.py": "from pydantic import BaseModel, Field\n\n\nclass TplDetailBiz(BaseModel):\n id: str = Field(...)\n\n async def detail(self):\n # TODO: \u4e1a\u52a1\u903b\u8f91\n pass\n"
10
10
  }
@@ -7,12 +7,12 @@
7
7
  "app/main.py": "\"\"\"\n@author axiner\n@version v1.0.0\n@created 2024/07/29 22:22\n@abstract main\n@description\n@history\n\"\"\"\nfrom contextlib import asynccontextmanager\nfrom fastapi import FastAPI\nfrom fastapi.responses import ORJSONResponse\n\nfrom app import (\n router,\n middleware,\n)\nfrom app.initializer import g\n\ng.setup()\n# #\nopenapi_url = \"/openapi.json\"\ndocs_url = \"/docs\"\nredoc_url = \"/redoc\"\nif g.config.is_disable_docs is True:\n openapi_url, docs_url, redoc_url = None, None, None\n\n\n@asynccontextmanager\nasync def lifespan(app_: FastAPI):\n g.logger.info(f\"Application using config file '{g.config.yamlname}'\")\n g.logger.info(f\"Application name '{g.config.appname}'\")\n g.logger.info(f\"Application version '{g.config.appversion}'\")\n # #\n g.logger.info(\"Application server running\")\n yield\n g.logger.info(\"Application server shutdown\")\n\n\napp = FastAPI(\n title=g.config.appname,\n version=g.config.appversion,\n debug=g.config.debug,\n openapi_url=openapi_url,\n docs_url=docs_url,\n redoc_url=redoc_url,\n lifespan=lifespan,\n default_response_class=ORJSONResponse,\n)\n# #\nrouter.register_routers(app)\nmiddleware.register_middlewares(app)\n",
8
8
  "app/__init__.py": "\"\"\"\n@author axiner\n@version v1.0.0\n@created 2024/07/29 22:22\n@abstract app\n@description\n@history\n\"\"\"\nfrom pathlib import Path\n\nAPP_DIR = Path(__file__).absolute().parent\n",
9
9
  "app/api/exception.py": "from typing import Any\n\nfrom app.api.status import Status\n\n\nclass CustomException(Exception):\n\n def __init__(\n self,\n msg: str = None,\n code: int = None,\n data: Any = None,\n status: Status = Status.FAILURE,\n ):\n self.msg = msg or status.msg\n self.code = code or status.code\n self.data = data\n self.status = status\n\n def __str__(self) -> str:\n return f\"{self.code} {self.msg}\"\n\n def __repr__(self) -> str:\n return f\"<{self.__class__.__name__}: ({self.code!r}, {self.msg!r})>\"\n",
10
- "app/api/response.py": "import json\nfrom typing import Mapping, get_type_hints, Any\n\nfrom fastapi.encoders import jsonable_encoder\nfrom starlette.background import BackgroundTask\nfrom starlette.responses import JSONResponse, StreamingResponse, ContentStream\nfrom toollib.utils import now2timestamp, map_jsontype\n\nfrom app.api.status import Status\n\n\nclass Response:\n\n @staticmethod\n def success(\n data: dict | list | str | None = None,\n msg: str = None,\n code: int = None,\n status: Status = Status.SUCCESS,\n is_encode_data: bool = False,\n status_code: int = 200,\n headers: Mapping[str, str] | None = None,\n media_type: str | None = None,\n background: BackgroundTask | None = None,\n ) -> JSONResponse:\n return JSONResponse(\n content={\n \"time\": now2timestamp(),\n \"msg\": msg or status.msg,\n \"code\": code or status.code,\n \"data\": Response.encode_data(data) if is_encode_data else data,\n },\n status_code=status_code,\n headers=headers,\n media_type=media_type,\n background=background,\n )\n\n @staticmethod\n def failure(\n msg: str = None,\n code: int = None,\n error: str | Exception | None = None,\n data: dict | list | str | None = None,\n status: Status = Status.FAILURE,\n is_encode_data: bool = False,\n status_code: int = 200,\n headers: Mapping[str, str] | None = None,\n media_type: str | None = None,\n background: BackgroundTask | None = None,\n ) -> JSONResponse:\n return JSONResponse(\n content={\n \"time\": now2timestamp(),\n \"msg\": msg or status.msg,\n \"code\": code or status.code,\n \"error\": str(error) if error else None,\n \"data\": Response.encode_data(data) if is_encode_data else data,\n },\n status_code=status_code,\n headers=headers,\n media_type=media_type,\n background=background,\n )\n\n @staticmethod\n def encode_data(data: Any) -> Any:\n if data is None or isinstance(data, (str, int, float, bool)):\n return data\n if isinstance(data, (dict, list)):\n try:\n json.dumps(data)\n return data\n except (TypeError, OverflowError):\n pass\n return jsonable_encoder(data)\n\n @staticmethod\n def stream(\n content: ContentStream,\n status_code: int = 200,\n headers: Mapping[str, str] | None = None,\n media_type: str | None = None,\n background: BackgroundTask | None = None,\n ) -> StreamingResponse:\n return StreamingResponse(\n content=content,\n status_code=status_code,\n headers=headers,\n media_type=media_type,\n background=background,\n )\n\n\ndef response_docs(\n model=None, # \u6a21\u578b(BaseModel): \u81ea\u52a8\u4ece\u6a21\u578b\u4e2d\u89e3\u6790\u5b57\u6bb5\u4e0e\u7c7b\u578b\n data: dict | str = None, # \u6570\u636e(dict/str): \u76f4\u63a5\u7ed9\u5b9a\u5b57\u6bb5\u4e0e\u7c7b\u578b/\u7c7b\u578b\n is_listwrap: bool = False,\n listwrap_key: str = None,\n listwrap_key_extra: dict = None,\n docs_extra: dict = None,\n):\n \"\"\"\u54cd\u5e94\u6587\u6863\"\"\"\n\n def _data_from_model(model_, default: str = \"\u672a\u77e5\") -> dict:\n \"\"\"\u6570\u636e\u6a21\u677f\"\"\"\n data_ = {}\n if hasattr(model_, \"response_fields\"):\n all_fields = set(model_.response_fields())\n else:\n all_fields = set(model_.model_fields.keys())\n type_hints = get_type_hints(model_)\n for field_name in all_fields:\n try:\n t = type_hints.get(field_name)\n t = str(t).replace(\"<class '\", \"\").replace(\"'>\", \"\") if t else default\n except Exception:\n t = default\n data_[field_name] = t\n return data_\n\n final_data = {}\n if model:\n final_data = _data_from_model(model)\n if data:\n if isinstance(data, dict):\n final_data.update(data)\n else:\n final_data = data\n if is_listwrap:\n final_data = [final_data] if not isinstance(final_data, list) else final_data\n if listwrap_key:\n final_data = {listwrap_key: final_data}\n if listwrap_key_extra:\n final_data.update(listwrap_key_extra)\n\n def _format_value(value):\n if isinstance(value, str):\n _value = value.split(\"|\")\n if len(_value) > 1:\n return \" | \".join([map_jsontype(_v.strip(), is_keep_integer=True) for _v in _value])\n return map_jsontype(value, is_keep_integer=True)\n elif isinstance(value, dict):\n return {k: _format_value(v) for k, v in value.items()}\n elif isinstance(value, (list, tuple)):\n return [_format_value(item) for item in value]\n else:\n return str(value)\n\n format_data = _format_value(final_data)\n\n docs = {\n 200: {\n \"description\": \"\u64cd\u4f5c\u6210\u529f\u3010code\u4e3a0 & http\u72b6\u6001\u7801200\u3011\",\n \"content\": {\n \"application/json\": {\n \"example\": {\n \"time\": \"integer\",\n \"msg\": \"string\",\n \"code\": \"integer\",\n \"data\": format_data\n }\n }\n }\n },\n 422: {\n \"description\": \"\u64cd\u4f5c\u5931\u8d25\u3010code\u975e0 & http\u72b6\u6001\u7801200\u3011\",\n \"content\": {\n \"application/json\": {\n \"example\": {\n \"time\": \"integer\",\n \"msg\": \"string\",\n \"code\": \"integer\",\n \"error\": \"string\",\n \"data\": \"object | array | ...\",\n }\n }\n }\n },\n }\n if docs_extra:\n docs.update(docs_extra)\n return docs\n",
11
- "app/api/status.py": "from enum import Enum\n\n\nclass Status(Enum):\n SUCCESS = (0, '\u64cd\u4f5c\u6210\u529f')\n FAILURE = (1, '\u64cd\u4f5c\u5931\u8d25')\n\n PARAMS_ERROR = (400, '\u53c2\u6570\u9519\u8bef')\n UNAUTHORIZED_ERROR = (401, '\u8ba4\u8bc1\u5931\u8d25')\n # \u5efa\u8bae\uff1a\u4e1a\u52a1\u6a21\u5757\u9519\u8bef\u7801\u4ece10000\u5f00\u59cb\n RECORD_NOT_EXIST_ERROR = (10000, '\u8bb0\u5f55\u4e0d\u5b58\u5728')\n RECORD_EXISTS_ERROR = (10001, '\u8bb0\u5f55\u5df2\u5b58\u5728')\n\n @property\n def code(self):\n return self.value[0]\n\n @property\n def msg(self):\n return self.value[1]\n\n @classmethod\n def collect_status(cls):\n text = \"\"\n for s in cls:\n text += f\"{s.code} {s.msg}\\n\"\n return text\n",
10
+ "app/api/response.py": "import json\nfrom typing import Mapping, get_type_hints, Any\n\nfrom fastapi.encoders import jsonable_encoder\nfrom starlette.background import BackgroundTask\nfrom starlette.requests import Request\nfrom starlette.responses import JSONResponse, StreamingResponse, ContentStream\nfrom toollib.utils import map_jsontype\n\nfrom app.api.status import Status\n\n\nclass Response:\n\n @staticmethod\n def success(\n data: dict | list | str | None = None,\n msg: str = None,\n code: int = None,\n status: Status = Status.SUCCESS,\n is_encode_data: bool = False,\n request: Request = None,\n status_code: int = 200,\n headers: Mapping[str, str] | None = None,\n media_type: str | None = None,\n background: BackgroundTask | None = None,\n ) -> JSONResponse:\n content = {\n \"msg\": msg or status.msg,\n \"code\": code or status.code,\n \"data\": Response.encode_data(data) if is_encode_data else data,\n }\n if request:\n if request_id := getattr(request.state, 'request_id', None):\n content[\"request_id\"] = request_id\n return JSONResponse(\n content=content,\n status_code=status_code,\n headers=headers,\n media_type=media_type,\n background=background,\n )\n\n @staticmethod\n def failure(\n msg: str = None,\n code: int = None,\n error: str | Exception | None = None,\n data: dict | list | str | None = None,\n status: Status = Status.FAILURE,\n is_encode_data: bool = False,\n request: Request = None,\n status_code: int = 200,\n headers: Mapping[str, str] | None = None,\n media_type: str | None = None,\n background: BackgroundTask | None = None,\n ) -> JSONResponse:\n content = {\n \"msg\": msg or status.msg,\n \"code\": code or status.code,\n \"error\": str(error) if error else None,\n \"data\": Response.encode_data(data) if is_encode_data else data,\n }\n if request:\n if request_id := getattr(request.state, 'request_id', None):\n content[\"request_id\"] = request_id\n return JSONResponse(\n content=content,\n status_code=status_code,\n headers=headers,\n media_type=media_type,\n background=background,\n )\n\n @staticmethod\n def encode_data(data: Any) -> Any:\n if data is None or isinstance(data, (str, int, float, bool)):\n return data\n if isinstance(data, (dict, list)):\n try:\n json.dumps(data)\n return data\n except (TypeError, OverflowError):\n pass\n return jsonable_encoder(data)\n\n @staticmethod\n def stream(\n content: ContentStream,\n status_code: int = 200,\n headers: Mapping[str, str] | None = None,\n media_type: str | None = None,\n background: BackgroundTask | None = None,\n ) -> StreamingResponse:\n return StreamingResponse(\n content=content,\n status_code=status_code,\n headers=headers,\n media_type=media_type,\n background=background,\n )\n\n\ndef response_docs(\n model=None, # \u6a21\u578b(BaseModel): \u81ea\u52a8\u4ece\u6a21\u578b\u4e2d\u89e3\u6790\u5b57\u6bb5\u4e0e\u7c7b\u578b\n data: dict | str = None, # \u6570\u636e(dict/str): \u76f4\u63a5\u7ed9\u5b9a\u5b57\u6bb5\u4e0e\u7c7b\u578b/\u7c7b\u578b\n is_listwrap: bool = False,\n listwrap_key: str = None,\n listwrap_key_extra: dict = None,\n docs_extra: dict = None,\n):\n \"\"\"\u54cd\u5e94\u6587\u6863\"\"\"\n\n def _data_from_model(model_, default: str = \"\u672a\u77e5\") -> dict:\n \"\"\"\u6570\u636e\u6a21\u677f\"\"\"\n data_ = {}\n if hasattr(model_, \"response_fields\"):\n all_fields = set(model_.response_fields())\n else:\n all_fields = set(model_.model_fields.keys())\n type_hints = get_type_hints(model_)\n for field_name in all_fields:\n try:\n t = type_hints.get(field_name)\n t = str(t).replace(\"<class '\", \"\").replace(\"'>\", \"\") if t else default\n except Exception:\n t = default\n data_[field_name] = t\n return data_\n\n final_data = {}\n if model:\n final_data = _data_from_model(model)\n if data:\n if isinstance(data, dict):\n final_data.update(data)\n else:\n final_data = data\n if is_listwrap:\n final_data = [final_data] if not isinstance(final_data, list) else final_data\n if listwrap_key:\n final_data = {listwrap_key: final_data}\n if listwrap_key_extra:\n final_data.update(listwrap_key_extra)\n\n def _format_value(value):\n if isinstance(value, str):\n _value = value.split(\"|\")\n if len(_value) > 1:\n return \" | \".join([map_jsontype(_v.strip(), is_keep_integer=True) for _v in _value])\n return map_jsontype(value, is_keep_integer=True)\n elif isinstance(value, dict):\n return {k: _format_value(v) for k, v in value.items()}\n elif isinstance(value, (list, tuple)):\n return [_format_value(item) for item in value]\n else:\n return str(value)\n\n format_data = _format_value(final_data)\n\n docs = {\n 200: {\n \"description\": \"\u64cd\u4f5c\u6210\u529f\u3010code\u4e3a0 & http\u72b6\u6001\u7801200\u3011\",\n \"content\": {\n \"application/json\": {\n \"example\": {\n \"msg\": \"string\",\n \"code\": \"integer\",\n \"data\": format_data,\n \"request_id\": \"string\",\n }\n }\n }\n },\n 422: {\n \"description\": \"\u64cd\u4f5c\u5931\u8d25\u3010code\u975e0 & http\u72b6\u6001\u7801200\u3011\",\n \"content\": {\n \"application/json\": {\n \"example\": {\n \"msg\": \"string\",\n \"code\": \"integer\",\n \"error\": \"string\",\n \"data\": \"object | array | ...\",\n \"request_id\": \"string\",\n }\n }\n }\n },\n }\n if docs_extra:\n docs.update(docs_extra)\n return docs\n",
11
+ "app/api/status.py": "from enum import Enum\n\n\nclass Status(Enum):\n SUCCESS = (0, '\u64cd\u4f5c\u6210\u529f')\n FAILURE = (1, '\u64cd\u4f5c\u5931\u8d25')\n\n PARAMS_ERROR = (400, '\u53c2\u6570\u9519\u8bef')\n UNAUTHORIZED_ERROR = (401, '\u8ba4\u8bc1\u5931\u8d25')\n # \u5efa\u8bae\uff1a\u4e1a\u52a1\u6a21\u5757\u9519\u8bef\u7801\u4ece10000\u5f00\u59cb\n RECORD_NOT_EXIST_ERROR = (10000, '\u8bb0\u5f55\u4e0d\u5b58\u5728')\n RECORD_EXISTS_ERROR = (10001, '\u8bb0\u5f55\u5df2\u5b58\u5728')\n USER_OR_PASSWORD_ERROR = (10002, '\u7528\u6237\u540d\u6216\u5bc6\u7801\u9519\u8bef')\n\n @property\n def code(self):\n return self.value[0]\n\n @property\n def msg(self):\n return self.value[1]\n\n @classmethod\n def collect_status(cls):\n text = \"\"\n for s in cls:\n text += f\"{s.code} {s.msg}\\n\"\n return text\n",
12
12
  "app/api/__init__.py": "\"\"\"\napi\n\"\"\"\n",
13
13
  "app/api/default/ping.py": "from fastapi import APIRouter\n\nping_router = APIRouter()\n\n\n@ping_router.get(\n path=\"/ping\",\n summary=\"ping\",\n)\ndef ping():\n return \"pong\"\n",
14
14
  "app/api/default/__init__.py": "\"\"\"\napi-default\n\"\"\"\n\n_prefix = \"/api\"\n",
15
- "app/api/v1/user.py": "import traceback\n\nfrom fastapi import APIRouter, Depends\n\nfrom app.api.response import Response, response_docs\nfrom app.api.status import Status\nfrom app.business.user import (\n UserDetailBiz,\n UserListBiz,\n UserCreateBiz,\n UserUpdateBiz,\n UserDeleteBiz,\n UserLoginBiz,\n UserTokenBiz,\n)\nfrom app.initializer import g\nfrom app.middleware.auth import JWTUser, get_current_user\n\nuser_router = APIRouter()\n_active = True # \u6fc0\u6d3b\u72b6\u6001\uff08\u9ed8\u8ba4\u6fc0\u6d3b\uff09\n_tag = \"user\" # \u6807\u7b7e\uff08\u9ed8\u8ba4\u6a21\u5757\u540d\u6216\u5b50\u76ee\u5f55\u540d\uff09\n\n\n# \u6ce8\u610f\uff1a`user`\u4ec5\u4e3a\u6a21\u5757\u793a\u4f8b\uff0c\u8bf7\u6839\u636e\u81ea\u8eab\u9700\u6c42\u4fee\u6539\n# \u6ce8\u610f\uff1a`user`\u4ec5\u4e3a\u6a21\u5757\u793a\u4f8b\uff0c\u8bf7\u6839\u636e\u81ea\u8eab\u9700\u6c42\u4fee\u6539\n# \u6ce8\u610f\uff1a`user`\u4ec5\u4e3a\u6a21\u5757\u793a\u4f8b\uff0c\u8bf7\u6839\u636e\u81ea\u8eab\u9700\u6c42\u4fee\u6539\n\n\n@user_router.get(\n path=\"/user/{user_id}\",\n summary=\"userDetail\",\n responses=response_docs(\n model=UserDetailBiz,\n ),\n)\nasync def detail(\n user_id: str,\n current_user: JWTUser = Depends(get_current_user), # \u8ba4\u8bc1\n):\n try:\n user_biz = UserDetailBiz(id=user_id)\n data = await user_biz.detail()\n if not data:\n return Response.failure(msg=\"\u672a\u5339\u914d\u5230\u8bb0\u5f55\", status=Status.RECORD_NOT_EXIST_ERROR)\n except Exception as e:\n g.logger.error(traceback.format_exc())\n return Response.failure(msg=\"userDetail\u5931\u8d25\", error=e)\n return Response.success(data=data)\n\n\n@user_router.get(\n path=\"/user\",\n summary=\"userList\",\n responses=response_docs(\n model=UserListBiz,\n is_listwrap=True,\n listwrap_key=\"items\",\n listwrap_key_extra={\n \"total\": \"int\",\n },\n ),\n)\nasync def lst(\n page: int = 1,\n size: int = 10,\n current_user: JWTUser = Depends(get_current_user),\n):\n try:\n user_biz = UserListBiz(page=page, size=size)\n data, total = await user_biz.lst()\n except Exception as e:\n g.logger.error(traceback.format_exc())\n return Response.failure(msg=\"userList\u5931\u8d25\", error=e)\n return Response.success(data={\"items\": data, \"total\": total})\n\n\n@user_router.post(\n path=\"/user\",\n summary=\"userCreate\",\n responses=response_docs(data={\n \"id\": \"str\",\n }),\n)\nasync def create(\n user_biz: UserCreateBiz,\n):\n try:\n user_id = await user_biz.create()\n if not user_id:\n return Response.failure(msg=\"\u7528\u6237\u5df2\u5b58\u5728\", status=Status.RECORD_EXISTS_ERROR)\n except Exception as e:\n g.logger.error(traceback.format_exc())\n return Response.failure(msg=\"userCreate\u5931\u8d25\", error=e)\n return Response.success(data={\"id\": user_id})\n\n\n@user_router.put(\n path=\"/user/{user_id}\",\n summary=\"userUpdate\",\n responses=response_docs(data={\n \"id\": \"str\",\n }),\n)\nasync def update(\n user_id: str,\n user_biz: UserUpdateBiz,\n current_user: JWTUser = Depends(get_current_user),\n):\n try:\n updated_ids = await user_biz.update(user_id)\n if not updated_ids:\n return Response.failure(msg=\"\u672a\u5339\u914d\u5230\u8bb0\u5f55\", status=Status.RECORD_NOT_EXIST_ERROR)\n except Exception as e:\n g.logger.error(traceback.format_exc())\n return Response.failure(msg=\"userUpdate\u5931\u8d25\", error=e)\n return Response.success(data={\"id\": user_id})\n\n\n@user_router.delete(\n path=\"/user/{user_id}\",\n summary=\"userDelete\",\n responses=response_docs(data={\n \"id\": \"str\",\n }),\n)\nasync def delete(\n user_id: str,\n current_user: JWTUser = Depends(get_current_user),\n):\n try:\n user_biz = UserDeleteBiz()\n deleted_ids = await user_biz.delete(user_id)\n if not deleted_ids:\n return Response.failure(msg=\"\u672a\u5339\u914d\u5230\u8bb0\u5f55\", status=Status.RECORD_NOT_EXIST_ERROR)\n except Exception as e:\n g.logger.error(traceback.format_exc())\n return Response.failure(msg=\"userDelete\u5931\u8d25\", error=e)\n return Response.success(data={\"id\": user_id})\n\n\n@user_router.post(\n path=\"/user/login\",\n summary=\"userLogin\",\n responses=response_docs(data={\n \"token\": \"str\",\n }),\n)\nasync def login(\n user_biz: UserLoginBiz,\n):\n try:\n data = await user_biz.login()\n if not data:\n return Response.failure(msg=\"\u7528\u6237\u540d\u6216\u5bc6\u7801\u9519\u8bef\", status=Status.UNAUTHORIZED_ERROR)\n except Exception as e:\n g.logger.error(traceback.format_exc())\n return Response.failure(msg=\"userLogin\u5931\u8d25\", error=e)\n return Response.success(data={\"token\": data})\n\n\n@user_router.post(\n path=\"/user/token\",\n summary=\"userToken\",\n responses=response_docs(data={\n \"token\": \"str\",\n }),\n)\nasync def token(\n user_biz: UserTokenBiz,\n current_user: JWTUser = Depends(get_current_user),\n):\n try:\n data = await user_biz.token()\n if not data:\n return Response.failure(msg=\"\u672a\u5339\u914d\u5230\u8bb0\u5f55\", status=Status.RECORD_NOT_EXIST_ERROR)\n except Exception as e:\n g.logger.error(traceback.format_exc())\n return Response.failure(msg=\"userToken\u5931\u8d25\", error=e)\n return Response.success(data={\"token\": data})\n",
15
+ "app/api/v1/user.py": "import traceback\n\nfrom fastapi import APIRouter, Depends\nfrom starlette.requests import Request\n\nfrom app.api.response import Response, response_docs\nfrom app.api.status import Status\nfrom app.business.user import (\n UserDetailBiz,\n UserListBiz,\n UserCreateBiz,\n UserUpdateBiz,\n UserDeleteBiz,\n UserLoginBiz,\n UserTokenBiz,\n)\nfrom app.initializer import g\nfrom app.middleware.auth import JWTUser, get_current_user\n\nuser_router = APIRouter()\n_active = True # \u6fc0\u6d3b\u72b6\u6001\uff08\u9ed8\u8ba4\u6fc0\u6d3b\uff09\n_tag = \"user\" # \u6807\u7b7e\uff08\u9ed8\u8ba4\u6a21\u5757\u540d\u6216\u5b50\u76ee\u5f55\u540d\uff09\n\n\n# \u6ce8\u610f\uff1a`user`\u4ec5\u4e3a\u6a21\u5757\u793a\u4f8b\uff0c\u8bf7\u6839\u636e\u81ea\u8eab\u9700\u6c42\u4fee\u6539\n# \u6ce8\u610f\uff1a`user`\u4ec5\u4e3a\u6a21\u5757\u793a\u4f8b\uff0c\u8bf7\u6839\u636e\u81ea\u8eab\u9700\u6c42\u4fee\u6539\n# \u6ce8\u610f\uff1a`user`\u4ec5\u4e3a\u6a21\u5757\u793a\u4f8b\uff0c\u8bf7\u6839\u636e\u81ea\u8eab\u9700\u6c42\u4fee\u6539\n\n\n@user_router.get(\n path=\"/user/{user_id}\",\n summary=\"userDetail\",\n responses=response_docs(\n model=UserDetailBiz,\n ),\n)\nasync def detail(\n request: Request,\n user_id: str,\n current_user: JWTUser = Depends(get_current_user), # \u8ba4\u8bc1\n):\n try:\n user_biz = UserDetailBiz(id=user_id)\n data = await user_biz.detail()\n if not data:\n return Response.failure(status=Status.RECORD_NOT_EXIST_ERROR)\n except Exception as e:\n g.logger.error(traceback.format_exc())\n return Response.failure(msg=\"userDetail\u5931\u8d25\", error=e, request=request)\n return Response.success(data=data, request=request)\n\n\n@user_router.get(\n path=\"/user\",\n summary=\"userList\",\n responses=response_docs(\n model=UserListBiz,\n is_listwrap=True,\n listwrap_key=\"items\",\n listwrap_key_extra={\n \"total\": \"int\",\n },\n ),\n)\nasync def lst(\n request: Request,\n page: int = 1,\n size: int = 10,\n current_user: JWTUser = Depends(get_current_user),\n):\n try:\n user_biz = UserListBiz(page=page, size=size)\n data, total = await user_biz.lst()\n except Exception as e:\n g.logger.error(traceback.format_exc())\n return Response.failure(msg=\"userList\u5931\u8d25\", error=e, request=request)\n return Response.success(data={\"items\": data, \"total\": total}, request=request)\n\n\n@user_router.post(\n path=\"/user\",\n summary=\"userCreate\",\n responses=response_docs(data={\n \"id\": \"str\",\n }),\n)\nasync def create(\n request: Request,\n user_biz: UserCreateBiz,\n):\n try:\n user_id = await user_biz.create()\n if not user_id:\n return Response.failure(status=Status.RECORD_EXISTS_ERROR, request=request)\n except Exception as e:\n g.logger.error(traceback.format_exc())\n return Response.failure(msg=\"userCreate\u5931\u8d25\", error=e, request=request)\n return Response.success(data={\"id\": user_id}, request=request)\n\n\n@user_router.put(\n path=\"/user/{user_id}\",\n summary=\"userUpdate\",\n responses=response_docs(data={\n \"id\": \"str\",\n }),\n)\nasync def update(\n request: Request,\n user_id: str,\n user_biz: UserUpdateBiz,\n current_user: JWTUser = Depends(get_current_user),\n):\n try:\n updated_ids = await user_biz.update(user_id)\n if not updated_ids:\n return Response.failure(status=Status.RECORD_NOT_EXIST_ERROR, request=request)\n except Exception as e:\n g.logger.error(traceback.format_exc())\n return Response.failure(msg=\"userUpdate\u5931\u8d25\", error=e, request=request)\n return Response.success(data={\"id\": user_id}, request=request)\n\n\n@user_router.delete(\n path=\"/user/{user_id}\",\n summary=\"userDelete\",\n responses=response_docs(data={\n \"id\": \"str\",\n }),\n)\nasync def delete(\n request: Request,\n user_id: str,\n current_user: JWTUser = Depends(get_current_user),\n):\n try:\n user_biz = UserDeleteBiz()\n deleted_ids = await user_biz.delete(user_id)\n if not deleted_ids:\n return Response.failure(status=Status.RECORD_NOT_EXIST_ERROR, request=request)\n except Exception as e:\n g.logger.error(traceback.format_exc())\n return Response.failure(msg=\"userDelete\u5931\u8d25\", error=e, request=request)\n return Response.success(data={\"id\": user_id}, request=request)\n\n\n@user_router.post(\n path=\"/user/login\",\n summary=\"userLogin\",\n responses=response_docs(data={\n \"token\": \"str\",\n }),\n)\nasync def login(\n request: Request,\n user_biz: UserLoginBiz,\n):\n try:\n data = await user_biz.login()\n if not data:\n return Response.failure(status=Status.USER_OR_PASSWORD_ERROR, request=request)\n except Exception as e:\n g.logger.error(traceback.format_exc())\n return Response.failure(msg=\"userLogin\u5931\u8d25\", error=e, request=request)\n return Response.success(data={\"token\": data}, request=request)\n\n\n@user_router.post(\n path=\"/user/token\",\n summary=\"userToken\",\n responses=response_docs(data={\n \"token\": \"str\",\n }),\n)\nasync def token(\n request: Request,\n user_biz: UserTokenBiz,\n current_user: JWTUser = Depends(get_current_user),\n):\n try:\n data = await user_biz.token()\n if not data:\n return Response.failure(status=Status.RECORD_NOT_EXIST_ERROR, request=request)\n except Exception as e:\n g.logger.error(traceback.format_exc())\n return Response.failure(msg=\"userToken\u5931\u8d25\", error=e, request=request)\n return Response.success(data={\"token\": data}, request=request)\n",
16
16
  "app/api/v1/__init__.py": "\"\"\"\napi-v1\n\"\"\"\n\n_prefix = \"/api/v1\"\n",
17
17
  "app/aplugin/__init__.py": "\"\"\"\n\u63d2\u4ef6\n\"\"\"\n",
18
18
  "app/atask/__init__.py": "\"\"\"\n\u5f02\u6b65\u4efb\u52a1\n eg: celery\n\"\"\"\n",
@@ -20,16 +20,18 @@
20
20
  "app/business/__init__.py": "\"\"\"\n\u4e1a\u52a1\n\"\"\"\n",
21
21
  "app/dschema/user.py": "import re\nfrom typing import Literal\n\nfrom pydantic import BaseModel, Field, field_validator\nfrom sqlalchemy import Column, BigInteger, Integer, String\nfrom toollib.utils import now2timestamp\n\nfrom app.dschema import DeclBase, filter_fields\nfrom app.initializer import g\n\n\nclass User(DeclBase):\n __tablename__ = \"user\"\n\n id = Column(String(20), primary_key=True, default=g.snow_cli.gen_uid, comment=\"\u4e3b\u952e\")\n phone = Column(String(15), unique=True, index=True, nullable=False, comment=\"\u624b\u673a\u53f7\")\n password = Column(String(128), nullable=True, comment=\"\u5bc6\u7801\")\n jwt_key = Column(String(128), nullable=True, comment=\"jwtKey\")\n name = Column(String(50), nullable=True, comment=\"\u540d\u79f0\")\n age = Column(Integer, nullable=True, comment=\"\u5e74\u9f84\")\n gender = Column(Integer, nullable=True, comment=\"\u6027\u522b\")\n created_at = Column(BigInteger, default=now2timestamp, comment=\"\u521b\u5efa\u65f6\u95f4\")\n updated_at = Column(BigInteger, default=now2timestamp, onupdate=now2timestamp, comment=\"\u66f4\u65b0\u65f6\u95f4\")\n\n\nclass UserDetail(BaseModel):\n id: str = Field(...)\n # #\n phone: str = None\n name: str = None\n age: int = None\n gender: int = None\n created_at: int = None\n updated_at: int = None\n\n @classmethod\n def response_fields(cls):\n return filter_fields(\n cls,\n exclude=[]\n )\n\n\nclass UserList(BaseModel):\n page: int = Field(1, ge=1)\n size: int = Field(10, ge=1)\n # #\n id: str = None\n phone: str = None\n name: str = None\n age: int = None\n gender: int = None\n created_at: int = None\n updated_at: int = None\n\n @classmethod\n def response_fields(cls):\n return filter_fields(\n cls,\n exclude=[\n \"page\",\n \"size\",\n ]\n )\n\n\nclass UserCreate(BaseModel):\n phone: str = Field(..., pattern=r\"^1[3-9]\\d{9}$\")\n password: str = Field(...)\n name: str | None = Field(None)\n age: int | None = Field(None, ge=0, le=200)\n gender: Literal[1, 2] | None = Field(None)\n\n @field_validator(\"password\")\n def validate_password(cls, v):\n if not re.match(r\"^(?=.*[A-Za-z])(?=.*\\d)\\S{6,20}$\", v):\n raise ValueError(\"\u5bc6\u7801\u5fc5\u987b\u5305\u542b\u81f3\u5c11\u4e00\u4e2a\u5b57\u6bcd\u548c\u4e00\u4e2a\u6570\u5b57\uff0c\u957f\u5ea6\u4e3a6-20\u4f4d\u7684\u975e\u7a7a\u767d\u5b57\u7b26\u7ec4\u5408\")\n return v\n\n @field_validator(\"name\")\n def validate_name(cls, v, info):\n if not v and (phone := info.data.get(\"phone\")):\n return f\"\u7528\u6237{phone[-4:]}\"\n if v and not re.match(r\"^[\\u4e00-\\u9fffA-Za-z0-9_\\-.]{1,50}$\", v):\n raise ValueError(\"\u540d\u79f0\u4ec5\u96501-50\u4f4d\u7684\u4e2d\u6587\u3001\u82f1\u6587\u3001\u6570\u5b57\u3001_-.\u7ec4\u5408\")\n return v\n\n\nclass UserUpdate(BaseModel):\n name: str | None = Field(None)\n age: int | None = Field(None, ge=0, le=200)\n gender: Literal[1, 2] | None = Field(None)\n\n @field_validator(\"name\")\n def validate_name(cls, v):\n if v and not re.match(r\"^[\\u4e00-\\u9fffA-Za-z0-9_\\-.]{1,50}$\", v):\n raise ValueError(\"\u540d\u79f0\u4ec5\u96501-50\u4f4d\u7684\u4e2d\u6587\u3001\u82f1\u6587\u3001\u6570\u5b57\u3001_-.\u7ec4\u5408\")\n return v\n\n\nclass UserDelete(BaseModel):\n pass\n\n\nclass UserLogin(BaseModel):\n phone: str = Field(...)\n password: str = Field(...)\n\n\nclass UserToken(BaseModel):\n id: str = Field(...)\n exp_minutes: int = Field(24 * 60 * 30, ge=1)\n",
22
22
  "app/dschema/__init__.py": "\"\"\"\n\u6570\u636e\u7ed3\u6784\uff08data schema\uff09\n\"\"\"\nfrom sqlalchemy.orm import declarative_base\n\nDeclBase = declarative_base()\n\n\ndef filter_fields(\n model,\n exclude: list = None,\n):\n if exclude:\n return list(set(model.model_fields.keys()) - set(exclude))\n return list(model.model_fields.keys())\n",
23
+ "app/initializer/context.py": "from contextvars import ContextVar\n\nrequest_id_ctx_var: ContextVar[str] = ContextVar(\"request_id\", default=\"N/A\")\n",
23
24
  "app/initializer/_conf.py": "import os\nfrom pathlib import Path\n\nimport yaml\nfrom dotenv import load_dotenv\nfrom toollib.utils import get_cls_attrs, parse_variable\n\nfrom app import APP_DIR\n\n_CONFIG_DIR = APP_DIR.parent.joinpath(\"config\")\n\nload_dotenv(dotenv_path=os.environ.setdefault(\n key=\"envpath\",\n value=str(_CONFIG_DIR.joinpath(\".env\")))\n)\n# #\nappyaml = Path(\n os.environ.get(\"appyaml\") or\n _CONFIG_DIR.joinpath(f\"app_{os.environ.setdefault(key='appenv', value='dev')}.yaml\")\n)\nif not appyaml.is_file():\n raise RuntimeError(f\"\u914d\u7f6e\u6587\u4ef6\u4e0d\u5b58\u5728\uff1a{appyaml}\")\n\n\nclass EnvConfig:\n \"\"\"env\u914d\u7f6e\"\"\"\n snow_datacenter_id: int = None\n\n def setattr_from_env(self):\n cls_attrs = get_cls_attrs(EnvConfig)\n for k, item in cls_attrs.items():\n v_type, v = item\n if callable(v_type):\n v = parse_variable(k=k, v_type=v_type, v_from=os.environ, default=v)\n setattr(self, k, v)\n\n\nclass Config(EnvConfig):\n \"\"\"\u914d\u7f6e\"\"\"\n _yamlconf: dict = None\n yamlname: str = appyaml.name\n #\n appname: str = \"xApp\"\n appversion: str = \"1.0.0\"\n debug: bool = True\n log_dir: str = \"./log\"\n is_disable_docs: bool = True\n # #\n redis_host: str = None\n redis_port: int = None\n redis_db: int = None\n redis_password: str = None\n redis_max_connections: int = None\n db_url: str = None\n db_async_url: str = None\n\n def setup(self):\n self.setattr_from_env()\n self.setattr_from_yaml()\n return self\n\n def setattr_from_yaml(self):\n cls_attrs = get_cls_attrs(Config)\n for k, item in cls_attrs.items():\n v_type, v = item\n if callable(v_type):\n v = parse_variable(k=k, v_type=v_type, v_from=self.load_yaml(), default=v)\n setattr(self, k, v)\n\n def load_yaml(self, reload: bool = False) -> dict:\n if self._yamlconf and not reload:\n return self._yamlconf\n with open(appyaml, mode=\"r\", encoding=\"utf-8\") as file:\n self._yamlconf = yaml.load(file, Loader=yaml.FullLoader)\n return self._yamlconf\n\n\ndef init_config() -> Config:\n return Config().setup()\n",
24
25
  "app/initializer/_db.py": "import asyncio\nimport importlib\n\nfrom sqlalchemy import create_engine, exc\nfrom sqlalchemy.ext.asyncio import create_async_engine, AsyncSession\nfrom sqlalchemy.orm import sessionmaker, scoped_session\n\nfrom app import APP_DIR\n\n_DSCHEMA_MOD_DIR = APP_DIR.joinpath(\"DSCHEMA\")\n_DSCHEMA_MOD_BASE = \"app.dschema\"\n\n_is_tables_created = False\n\n\ndef init_db_session(\n db_url: str,\n db_echo: bool,\n db_pool_size: int = 10,\n db_max_overflow: int = 5,\n db_pool_recycle: int = 3600,\n is_create_tables: bool = True,\n) -> scoped_session:\n db_echo = db_echo or False\n kwargs = {\n \"pool_size\": db_pool_size,\n \"max_overflow\": db_max_overflow,\n \"pool_recycle\": db_pool_recycle,\n }\n if db_url.startswith(\"sqlite\"):\n kwargs = {}\n engine = create_engine(\n url=db_url,\n echo=db_echo,\n echo_pool=db_echo,\n **kwargs,\n )\n db_session = sessionmaker(engine, expire_on_commit=False)\n\n def create_tables():\n from app.dschema import DeclBase\n _import_tables()\n try:\n DeclBase.metadata.create_all(engine)\n except (\n exc.OperationalError,\n exc.IntegrityError,\n exc.ProgrammingError,\n ) as e:\n if \"already exists\" not in str(e):\n raise\n\n global _is_tables_created\n if is_create_tables and not _is_tables_created:\n create_tables()\n _is_tables_created = True\n\n return scoped_session(db_session)\n\n\ndef init_db_async_session(\n db_url: str,\n db_echo: bool,\n db_pool_size: int = 10,\n db_max_overflow: int = 5,\n db_pool_recycle: int = 3600,\n is_create_tables: bool = True,\n) -> sessionmaker:\n db_echo = db_echo or False\n kwargs = {\n \"pool_size\": db_pool_size,\n \"max_overflow\": db_max_overflow,\n \"pool_recycle\": db_pool_recycle,\n }\n if db_url.startswith(\"sqlite\"):\n kwargs = {}\n async_engine = create_async_engine(\n url=db_url,\n echo=db_echo,\n echo_pool=db_echo,\n **kwargs,\n )\n db_async_session = sessionmaker(async_engine, class_=AsyncSession, expire_on_commit=False) # noqa\n\n async def create_tables():\n from app.dschema import DeclBase\n _import_tables()\n async with async_engine.begin() as conn:\n try:\n await conn.run_sync(DeclBase.metadata.create_all)\n except (\n exc.OperationalError,\n exc.IntegrityError,\n exc.ProgrammingError,\n ) as e:\n if \"already exists\" not in str(e):\n raise\n\n global _is_tables_created\n if is_create_tables and not _is_tables_created:\n try:\n loop = asyncio.get_running_loop()\n except RuntimeError:\n loop = asyncio.new_event_loop()\n asyncio.set_event_loop(loop)\n task = loop.create_task(create_tables())\n task.add_done_callback(lambda t: t.result() if not t.cancelled() else None)\n if not loop.is_running():\n loop.run_until_complete(task)\n _is_tables_created = True\n return db_async_session\n\n\ndef _import_tables():\n \"\"\"\u5bfc\u5165\u8868\"\"\"\n for f in _DSCHEMA_MOD_DIR.glob(\"*.py\"):\n if not f.name.startswith(\"__\"):\n _ = importlib.import_module(f\"{_DSCHEMA_MOD_BASE}.{f.stem}\")\n",
25
- "app/initializer/_log.py": "import os\nimport sys\nfrom pathlib import Path\n\nfrom loguru import logger\nfrom loguru._logger import Logger # noqa\n\n_LOG_CONSOLE_FORMAT = \"{time:YYYY-MM-DD HH:mm:ss.SSS} {level} {file}:{line} {message}\"\n_LOG_FILE_FORMAT = \"{time:YYYY-MM-DD HH:mm:ss.SSS} {level} {file}:{line} {message}\"\n_LOG_FILE_PREFIX = \"app\"\n_LOG_ROTATION = \"100 MB\"\n_LOG_RETENTION = \"15 days\"\n_LOG_COMPRESSION = None\n_LOG_ENQUEUE = True\n_LOG_BACKTRACE = False\n_LOG_DIAGNOSE = False\n_LOG_PID = False\n\n\ndef init_logger(\n debug: bool,\n log_dir: str = None,\n) -> Logger:\n logger.remove(None)\n _lever = \"DEBUG\" if debug else \"INFO\"\n logger.add(\n sys.stdout,\n format=_LOG_CONSOLE_FORMAT,\n level=_lever,\n enqueue=_LOG_ENQUEUE,\n backtrace=_LOG_BACKTRACE,\n diagnose=_LOG_DIAGNOSE,\n )\n if log_dir:\n _log_dir = Path(log_dir)\n _log_access_file = _log_dir.joinpath(f\"{_LOG_FILE_PREFIX}-access.log\")\n _log_error_file = _log_dir.joinpath(f\"{_LOG_FILE_PREFIX}-error.log\")\n if _LOG_PID:\n _log_access_file = str(_log_access_file).replace(\".log\", f\".{os.getpid()}.log\")\n _log_error_file = str(_log_error_file).replace(\".log\", f\".{os.getpid()}.log\")\n logger.add(\n _log_access_file,\n encoding=\"utf-8\",\n format=_LOG_FILE_FORMAT,\n level=_lever,\n rotation=_LOG_ROTATION,\n retention=_LOG_RETENTION,\n compression=_LOG_COMPRESSION,\n enqueue=_LOG_ENQUEUE,\n backtrace=_LOG_BACKTRACE,\n diagnose=_LOG_DIAGNOSE,\n )\n logger.add(\n _log_error_file,\n encoding=\"utf-8\",\n format=_LOG_FILE_FORMAT,\n level=\"ERROR\",\n rotation=_LOG_ROTATION,\n retention=_LOG_RETENTION,\n compression=_LOG_COMPRESSION,\n enqueue=_LOG_ENQUEUE,\n backtrace=_LOG_BACKTRACE,\n diagnose=_LOG_DIAGNOSE,\n )\n return logger\n",
26
+ "app/initializer/_log.py": "import os\nimport sys\nfrom pathlib import Path\n\nfrom loguru import logger\nfrom loguru._logger import Logger # noqa\n\nfrom app.initializer.context import request_id_ctx_var\n\n_LOG_CONSOLE_FORMAT = \"{time:YYYY-MM-DD HH:mm:ss.SSS} {level} {extra[request_id]} {file}:{line} {message}\"\n_LOG_FILE_FORMAT = \"{time:YYYY-MM-DD HH:mm:ss.SSS} {level} {extra[request_id]} {file}:{line} {message}\"\n_LOG_FILE_PREFIX = \"app\"\n_LOG_ROTATION = \"100 MB\"\n_LOG_RETENTION = \"15 days\"\n_LOG_COMPRESSION = None\n_LOG_ENQUEUE = True\n_LOG_BACKTRACE = False\n_LOG_DIAGNOSE = False\n_LOG_PID = False\n\n\ndef init_logger(\n debug: bool,\n log_dir: str = None,\n) -> Logger:\n logger.remove(None)\n _lever = \"DEBUG\" if debug else \"INFO\"\n\n def _filter(record: dict) -> bool:\n record[\"extra\"][\"request_id\"] = request_id_ctx_var.get()\n return True\n\n logger.add(\n sys.stdout,\n format=_LOG_CONSOLE_FORMAT,\n level=_lever,\n enqueue=_LOG_ENQUEUE,\n backtrace=_LOG_BACKTRACE,\n diagnose=_LOG_DIAGNOSE,\n filter=_filter,\n )\n if log_dir:\n _log_dir = Path(log_dir)\n _log_access_file = _log_dir.joinpath(f\"{_LOG_FILE_PREFIX}-access.log\")\n _log_error_file = _log_dir.joinpath(f\"{_LOG_FILE_PREFIX}-error.log\")\n if _LOG_PID:\n _log_access_file = str(_log_access_file).replace(\".log\", f\".{os.getpid()}.log\")\n _log_error_file = str(_log_error_file).replace(\".log\", f\".{os.getpid()}.log\")\n logger.add(\n _log_access_file,\n encoding=\"utf-8\",\n format=_LOG_FILE_FORMAT,\n level=_lever,\n rotation=_LOG_ROTATION,\n retention=_LOG_RETENTION,\n compression=_LOG_COMPRESSION,\n enqueue=_LOG_ENQUEUE,\n backtrace=_LOG_BACKTRACE,\n diagnose=_LOG_DIAGNOSE,\n )\n logger.add(\n _log_error_file,\n encoding=\"utf-8\",\n format=_LOG_FILE_FORMAT,\n level=\"ERROR\",\n rotation=_LOG_ROTATION,\n retention=_LOG_RETENTION,\n compression=_LOG_COMPRESSION,\n enqueue=_LOG_ENQUEUE,\n backtrace=_LOG_BACKTRACE,\n diagnose=_LOG_DIAGNOSE,\n )\n return logger\n",
26
27
  "app/initializer/_redis.py": "from toollib.rediser import RedisCli\n\n\ndef init_redis_cli(\n host: str,\n port: int,\n db: int,\n password: str = None,\n max_connections: int = None,\n **kwargs,\n) -> RedisCli:\n if not host:\n return RedisCli()\n return RedisCli(\n host=host,\n port=port,\n db=db,\n password=password,\n max_connections=max_connections,\n **kwargs,\n )\n",
27
28
  "app/initializer/_snow.py": "import os\n\nfrom loguru import logger\nfrom toollib.guid import SnowFlake\nfrom toollib.rediser import RedisCli\nfrom toollib.utils import localip\n\n_CACHE_KEY_SNOW_WORKER_ID_INCR = \"config:snow_worker_id_incr\"\n_CACHE_KEY_SNOW_DATACENTER_ID_INCR = \"config:snow_datacenter_id_incr\"\n_CACHE_EXPIRE_SNOW = 120\n\n\ndef init_snow_cli(\n redis_cli: RedisCli,\n datacenter_id: int = None,\n to_str: bool = True,\n) -> SnowFlake: # \u5efa\u8bae\uff1a\u91c7\u7528\u670d\u52a1\u7684\u65b9\u5f0f\u8c03\u7528api\u83b7\u53d6\n if datacenter_id is None:\n datacenter_id = _snow_incr(redis_cli, _CACHE_KEY_SNOW_DATACENTER_ID_INCR, _CACHE_EXPIRE_SNOW)\n if datacenter_id is None:\n local_ip = localip()\n if local_ip:\n ip_parts = list(map(int, local_ip.split('.')))\n ip_int = (ip_parts[0] << 24) + (ip_parts[1] << 16) + (ip_parts[2] << 8) + ip_parts[3]\n datacenter_id = ip_int % 32\n worker_id = _snow_incr(redis_cli, _CACHE_KEY_SNOW_WORKER_ID_INCR, _CACHE_EXPIRE_SNOW)\n if worker_id is None:\n worker_id = os.getpid() % 32\n return SnowFlake(worker_id=worker_id, datacenter_id=datacenter_id, to_str=to_str)\n\n\ndef _snow_incr(redis_cli, cache_key: str, cache_expire: int):\n incr = None\n try:\n with redis_cli.connection() as r:\n resp = r.ping()\n if resp:\n lua_script = \"\"\"\n if redis.call('exists', KEYS[1]) == 1 then\n redis.call('expire', KEYS[1], ARGV[1])\n return redis.call('incr', KEYS[1])\n else\n redis.call('set', KEYS[1], 0)\n redis.call('expire', KEYS[1], ARGV[1])\n return 0\n end\n \"\"\"\n incr = r.eval(lua_script, 1, cache_key, cache_expire)\n except Exception as e:\n logger.warning(f\"snow\u521d\u59cb\u5316id\u5c06\u91c7\u7528\u672c\u5730\u65b9\u5f0f\uff0c\u7531\u4e8e\uff08{e}\uff09\")\n return incr\n",
28
29
  "app/initializer/__init__.py": "\"\"\"\n\u521d\u59cb\u5316\n\"\"\"\nfrom loguru._logger import Logger # noqa\nfrom sqlalchemy.orm import sessionmaker, scoped_session\nfrom toollib.guid import SnowFlake\nfrom toollib.rediser import RedisCli\nfrom toollib.utils import Singleton\n\nfrom app.initializer._conf import init_config\nfrom app.initializer._db import init_db_session, init_db_async_session\nfrom app.initializer._log import init_logger\nfrom app.initializer._redis import init_redis_cli\nfrom app.initializer._snow import init_snow_cli\n\n\nclass G(metaclass=Singleton):\n \"\"\"\n \u5168\u5c40\u53d8\u91cf\n \"\"\"\n config = None\n logger: Logger = None\n redis_cli: RedisCli = None\n snow_cli: SnowFlake = None\n db_session: scoped_session = None\n db_async_session: sessionmaker = None\n\n def __getattribute__(self, name):\n try:\n value = super().__getattribute__(name)\n except AttributeError:\n value = None\n if value is None:\n getter_name = f\"_get_{name}\"\n getter_method = getattr(self.__class__, getter_name, None)\n if callable(getter_method):\n value = getter_method()\n setattr(self, name, value)\n return value\n\n @classmethod\n def _get_config(cls):\n if not cls.config:\n cls.config = init_config()\n return cls.config\n\n @classmethod\n def _get_logger(cls):\n if not cls.logger:\n cls.logger = init_logger(\n debug=cls.config.debug,\n log_dir=cls.config.log_dir,\n )\n return cls.logger\n\n @classmethod\n def _get_redis_cli(cls):\n if not cls.redis_cli:\n cls.redis_cli = init_redis_cli(\n host=cls.config.redis_host,\n port=cls.config.redis_port,\n db=cls.config.redis_db,\n password=cls.config.redis_password,\n max_connections=cls.config.redis_max_connections,\n )\n return cls.redis_cli\n\n @classmethod\n def _get_snow_cli(cls):\n if not cls.snow_cli:\n cls.snow_cli = init_snow_cli(\n redis_cli=cls.redis_cli,\n datacenter_id=cls.config.snow_datacenter_id,\n )\n return cls.snow_cli\n\n @classmethod\n def _get_db_session(cls):\n if not cls.db_session:\n cls.db_session = init_db_session(\n db_url=cls.config.db_url,\n db_echo=cls.config.debug,\n )\n return cls.db_session\n\n @classmethod\n def _get_db_async_session(cls):\n if not cls.db_async_session:\n cls.db_async_session = init_db_async_session(\n db_url=cls.config.db_async_url,\n db_echo=cls.config.debug,\n )\n return cls.db_async_session\n\n @classmethod\n def setup(cls):\n \"\"\"\n \u521d\u59cb\u5316\n \"\"\"\n cls._get_config()\n cls._get_logger()\n cls._get_redis_cli()\n cls._get_snow_cli()\n # cls._get_db_session()\n cls._get_db_async_session()\n\n\ng = G()\n",
29
30
  "app/middleware/auth.py": "from fastapi import Depends\nfrom fastapi.security import HTTPBearer, HTTPAuthorizationCredentials\nfrom typing import Optional\n\nfrom fastapi.security.utils import get_authorization_scheme_param\nfrom pydantic import BaseModel\nfrom starlette.requests import Request\n\nfrom app.api.exception import CustomException\nfrom app.api.status import Status\nfrom app.dschema.user import User\nfrom app.initializer import g\nfrom app.utils import db_async\nfrom app.utils.auth import verify_jwt\n\n\nclass JWTUser(BaseModel):\n # \u5b57\u6bb5\u4e0eUser\u5bf9\u9f50\n id: str = None\n phone: str = None\n name: str = None\n age: int = None\n gender: int = None\n\n\nclass JWTAuthorizationCredentials(HTTPAuthorizationCredentials):\n user: JWTUser\n\n\nclass JWTBearer(HTTPBearer):\n\n async def __call__(\n self, request: Request\n ) -> Optional[JWTAuthorizationCredentials]:\n authorization = request.headers.get(\"Authorization\")\n scheme, credentials = get_authorization_scheme_param(authorization)\n if not (authorization and scheme and credentials):\n if self.auto_error:\n raise CustomException(\n msg=\"Not authenticated\",\n status=Status.UNAUTHORIZED_ERROR,\n )\n else:\n return None\n if scheme.lower() != \"bearer\":\n if self.auto_error:\n raise CustomException(\n msg=\"Invalid authentication credentials\",\n status=Status.UNAUTHORIZED_ERROR,\n )\n else:\n return None\n user = await self.verify_credentials(credentials)\n return JWTAuthorizationCredentials(scheme=scheme, credentials=credentials, user=user)\n\n async def verify_credentials(self, credentials: str) -> JWTUser:\n playload = await self._verify_jwt(credentials)\n if playload is None:\n raise CustomException(status=Status.UNAUTHORIZED_ERROR)\n # \u5efa\u8bae\uff1ajwt_key\u8fdb\u884credis\u7f13\u5b58\n async with g.db_async_session() as session:\n data = await db_async.query_one(\n session=session,\n model=User,\n fields=[\"jwt_key\"],\n filter_by={\"id\": playload.get(\"id\")}\n )\n if not data:\n raise CustomException(status=Status.UNAUTHORIZED_ERROR)\n # <<< \u5efa\u8bae\n await self._verify_jwt(credentials, jwt_key=data.get(\"jwt_key\"))\n return JWTUser(\n id=playload.get(\"id\"),\n phone=playload.get(\"phone\"),\n name=playload.get(\"name\"),\n age=playload.get(\"age\"),\n gender=playload.get(\"gender\"),\n )\n\n @staticmethod\n async def _verify_jwt(token: str, jwt_key: str = None) -> dict:\n try:\n return verify_jwt(token=token, jwt_key=jwt_key)\n except Exception as e:\n raise CustomException(status=Status.UNAUTHORIZED_ERROR, msg=str(e))\n\n\ndef get_current_user(\n credentials: Optional[JWTAuthorizationCredentials] = Depends(JWTBearer(auto_error=True))\n) -> JWTUser:\n if not credentials:\n return JWTUser()\n return credentials.user\n",
30
31
  "app/middleware/cors.py": "from fastapi.middleware.cors import CORSMiddleware\n\n\nclass Cors:\n middleware_class = CORSMiddleware\n allow_origins = [\n \"http://localhost:8000\",\n # \u53ef\u8ffd\u52a0\u6240\u9700\n ]\n allow_credentials = True\n allow_methods = [\"*\"]\n allow_headers = [\"*\"]\n",
31
- "app/middleware/exception.py": "import traceback\n\nfrom fastapi.exceptions import RequestValidationError\nfrom starlette.exceptions import HTTPException\nfrom starlette.requests import Request\nfrom starlette.responses import JSONResponse\n\nfrom app.api.exception import CustomException\nfrom app.api.response import Response\nfrom app.api.status import Status\nfrom app.initializer import g\n\n\nclass ExceptionHandler:\n\n @staticmethod\n async def custom_exception_handler(\n request: Request,\n exc: CustomException,\n is_traceback: bool = False,\n ) -> JSONResponse:\n lmsg = f'- \"{request.method} {request.url.path}\" {exc.code} {exc.msg}'\n if is_traceback:\n lmsg = traceback.format_exc()\n g.logger.error(lmsg)\n return Response.failure(msg=exc.msg, code=exc.code, data=exc.data)\n\n @staticmethod\n async def http_exception_handler(\n request: Request,\n exc: HTTPException,\n is_traceback: bool = False,\n ) -> JSONResponse:\n lmsg = f'- \"{request.method} {request.url.path}\" {exc.status_code} {exc.detail}'\n if is_traceback:\n lmsg = traceback.format_exc()\n g.logger.error(lmsg)\n return Response.failure(msg=exc.detail, code=exc.status_code)\n\n @staticmethod\n async def validation_exception_handler(\n request: Request,\n exc: RequestValidationError,\n is_display_all: bool = False,\n is_traceback: bool = False,\n ) -> JSONResponse:\n if is_display_all:\n msg = \", \".join([f\"'{item['loc'][1] if len(item['loc']) > 1 else item['loc'][0]}' {item['msg'].lower()}\" for item in exc.errors()]) # noqa: E501\n else:\n _first_error = exc.errors()[0]\n msg = f\"'{_first_error['loc'][1] if len(_first_error['loc']) > 1 else _first_error['loc'][0]}' {_first_error['msg'].lower()}\" # noqa: E501\n lmsg = f'- \"{request.method} {request.url.path}\" {Status.PARAMS_ERROR.code} {msg}'\n if is_traceback:\n lmsg = traceback.format_exc()\n g.logger.error(lmsg)\n return Response.failure(\n msg=msg,\n status=Status.PARAMS_ERROR,\n )\n",
32
- "app/middleware/__init__.py": "\"\"\"\n\u4e2d\u95f4\u4ef6\n\"\"\"\nfrom fastapi import FastAPI\nfrom fastapi.exceptions import RequestValidationError\nfrom starlette.exceptions import HTTPException\n\nfrom app.api.exception import CustomException\nfrom app.middleware.cors import Cors\nfrom app.middleware.exception import ExceptionHandler\n\n\ndef register_middlewares(app: FastAPI):\n \"\"\"\u6ce8\u518c\u4e2d\u95f4\u4ef6\"\"\"\n app.add_middleware(\n middleware_class=Cors.middleware_class,\n allow_origins=Cors.allow_origins,\n allow_credentials=Cors.allow_credentials,\n allow_methods=Cors.allow_methods,\n allow_headers=Cors.allow_headers,\n )\n\n app.add_exception_handler(CustomException, ExceptionHandler.custom_exception_handler) # type: ignore\n app.add_exception_handler(HTTPException, ExceptionHandler.http_exception_handler) # type: ignore\n app.add_exception_handler(RequestValidationError, ExceptionHandler.validation_exception_handler) # type: ignore\n",
32
+ "app/middleware/exception.py": "import traceback\n\nfrom fastapi.exceptions import RequestValidationError\nfrom starlette.exceptions import HTTPException\nfrom starlette.requests import Request\nfrom starlette.responses import JSONResponse\n\nfrom app.api.exception import CustomException\nfrom app.api.response import Response\nfrom app.api.status import Status\nfrom app.initializer import g\n\n\nclass ExceptionHandler:\n\n @staticmethod\n async def custom_exception_handler(\n request: Request,\n exc: CustomException,\n is_traceback: bool = False,\n ) -> JSONResponse:\n lmsg = f'- \"{request.method} {request.url.path}\" {exc.code} {exc.msg}'\n if is_traceback:\n lmsg = traceback.format_exc()\n g.logger.error(lmsg)\n return Response.failure(\n msg=exc.msg,\n code=exc.code,\n data=exc.data,\n request=request,\n )\n\n @staticmethod\n async def http_exception_handler(\n request: Request,\n exc: HTTPException,\n is_traceback: bool = False,\n ) -> JSONResponse:\n lmsg = f'- \"{request.method} {request.url.path}\" {exc.status_code} {exc.detail}'\n if is_traceback:\n lmsg = traceback.format_exc()\n g.logger.error(lmsg)\n return Response.failure(\n msg=exc.detail,\n code=exc.status_code,\n request=request,\n )\n\n @staticmethod\n async def validation_exception_handler(\n request: Request,\n exc: RequestValidationError,\n is_display_all: bool = False,\n is_traceback: bool = False,\n ) -> JSONResponse:\n if is_display_all:\n msg = \", \".join([f\"'{item['loc'][1] if len(item['loc']) > 1 else item['loc'][0]}' {item['msg'].lower()}\" for item in exc.errors()]) # noqa: E501\n else:\n _first_error = exc.errors()[0]\n msg = f\"'{_first_error['loc'][1] if len(_first_error['loc']) > 1 else _first_error['loc'][0]}' {_first_error['msg'].lower()}\" # noqa: E501\n lmsg = f'- \"{request.method} {request.url.path}\" {Status.PARAMS_ERROR.code} {msg}'\n if is_traceback:\n lmsg = traceback.format_exc()\n g.logger.error(lmsg)\n return Response.failure(\n msg=msg,\n status=Status.PARAMS_ERROR,\n request=request,\n )\n",
33
+ "app/middleware/headers.py": "import uuid\nfrom starlette.middleware.base import BaseHTTPMiddleware\nfrom starlette.requests import Request\n\nfrom app.initializer.context import request_id_ctx_var\n\n\nclass HeadersMiddleware(BaseHTTPMiddleware):\n \"\"\"HTTP \u5934\u5904\u7406\u4e2d\u95f4\u4ef6\"\"\"\n _HEADERS = {\n # \u53ef\u6dfb\u52a0\u76f8\u5173\u5934\n }\n\n async def dispatch(self, request: Request, call_next):\n request_id = self._get_or_create_request_id(request)\n request.state.request_id = request_id\n ctx_token = request_id_ctx_var.set(request_id)\n try:\n response = await call_next(request)\n response.headers[\"X-Request-ID\"] = request_id\n for key, value in self._HEADERS.items():\n if key not in response.headers:\n response.headers[key] = value\n return response\n finally:\n request_id_ctx_var.reset(ctx_token)\n\n @staticmethod\n def _get_or_create_request_id(request: Request) -> str:\n request_id = request.headers.get(\"X-Request-ID\")\n if not request_id:\n request_id = f\"req-{uuid.uuid4()}\"\n return request_id\n",
34
+ "app/middleware/__init__.py": "\"\"\"\n\u4e2d\u95f4\u4ef6\n\"\"\"\nfrom fastapi import FastAPI\nfrom fastapi.exceptions import RequestValidationError\nfrom starlette.exceptions import HTTPException\n\nfrom app.api.exception import CustomException\nfrom app.middleware.cors import Cors\nfrom app.middleware.exception import ExceptionHandler\nfrom app.middleware.headers import HeadersMiddleware\n\n\ndef register_middlewares(app: FastAPI):\n \"\"\"\u6ce8\u518c\u4e2d\u95f4\u4ef6\"\"\"\n app.add_middleware(HeadersMiddleware) # type: ignore\n app.add_middleware(\n middleware_class=Cors.middleware_class,\n allow_origins=Cors.allow_origins,\n allow_credentials=Cors.allow_credentials,\n allow_methods=Cors.allow_methods,\n allow_headers=Cors.allow_headers,\n )\n app.add_exception_handler(CustomException, ExceptionHandler.custom_exception_handler) # type: ignore\n app.add_exception_handler(HTTPException, ExceptionHandler.http_exception_handler) # type: ignore\n app.add_exception_handler(RequestValidationError, ExceptionHandler.validation_exception_handler) # type: ignore\n",
33
35
  "app/router/__init__.py": "\"\"\"\n\u8def\u7531\n\"\"\"\nimport importlib\nimport sys\nfrom pathlib import Path\n\nfrom fastapi import FastAPI\nfrom loguru import logger\n\nfrom app import APP_DIR\n\n_API_MOD_DIR = APP_DIR.joinpath(\"api\")\n_API_MOD_BASE = \"app.api\"\n\n\ndef register_routers(\n app: FastAPI,\n mod_dir: Path = _API_MOD_DIR,\n mod_base: str = _API_MOD_BASE,\n prefix: str = \"\",\n obj_suffix: str = \"_router\",\n depth: int = 0,\n max_depth: int = 2\n):\n \"\"\"\n \u6ce8\u518c\u8def\u7531\n \u8981\u6c42\uff1a\n \u8def\u7531\u6a21\u5757\uff1a\u975e'__'\u5f00\u5934\u7684\u6a21\u5757\n \u8def\u7531\u5bf9\u8c61\uff1a{\u6a21\u5757\u540d\u79f0}{\u8def\u7531\u5bf9\u8c61\u540e\u7f00}\n :param app: FastAPI\u5e94\u7528\n :param mod_dir: api\u6a21\u5757\u76ee\u5f55\n :param mod_base: api\u6a21\u5757\u57fa\u7840\n :param prefix: url\u524d\u7f00\n :param obj_suffix: \u8def\u7531\u5bf9\u8c61\u540e\u7f00\n :param depth: \u5f53\u524d\u9012\u5f52\u6df1\u5ea6\n :param max_depth: \u6700\u5927\u9012\u5f52\u6df1\u5ea6\n \"\"\"\n if depth > max_depth:\n return\n for item in mod_dir.iterdir():\n if item.name.startswith(\"__\") or item.name == \"__pycache__\":\n continue\n if item.is_dir():\n new_mod_dir = item\n new_mod_base = f\"{mod_base}.{item.name}\"\n new_prefix = prefix\n try:\n mod = importlib.import_module(new_mod_base)\n _prefix = getattr(mod, \"_prefix\", None)\n if _prefix:\n new_prefix = f\"{new_prefix}/{_prefix}\"\n except ImportError:\n logger.error(f\"Register router failed to import module: {new_mod_base}\")\n continue\n register_routers(\n app=app,\n mod_dir=new_mod_dir,\n mod_base=new_mod_base,\n prefix=new_prefix,\n obj_suffix=obj_suffix,\n depth=depth + 1,\n max_depth=max_depth\n )\n elif item.is_file() and item.suffix == \".py\" and depth > 0:\n mod_name = item.stem\n final_mod = f\"{mod_base}.{mod_name}\"\n try:\n mod = importlib.import_module(final_mod)\n if not getattr(mod, \"_active\", True):\n logger.info(f\"Register router skipping inactive module: {final_mod}\")\n sys.modules.pop(final_mod)\n continue\n router_name = f\"{mod_name}{obj_suffix}\"\n if router := getattr(mod, router_name, None):\n tag = getattr(mod, \"_tag\", None)\n if not tag:\n tag = item.parent.stem if depth > 1 else mod_name\n app.include_router(\n router=router,\n prefix=prefix.replace(\"//\", \"/\").rstrip(\"/\"),\n tags=[tag]\n )\n except ImportError:\n logger.error(f\"Register router failed to import module: {final_mod}\")\n continue\n",
34
36
  "app/tests/__init__.py": "\"\"\"\n\u6d4b\u8bd5\n\"\"\"\n",
35
37
  "app/utils/auth.py": "import secrets\nfrom datetime import datetime, timedelta\n\nimport bcrypt\nimport jwt\n\n_ALGORITHM = \"HS256\"\n\n\ndef gen_jwt(payload: dict, jwt_key: str, exp_minutes: int = 24 * 60 * 30):\n payload.update({\"exp\": datetime.utcnow() + timedelta(minutes=exp_minutes)})\n encoded_jwt = jwt.encode(payload=payload, key=jwt_key, algorithm=_ALGORITHM)\n return encoded_jwt\n\n\ndef verify_jwt(token: str, jwt_key: str = None) -> dict:\n if not jwt_key:\n return jwt.decode(jwt=token, options={\"verify_signature\": False})\n return jwt.decode(jwt=token, key=jwt_key, algorithms=[_ALGORITHM])\n\n\ndef gen_jwt_key():\n return secrets.token_hex(16)\n\n\ndef hash_password(password: str) -> str:\n salt = bcrypt.gensalt()\n hashed_password = bcrypt.hashpw(password.encode('utf-8'), salt)\n return hashed_password.decode('utf-8')\n\n\ndef verify_password(password: str, hashed_password: str) -> bool:\n return bcrypt.checkpw(password.encode('utf-8'), hashed_password.encode('utf-8'))\n",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastapi-scaff
3
- Version: 0.0.4
3
+ Version: 0.0.5
4
4
  Summary: This is a fastapi scaff.
5
5
  Author-email: axiner <atpuxiner@163.com>
6
6
  Project-URL: Homepage, https://github.com/atpuxiner/fastapi-scaff
@@ -0,0 +1,10 @@
1
+ fastapi_scaff/__init__.py,sha256=3nQa61FfMHfbGg7lKvtdIBIxjaK2NmTPzuWhKCwv0bQ,120
2
+ fastapi_scaff/__main__.py,sha256=TT7ch-i1vZ7u3PC5tXwsiXymxBzdeFgqhSQzRJHJtNc,12134
3
+ fastapi_scaff/_api_tpl.json,sha256=aP4CJs2hvpF5sK30w48lIV73gRespYvv3LdGd_mCCWQ,5373
4
+ fastapi_scaff/_project_tpl.json,sha256=dKbtxw9YyNyQXJIZ3KOV-Oy-2GJ-ZNVGjhX6eoyTumQ,71306
5
+ fastapi_scaff-0.0.5.dist-info/licenses/LICENSE,sha256=A5H6q7zd1QrL3iVs1KLsBOG0ImV-t9PpPspM4x-4Ea8,1069
6
+ fastapi_scaff-0.0.5.dist-info/METADATA,sha256=T264Uz4q7vErFYlBp-tpU3VA9FggUa_KnLn3YN6q42o,3351
7
+ fastapi_scaff-0.0.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
8
+ fastapi_scaff-0.0.5.dist-info/entry_points.txt,sha256=kzs28nmpRWVCmWmZav3X7u7YOIOEir3sCkLnvQKTJbY,62
9
+ fastapi_scaff-0.0.5.dist-info/top_level.txt,sha256=LeyfUxMRhdbRHcYoH37ftfdspyZ8V3Uut2YBaTCzq2k,14
10
+ fastapi_scaff-0.0.5.dist-info/RECORD,,
@@ -1,10 +0,0 @@
1
- fastapi_scaff/__init__.py,sha256=nQ9FyTsZvuPlKqGzJr-2I-lXtHIrFtIlLhTISzvmyzc,120
2
- fastapi_scaff/__main__.py,sha256=TT7ch-i1vZ7u3PC5tXwsiXymxBzdeFgqhSQzRJHJtNc,12134
3
- fastapi_scaff/_api_tpl.json,sha256=8OEB1-V0gdumMxC0-PqoD1a5VwWix8wLqNeI0_ozYwY,5157
4
- fastapi_scaff/_project_tpl.json,sha256=X3WdVnFuXp4UQlMyikuKnHJghgMKls1ZRKVQV5O-kWc,68657
5
- fastapi_scaff-0.0.4.dist-info/licenses/LICENSE,sha256=A5H6q7zd1QrL3iVs1KLsBOG0ImV-t9PpPspM4x-4Ea8,1069
6
- fastapi_scaff-0.0.4.dist-info/METADATA,sha256=1ijjGOkFVL1kd9Mn_OT_FK77XU_O-vGUEQVo-9yeZMo,3351
7
- fastapi_scaff-0.0.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
8
- fastapi_scaff-0.0.4.dist-info/entry_points.txt,sha256=kzs28nmpRWVCmWmZav3X7u7YOIOEir3sCkLnvQKTJbY,62
9
- fastapi_scaff-0.0.4.dist-info/top_level.txt,sha256=LeyfUxMRhdbRHcYoH37ftfdspyZ8V3Uut2YBaTCzq2k,14
10
- fastapi_scaff-0.0.4.dist-info/RECORD,,