fastapi-scaff 0.3.1__py3-none-any.whl → 0.3.2__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 +1 -1
- fastapi_scaff/__main__.py +25 -30
- fastapi_scaff/_api_tpl.json +3 -3
- fastapi_scaff/_project_tpl.json +8 -7
- {fastapi_scaff-0.3.1.dist-info → fastapi_scaff-0.3.2.dist-info}/METADATA +1 -1
- fastapi_scaff-0.3.2.dist-info/RECORD +10 -0
- fastapi_scaff-0.3.1.dist-info/RECORD +0 -10
- {fastapi_scaff-0.3.1.dist-info → fastapi_scaff-0.3.2.dist-info}/WHEEL +0 -0
- {fastapi_scaff-0.3.1.dist-info → fastapi_scaff-0.3.2.dist-info}/entry_points.txt +0 -0
- {fastapi_scaff-0.3.1.dist-info → fastapi_scaff-0.3.2.dist-info}/licenses/LICENSE +0 -0
- {fastapi_scaff-0.3.1.dist-info → fastapi_scaff-0.3.2.dist-info}/top_level.txt +0 -0
fastapi_scaff/__init__.py
CHANGED
fastapi_scaff/__main__.py
CHANGED
|
@@ -173,6 +173,7 @@ class CMD:
|
|
|
173
173
|
"app/services/user.py",
|
|
174
174
|
"app_celery/",
|
|
175
175
|
"deploy/",
|
|
176
|
+
"docs/",
|
|
176
177
|
"tests/",
|
|
177
178
|
"runcbeat.py",
|
|
178
179
|
"runcworker.py",
|
|
@@ -188,38 +189,32 @@ from toollib.guid import SnowFlake
|
|
|
188
189
|
from toollib.rediser import RedisCli""", "").replace("""
|
|
189
190
|
from app.initializer._redis import init_redis_cli
|
|
190
191
|
from app.initializer._snow import init_snow_cli""", "").replace("""
|
|
191
|
-
|
|
192
|
-
|
|
192
|
+
'redis_cli',
|
|
193
|
+
'snow_cli',""", "").replace("""
|
|
194
|
+
@cached_property
|
|
195
|
+
def redis_cli(self) -> RedisCli:
|
|
196
|
+
return init_redis_cli(
|
|
197
|
+
host=self.config.redis_host,
|
|
198
|
+
port=self.config.redis_port,
|
|
199
|
+
db=self.config.redis_db,
|
|
200
|
+
password=self.config.redis_password,
|
|
201
|
+
max_connections=self.config.redis_max_connections,
|
|
202
|
+
)
|
|
193
203
|
|
|
194
|
-
@
|
|
195
|
-
def
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
password=cls.config.redis_password,
|
|
202
|
-
max_connections=cls.config.redis_max_connections,
|
|
203
|
-
)
|
|
204
|
-
return cls.redis_cli
|
|
205
|
-
|
|
206
|
-
@classmethod
|
|
207
|
-
def _get_snow_cli(cls):
|
|
208
|
-
if not cls.snow_cli:
|
|
209
|
-
cls.snow_cli = init_snow_cli(
|
|
210
|
-
redis_cli=cls.redis_cli,
|
|
211
|
-
datacenter_id=cls.config.snow_datacenter_id,
|
|
212
|
-
)
|
|
213
|
-
return cls.snow_cli""", "").replace("""
|
|
214
|
-
cls._get_redis_cli()
|
|
215
|
-
cls._get_snow_cli()""", "")
|
|
204
|
+
@cached_property
|
|
205
|
+
def snow_cli(self) -> SnowFlake:
|
|
206
|
+
return init_snow_cli(
|
|
207
|
+
redis_cli=self.redis_cli,
|
|
208
|
+
datacenter_id=self.config.snow_datacenter_id,
|
|
209
|
+
)
|
|
210
|
+
""", "")
|
|
216
211
|
elif k == "app/initializer/_conf.py":
|
|
217
|
-
v = v.replace("""
|
|
218
|
-
redis_host: str = None
|
|
212
|
+
v = v.replace("""redis_host: str = None
|
|
219
213
|
redis_port: int = None
|
|
220
214
|
redis_db: int = None
|
|
221
215
|
redis_password: str = None
|
|
222
|
-
redis_max_connections: int = None
|
|
216
|
+
redis_max_connections: int = None
|
|
217
|
+
""", "")
|
|
223
218
|
elif k == "app/initializer/_db.py":
|
|
224
219
|
v = v.replace("""
|
|
225
220
|
_MODELS_MOD_DIR = APP_DIR.joinpath("models")
|
|
@@ -262,8 +257,7 @@ redis_port:
|
|
|
262
257
|
redis_db:
|
|
263
258
|
redis_password:
|
|
264
259
|
redis_max_connections:
|
|
265
|
-
""", "").replace("""
|
|
266
|
-
# #
|
|
260
|
+
""", "").replace("""# #
|
|
267
261
|
celery_broker_url: redis://:<password>@<host>:<port>/<db>
|
|
268
262
|
celery_backend_url: redis://:<password>@<host>:<port>/<db>
|
|
269
263
|
celery_timezone: Asia/Shanghai
|
|
@@ -278,7 +272,8 @@ celery_worker_concurrency: 8
|
|
|
278
272
|
celery_worker_prefetch_multiplier: 2
|
|
279
273
|
celery_worker_max_tasks_per_child: 100
|
|
280
274
|
celery_broker_connection_retry_on_startup: true
|
|
281
|
-
celery_task_reject_on_worker_lost: true
|
|
275
|
+
celery_task_reject_on_worker_lost: true
|
|
276
|
+
""", "")
|
|
282
277
|
elif k == "requirements.txt":
|
|
283
278
|
v = v.replace("""
|
|
284
279
|
redis==6.4.0""", "").replace("""
|
fastapi_scaff/_api_tpl.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
|
-
"asm_app_api.py": "import traceback\n\nfrom fastapi import APIRouter\nfrom starlette.requests import Request\n\nfrom app.api.responses import Responses, response_docs\nfrom app.
|
|
2
|
+
"asm_app_api.py": "import traceback\n\nfrom fastapi import APIRouter\nfrom starlette.requests import Request\n\nfrom app.api.responses import Responses, response_docs\nfrom app.api.status import Status\nfrom app.initializer import g\nfrom app.services.tpl import (\n TplDetailSvc,\n)\n\nrouter = APIRouter()\n\n\n@router.get(\n path=\"/tpl/{tpl_id}\",\n summary=\"tplDetail\",\n responses=response_docs(\n model=TplDetailSvc,\n ),\n)\nasync def detail(\n request: Request,\n tpl_id: str,\n # TODO: \u8ba4\u8bc1\n):\n try:\n tpl_svc = TplDetailSvc(id=tpl_id)\n data = await tpl_svc.detail()\n if not data:\n return Responses.failure(status=Status.RECORD_NOT_EXIST_ERROR, request=request)\n except Exception as e:\n g.logger.error(traceback.format_exc())\n return Responses.failure(msg=\"tplDetail\u5931\u8d25\", error=e, request=request)\n return Responses.success(data=data, request=request)\n",
|
|
3
3
|
"asm_app_models.py": "from sqlalchemy import Column, String\n\nfrom app.models import DeclBase\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",
|
|
4
4
|
"asm_app_schemas.py": "from pydantic import BaseModel, Field\n\nfrom app.schemas import filter_fields\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
5
|
"asm_app_services.py": "from app.schemas.tpl import (\n TplDetail,\n)\n\n\nclass TplDetailSvc(TplDetail):\n model_config = {\n \"json_schema_extra\": {\n \"title\": \"TplDetail\"\n }\n }\n\n async def detail(self):\n # TODO: \u4e1a\u52a1\u903b\u8f91\n pass\n",
|
|
6
|
-
"as_app_api.py": "import traceback\n\nfrom fastapi import APIRouter\nfrom starlette.requests import Request\n\nfrom app.api.responses import Responses, response_docs\nfrom app.
|
|
6
|
+
"as_app_api.py": "import traceback\n\nfrom fastapi import APIRouter\nfrom starlette.requests import Request\n\nfrom app.api.responses import Responses, response_docs\nfrom app.api.status import Status\nfrom app.initializer import g\nfrom app.services.tpl import (\n TplDetailSvc,\n)\n\nrouter = APIRouter()\n\n\n@router.get(\n path=\"/tpl/{tpl_id}\",\n summary=\"tplDetail\",\n responses=response_docs(\n model=TplDetailSvc,\n ),\n)\nasync def detail(\n request: Request,\n tpl_id: str,\n # TODO: \u8ba4\u8bc1\n):\n try:\n tpl_svc = TplDetailSvc(id=tpl_id)\n data = await tpl_svc.detail()\n if not data:\n return Responses.failure(status=Status.RECORD_NOT_EXIST_ERROR, request=request)\n except Exception as e:\n g.logger.error(traceback.format_exc())\n return Responses.failure(msg=\"tplDetail\u5931\u8d25\", error=e, request=request)\n return Responses.success(data=data, request=request)\n",
|
|
7
7
|
"as_app_schemas.py": "from pydantic import BaseModel, Field\n\nfrom app.schemas import filter_fields\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",
|
|
8
8
|
"as_app_services.py": "from app.schemas.tpl import (\n TplDetail,\n)\n\n\nclass TplDetailSvc(TplDetail):\n model_config = {\n \"json_schema_extra\": {\n \"title\": \"TplDetail\"\n }\n }\n\n async def detail(self):\n # TODO: \u4e1a\u52a1\u903b\u8f91\n pass\n",
|
|
9
9
|
"a_app_api.py": "import traceback\n\nfrom fastapi import APIRouter\nfrom starlette.requests import Request\n\nfrom app.api.responses import Responses, response_docs\nfrom app.api.status import Status\nfrom app.initializer import g\n\nrouter = APIRouter()\n\n\n@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 # TODO: \u8ba4\u8bc1\n):\n try:\n data = {} # TODO: \u6570\u636e\n if not data:\n return Responses.failure(status=Status.RECORD_NOT_EXIST_ERROR, request=request)\n except Exception as e:\n g.logger.error(traceback.format_exc())\n return Responses.failure(msg=\"tplDetail\u5931\u8d25\", error=e, request=request)\n return Responses.success(data=data, request=request)\n",
|
|
10
|
-
"light_app_api.py": "import traceback\n\nfrom fastapi import APIRouter\nfrom starlette.requests import Request\n\nfrom app.api.responses import Responses, response_docs\nfrom app.
|
|
10
|
+
"light_app_api.py": "import traceback\n\nfrom fastapi import APIRouter\nfrom starlette.requests import Request\n\nfrom app.api.responses import Responses, response_docs\nfrom app.api.status import Status\nfrom app.initializer import g\nfrom app.services.tpl import (\n TplDetailSvc,\n)\n\nrouter = APIRouter()\n\n\n@router.get(\n path=\"/tpl/{tpl_id}\",\n summary=\"tplDetail\",\n responses=response_docs(\n model=TplDetailSvc,\n ),\n)\nasync def detail(\n request: Request,\n tpl_id: str,\n # TODO: \u8ba4\u8bc1\n):\n try:\n tpl_svc = TplDetailSvc(id=tpl_id)\n data = await tpl_svc.detail()\n if not data:\n return Responses.failure(status=Status.RECORD_NOT_EXIST_ERROR, request=request)\n except Exception as e:\n g.logger.error(traceback.format_exc())\n return Responses.failure(msg=\"tplDetail\u5931\u8d25\", error=e, request=request)\n return Responses.success(data=data, request=request)\n",
|
|
11
11
|
"light_app_services.py": "from pydantic import BaseModel\n\n\nclass TplDetailSvc(BaseModel):\n model_config = {\n \"json_schema_extra\": {\n \"title\": \"TplDetail\"\n }\n }\n\n async def detail(self):\n # TODO: \u4e1a\u52a1\u903b\u8f91\n pass\n",
|
|
12
12
|
"only_app_api.py": "import traceback\n\nfrom fastapi import APIRouter\nfrom starlette.requests import Request\n\nfrom app.api.responses import Responses, response_docs\nfrom app.api.status import Status\nfrom app.initializer import g\n\nrouter = APIRouter()\n\n\n@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 # TODO: \u8ba4\u8bc1\n):\n try:\n data = {} # TODO: \u6570\u636e\n if not data:\n return Responses.failure(status=Status.RECORD_NOT_EXIST_ERROR, request=request)\n except Exception as e:\n g.logger.error(traceback.format_exc())\n return Responses.failure(msg=\"tplDetail\u5931\u8d25\", error=e, request=request)\n return Responses.success(data=data, request=request)\n",
|
|
13
13
|
"only_app_models.py": "from sqlalchemy import Column, String\n\nfrom app.models import DeclBase\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",
|
fastapi_scaff/_project_tpl.json
CHANGED
|
@@ -15,28 +15,29 @@
|
|
|
15
15
|
"app/api/default/aping.py": "from fastapi import APIRouter\n\nfrom app_celery.producer.publisher import publish\n\nrouter = APIRouter()\n\n\n@router.get(\n path=\"/aping\",\n summary=\"aping\",\n)\ndef ping():\n task_id = publish(\"ping\")\n return f\"pong > {task_id}\"\n",
|
|
16
16
|
"app/api/default/ping.py": "from fastapi import APIRouter\n\nrouter = APIRouter()\n\n\n@router.get(\n path=\"/ping\",\n summary=\"ping\",\n)\ndef ping():\n return \"pong\"\n",
|
|
17
17
|
"app/api/default/__init__.py": "\"\"\"\napi-default\n\"\"\"\n\n_prefix = \"/api\"\n",
|
|
18
|
-
"app/api/v1/user.py": "import traceback\n\nfrom fastapi import APIRouter, Depends\nfrom starlette.requests import Request\n\nfrom app.api.responses import Responses, response_docs\nfrom app.api.status import Status\nfrom app.services.user import (\n UserDetailSvc,\n UserListSvc,\n UserCreateSvc,\n UserUpdateSvc,\n UserDeleteSvc,\n UserLoginSvc,\n UserTokenSvc,\n)\
|
|
18
|
+
"app/api/v1/user.py": "import traceback\n\nfrom fastapi import APIRouter, Depends\nfrom starlette.requests import Request\n\nfrom app.api.responses import Responses, response_docs\nfrom app.api.status import Status\nfrom app.initializer import g\nfrom app.initializer.dependencies import JWTUser, get_current_user\nfrom app.services.user import (\n UserDetailSvc,\n UserListSvc,\n UserCreateSvc,\n UserUpdateSvc,\n UserDeleteSvc,\n UserLoginSvc,\n UserTokenSvc,\n)\n\nrouter = 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\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@router.get(\n path=\"/user/{user_id}\",\n summary=\"userDetail\",\n responses=response_docs(\n model=UserDetailSvc,\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_svc = UserDetailSvc(id=user_id)\n data = await user_svc.detail()\n if not data:\n return Responses.failure(status=Status.RECORD_NOT_EXIST_ERROR)\n except Exception as e:\n g.logger.error(traceback.format_exc())\n return Responses.failure(msg=\"userDetail\u5931\u8d25\", error=e, request=request)\n return Responses.success(data=data, request=request)\n\n\n@router.get(\n path=\"/user\",\n summary=\"userList\",\n responses=response_docs(\n model=UserListSvc,\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_svc = UserListSvc(page=page, size=size)\n data, total = await user_svc.lst()\n except Exception as e:\n g.logger.error(traceback.format_exc())\n return Responses.failure(msg=\"userList\u5931\u8d25\", error=e, request=request)\n return Responses.success(data={\"items\": data, \"total\": total}, request=request)\n\n\n@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_svc: UserCreateSvc,\n):\n try:\n user_id = await user_svc.create()\n if not user_id:\n return Responses.failure(status=Status.RECORD_EXISTS_ERROR, request=request)\n except Exception as e:\n g.logger.error(traceback.format_exc())\n return Responses.failure(msg=\"userCreate\u5931\u8d25\", error=e, request=request)\n return Responses.success(data={\"id\": user_id}, request=request)\n\n\n@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_svc: UserUpdateSvc,\n current_user: JWTUser = Depends(get_current_user),\n):\n try:\n updated_ids = await user_svc.update(user_id)\n if not updated_ids:\n return Responses.failure(status=Status.RECORD_NOT_EXIST_ERROR, request=request)\n except Exception as e:\n g.logger.error(traceback.format_exc())\n return Responses.failure(msg=\"userUpdate\u5931\u8d25\", error=e, request=request)\n return Responses.success(data={\"id\": user_id}, request=request)\n\n\n@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_svc = UserDeleteSvc()\n deleted_ids = await user_svc.delete(user_id)\n if not deleted_ids:\n return Responses.failure(status=Status.RECORD_NOT_EXIST_ERROR, request=request)\n except Exception as e:\n g.logger.error(traceback.format_exc())\n return Responses.failure(msg=\"userDelete\u5931\u8d25\", error=e, request=request)\n return Responses.success(data={\"id\": user_id}, request=request)\n\n\n@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_svc: UserLoginSvc,\n):\n try:\n data = await user_svc.login()\n if not data:\n return Responses.failure(status=Status.USER_OR_PASSWORD_ERROR, request=request)\n except Exception as e:\n g.logger.error(traceback.format_exc())\n return Responses.failure(msg=\"userLogin\u5931\u8d25\", error=e, request=request)\n return Responses.success(data={\"token\": data}, request=request)\n\n\n@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_svc: UserTokenSvc,\n current_user: JWTUser = Depends(get_current_user),\n):\n try:\n data = await user_svc.token()\n if not data:\n return Responses.failure(status=Status.RECORD_NOT_EXIST_ERROR, request=request)\n except Exception as e:\n g.logger.error(traceback.format_exc())\n return Responses.failure(msg=\"userToken\u5931\u8d25\", error=e, request=request)\n return Responses.success(data={\"token\": data}, request=request)\n",
|
|
19
19
|
"app/api/v1/__init__.py": "\"\"\"\napi-v1\n\"\"\"\n\n_prefix = \"/api/v1\"\n",
|
|
20
20
|
"app/initializer/context.py": "from contextvars import ContextVar\n\nrequest_id_ctx_var: ContextVar[str] = ContextVar(\"request_id\", default=\"N/A\")\n",
|
|
21
|
+
"app/initializer/dependencies.py": "from fastapi import Depends, Security\nfrom fastapi.security import HTTPBearer, HTTPAuthorizationCredentials, APIKeyHeader\nfrom fastapi.security.utils import get_authorization_scheme_param\nfrom pydantic import BaseModel\nfrom starlette.requests import Request\n\nfrom app.api.exceptions import CustomException\nfrom app.api.status import Status\nfrom app.initializer import g\nfrom app.utils.db_async_util import sqlfetch_one\nfrom app.utils.jwt_util import verify_jwt\n\n\n# ======= jwt =======\n\nclass JWTUser(BaseModel):\n # \u4e0e\u5b9e\u9645`user`\u5bf9\u9f50\n id: str = None\n phone: str = None\n name: str = None\n age: int = None\n gender: int = None\n\n @staticmethod\n async def get_user_jwt_key(user_id: str) -> str:\n # \u5efa\u8bae\uff1ajwt_key\u8fdb\u884credis\u7f13\u5b58\n async with g.db_async_session() as session:\n data = await sqlfetch_one(\n session=session,\n sql='SELECT jwt_key FROM \"user\" WHERE id = :id', # noqa\n params={\"id\": user_id},\n )\n return data.get(\"jwt_key\")\n\n\nclass JWTAuthorizationCredentials(HTTPAuthorizationCredentials):\n jwt_user: JWTUser\n\n\nclass JWTBearer(HTTPBearer):\n\n async def __call__(\n self, request: Request\n ) -> JWTAuthorizationCredentials | None:\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 jwt_user = await self.verify_credentials(credentials)\n return JWTAuthorizationCredentials(scheme=scheme, credentials=credentials, jwt_user=jwt_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 user_jwt_key = await JWTUser.get_user_jwt_key(playload.get(\"id\"))\n if not user_jwt_key:\n raise CustomException(status=Status.UNAUTHORIZED_ERROR)\n await self._verify_jwt(credentials, jwt_key=user_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: JWTAuthorizationCredentials | None = Depends(JWTBearer(auto_error=True))\n) -> JWTUser:\n if not credentials:\n return JWTUser()\n return credentials.jwt_user\n\n\n# ======= api key =======\n\n_API_KEY_HEADER = APIKeyHeader(name=\"X-API-Key\", auto_error=False)\n\n\nclass ApiKeyUser(BaseModel):\n\n @staticmethod\n def get_user_api_key(user_id: str = None) -> list:\n if user_id:\n return g.config.api_keys.get(user_id)\n return g.config.api_keys\n\n\nasync def get_current_api_key(api_key: str | None = Security(_API_KEY_HEADER)):\n if not api_key:\n raise CustomException(status=Status.FORBIDDEN_ERROR)\n user_api_key = ApiKeyUser.get_user_api_key()\n if not user_api_key:\n raise CustomException(status=Status.UNAUTHORIZED_ERROR)\n if api_key not in user_api_key:\n raise CustomException(status=Status.UNAUTHORIZED_ERROR)\n return api_key\n",
|
|
21
22
|
"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=\"env_path\",\n value=str(_CONFIG_DIR.joinpath(\".env\")))\n)\n# #\napp_yaml = Path(\n os.environ.get(\"app_yaml\") or\n _CONFIG_DIR.joinpath(f\"app_{os.environ.setdefault(key='app_env', value='dev')}.yaml\")\n)\nif not app_yaml.is_file():\n raise RuntimeError(f\"\u914d\u7f6e\u6587\u4ef6\u4e0d\u5b58\u5728\uff1a{app_yaml}\")\n\n\nclass Config:\n \"\"\"\u914d\u7f6e\"\"\"\n _yaml_conf: dict = None\n # from env\n app_env: str = \"dev\"\n app_yaml: str = app_yaml.name\n snow_datacenter_id: int = None\n api_keys: list = []\n # from yaml\n app_title: str = \"xApp\"\n app_summary: str = \"xxApp\"\n app_description: str = \"xxxApp\"\n app_version: str = \"1.0.0\"\n app_debug: bool = True\n app_log_dir: str = \"./logs\"\n app_disable_docs: bool = True\n app_allow_origins: list = [\"*\"]\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_or_yaml()\n return self\n\n def setattr_from_env_or_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 if k in os.environ: # \u4f18\u5148\u73af\u5883\u53d8\u91cf\n v = parse_variable(k=k, v_type=v_type, v_from=os.environ, default=v)\n else:\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._yaml_conf and not reload:\n return self._yaml_conf\n with open(app_yaml, mode=\"r\", encoding=\"utf-8\") as file:\n self._yaml_conf = yaml.load(file, Loader=yaml.FullLoader)\n return self._yaml_conf\n\n\ndef init_config() -> Config:\n return Config().setup()\n",
|
|
22
23
|
"app/initializer/_db.py": "import asyncio\nimport importlib\nimport re\n\nfrom sqlalchemy import create_engine\nfrom sqlalchemy.ext.asyncio import create_async_engine, AsyncSession\nfrom sqlalchemy.orm import sessionmaker, scoped_session\nfrom sqlalchemy.orm.decl_api import DeclarativeAttributeIntercept\n\nfrom app import APP_DIR\n\n_MODELS_MOD_DIR = APP_DIR.joinpath(\"models\")\n_MODELS_MOD_BASE = \"app.models\"\n_DECL_BASE_NAME = \"DeclBase\"\n_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 = False,\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 decl_base = _import_tables()\n if decl_base:\n try:\n decl_base.metadata.create_all(engine) # noqa\n except Exception as e:\n if \"already exists\" not in str(e):\n raise\n\n global _TABLES_CREATED\n if is_create_tables and not _TABLES_CREATED:\n create_tables()\n _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 = False,\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 decl_base = _import_tables()\n if decl_base:\n async with async_engine.begin() as conn:\n try:\n await conn.run_sync(decl_base.metadata.create_all) # noqa\n except Exception as e:\n if \"already exists\" not in str(e):\n raise\n\n global _TABLES_CREATED\n if is_create_tables and not _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 _TABLES_CREATED = True\n return db_async_session\n\n\ndef _import_tables() -> DeclarativeAttributeIntercept | None:\n decl_base = getattr(importlib.import_module(_MODELS_MOD_BASE), _DECL_BASE_NAME, None)\n if isinstance(decl_base, DeclarativeAttributeIntercept):\n pat = re.compile(rf\"^\\s*class\\s+[A-Za-z_]\\w*\\s*\\(\\s*{_DECL_BASE_NAME}\\s*\\)\\s*:\", re.MULTILINE)\n for f in _MODELS_MOD_DIR.rglob(\"*.py\"):\n if f.name.startswith(\"__\"):\n continue\n if pat.search(f.read_text(\"utf-8\")):\n rel = f.relative_to(_MODELS_MOD_DIR).with_suffix(\"\")\n _ = importlib.import_module(f\"{_MODELS_MOD_BASE}.{'.'.join(rel.parts)}\")\n return decl_base\n",
|
|
23
24
|
"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_CATCH = 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 catch=_LOG_CATCH,\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 catch=_LOG_CATCH,\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 catch=_LOG_CATCH,\n )\n return logger\n",
|
|
24
25
|
"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",
|
|
25
|
-
"app/initializer/_snow.py": "import os\
|
|
26
|
-
"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
|
|
27
|
-
"app/middleware/auths.py": "from fastapi import Depends, Security\nfrom fastapi.security import HTTPBearer, HTTPAuthorizationCredentials, APIKeyHeader\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.exceptions import CustomException\nfrom app.api.status import Status\nfrom app.initializer import g\nfrom app.utils import db_async_util\nfrom app.utils.auths_util import verify_jwt\n\n\n# ======= jwt =======\n\nclass JWTUser(BaseModel):\n # \u4e0e\u5b9e\u9645`user`\u5bf9\u9f50\n id: str = None\n phone: str = None\n name: str = None\n age: int = None\n gender: int = None\n\n @staticmethod\n async def get_user_jwt_key(user_id: str) -> str:\n # \u5efa\u8bae\uff1ajwt_key\u8fdb\u884credis\u7f13\u5b58\n async with g.db_async_session() as session:\n data = await db_async_util.sqlfetch_one(\n session=session,\n sql='SELECT jwt_key FROM \"user\" WHERE id = :id', # noqa\n params={\"id\": user_id},\n )\n return data.get(\"jwt_key\")\n\n\nclass JWTAuthorizationCredentials(HTTPAuthorizationCredentials):\n jwt_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 jwt_user = await self.verify_credentials(credentials)\n return JWTAuthorizationCredentials(scheme=scheme, credentials=credentials, jwt_user=jwt_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 user_jwt_key = await JWTUser.get_user_jwt_key(playload.get(\"id\"))\n if not user_jwt_key:\n raise CustomException(status=Status.UNAUTHORIZED_ERROR)\n await self._verify_jwt(credentials, jwt_key=user_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.jwt_user\n\n\n# ======= api key =======\n\n_API_KEY_HEADER = APIKeyHeader(name=\"X-API-Key\", auto_error=False)\n\n\nclass ApiKeyUser(BaseModel):\n\n @staticmethod\n def get_user_api_key(user_id: str = None) -> list:\n if user_id:\n return g.config.api_keys.get(user_id)\n return g.config.api_keys\n\n\nasync def get_current_api_key(api_key: str = Security(_API_KEY_HEADER)):\n if not api_key:\n raise CustomException(status=Status.FORBIDDEN_ERROR)\n user_api_key = ApiKeyUser.get_user_api_key()\n if not user_api_key:\n raise CustomException(status=Status.UNAUTHORIZED_ERROR)\n if api_key not in user_api_key:\n raise CustomException(status=Status.UNAUTHORIZED_ERROR)\n return api_key\n",
|
|
26
|
+
"app/initializer/_snow.py": "import os\nimport sys\nfrom datetime import datetime\n\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 sys.stderr.write(\n f\"{datetime.now().strftime('%Y-%m-%d %H:%M:%S,%f')[:-3]} \"\n f\"Warning initializer \"\n f\"snow\u521d\u59cb\u5316id\u5c06\u91c7\u7528\u672c\u5730\u65b9\u5f0f\uff0c\u7531\u4e8e\uff08{e}\uff09\"\n f\"\\n\"\n )\n return incr\n",
|
|
27
|
+
"app/initializer/__init__.py": "\"\"\"\n\u521d\u59cb\u5316\n\"\"\"\nimport sys\nimport threading\nfrom datetime import datetime\nfrom functools import cached_property\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 Config, 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 _initialized = False\n _init_lock = threading.Lock()\n _init_properties = [\n 'config',\n 'logger',\n 'redis_cli',\n 'snow_cli',\n # 'db_session',\n 'db_async_session',\n ]\n\n def __init__(self):\n self._initialized = False\n\n @cached_property\n def config(self) -> Config:\n return init_config()\n\n @cached_property\n def logger(self) -> Logger:\n return init_logger(\n debug=self.config.app_debug,\n log_dir=self.config.app_log_dir,\n )\n\n @cached_property\n def redis_cli(self) -> RedisCli:\n return init_redis_cli(\n host=self.config.redis_host,\n port=self.config.redis_port,\n db=self.config.redis_db,\n password=self.config.redis_password,\n max_connections=self.config.redis_max_connections,\n )\n\n @cached_property\n def snow_cli(self) -> SnowFlake:\n return init_snow_cli(\n redis_cli=self.redis_cli,\n datacenter_id=self.config.snow_datacenter_id,\n )\n\n @cached_property\n def db_session(self) -> scoped_session:\n return init_db_session(\n db_url=self.config.db_url,\n db_echo=self.config.app_debug,\n is_create_tables=True,\n )\n\n @cached_property\n def db_async_session(self) -> sessionmaker:\n return init_db_async_session(\n db_url=self.config.db_async_url,\n db_echo=self.config.app_debug,\n is_create_tables=True,\n )\n\n def setup(self):\n with self._init_lock:\n if not self._initialized:\n for prop_name in self._init_properties:\n if hasattr(self, prop_name):\n getattr(self, prop_name)\n else:\n sys.stderr.write(\n f\"{datetime.now().strftime('%Y-%m-%d %H:%M:%S,%f')[:-3]} \"\n f\"Warning initializer \"\n f\"{prop_name} not found\"\n f\"\\n\"\n )\n self._initialized = True\n\n\ng = G()\n",
|
|
28
28
|
"app/middleware/cors.py": "from fastapi.middleware.cors import CORSMiddleware\n\nfrom app.initializer import g\n\n\nclass Cors:\n middleware_class = CORSMiddleware\n allow_origins = g.config.app_allow_origins\n allow_credentials = True\n allow_methods = [\"*\"]\n allow_headers = [\"*\"]\n",
|
|
29
29
|
"app/middleware/exceptions.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.exceptions import CustomException\nfrom app.api.responses import Responses\nfrom app.api.status import Status\nfrom app.initializer import g\n\n\nclass ExceptionsHandler:\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 Responses.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 Responses.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 Responses.failure(\n msg=msg,\n status=Status.PARAMS_ERROR,\n request=request,\n )\n",
|
|
30
30
|
"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 \"\"\"\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",
|
|
31
31
|
"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.exceptions import CustomException\nfrom app.middleware.cors import Cors\nfrom app.middleware.exceptions import ExceptionsHandler\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, ExceptionsHandler.custom_exception_handler) # type: ignore\n app.add_exception_handler(HTTPException, ExceptionsHandler.http_exception_handler) # type: ignore\n app.add_exception_handler(RequestValidationError, ExceptionsHandler.validation_exception_handler) # type: ignore\n",
|
|
32
|
-
"app/models/user.py": "from sqlalchemy import Column, BigInteger, Integer, String\nfrom toollib.utils import now2timestamp\n\nfrom app.
|
|
32
|
+
"app/models/user.py": "from sqlalchemy import Column, BigInteger, Integer, String\nfrom toollib.utils import now2timestamp\n\nfrom app.initializer import g\nfrom app.models import DeclBase\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",
|
|
33
33
|
"app/models/__init__.py": "\"\"\"\n\u6570\u636e\u6a21\u578b\n\"\"\"\nfrom sqlalchemy.orm import DeclarativeBase\n\n\nclass DeclBase(DeclarativeBase):\n pass\n",
|
|
34
34
|
"app/schemas/user.py": "import re\nfrom typing import Literal\n\nfrom pydantic import BaseModel, Field, field_validator\n\nfrom app.schemas import filter_fields\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",
|
|
35
35
|
"app/schemas/__init__.py": "\"\"\"\n\u6570\u636e\u7ed3\u6784\n\"\"\"\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",
|
|
36
|
-
"app/services/user.py": "from app.models.user import User\nfrom app.schemas.user import (\n UserDetail,\n UserList,\n UserCreate,\n UserUpdate,\n UserDelete,\n UserLogin,\n UserToken,\n)\nfrom app.
|
|
36
|
+
"app/services/user.py": "from app.initializer import g\nfrom app.models.user import User\nfrom app.schemas.user import (\n UserDetail,\n UserList,\n UserCreate,\n UserUpdate,\n UserDelete,\n UserLogin,\n UserToken,\n)\nfrom app.utils import jwt_util, db_async_util\n\n\nclass UserDetailSvc(UserDetail):\n model_config = {\n \"json_schema_extra\": {\n \"title\": \"UserDetail\"\n }\n }\n\n async def detail(self):\n async with g.db_async_session() as session:\n data = await db_async_util.fetch_one(\n session=session,\n model=User,\n fields=self.response_fields(),\n filter_by={\"id\": self.id},\n )\n return data\n\n\nclass UserListSvc(UserList):\n model_config = {\n \"json_schema_extra\": {\n \"title\": \"UserList\"\n }\n }\n\n async def lst(self):\n async with g.db_async_session() as session:\n data = await db_async_util.fetch_all(\n session=session,\n model=User,\n fields=self.response_fields(),\n page=self.page,\n size=self.size,\n )\n total = await db_async_util.fetch_total(session, User)\n return data, total\n\n\nclass UserCreateSvc(UserCreate):\n model_config = {\n \"json_schema_extra\": {\n \"title\": \"UserCreate\"\n }\n }\n\n async def create(self):\n async with g.db_async_session() as session:\n return await db_async_util.create(\n session=session,\n model=User,\n data={\n \"name\": self.name,\n \"phone\": self.phone,\n \"age\": self.age,\n \"gender\": self.gender,\n \"password\": jwt_util.hash_password(self.password),\n \"jwt_key\": jwt_util.gen_jwt_key(),\n },\n filter_by={\"phone\": self.phone},\n )\n\n\nclass UserUpdateSvc(UserUpdate):\n model_config = {\n \"json_schema_extra\": {\n \"title\": \"UserUpdate\"\n }\n }\n\n async def update(self, user_id: str):\n async with g.db_async_session() as session:\n return await db_async_util.update(\n session=session,\n model=User,\n data=self.model_dump(),\n filter_by={\"id\": user_id},\n )\n\n\nclass UserDeleteSvc(UserDelete):\n model_config = {\n \"json_schema_extra\": {\n \"title\": \"UserDelete\"\n }\n }\n\n @staticmethod\n async def delete(user_id: str):\n async with g.db_async_session() as session:\n return await db_async_util.delete(\n session=session,\n model=User,\n filter_by={\"id\": user_id},\n )\n\n\nclass UserLoginSvc(UserLogin):\n model_config = {\n \"json_schema_extra\": {\n \"title\": \"UserLogin\"\n }\n }\n\n async def login(self):\n async with g.db_async_session() as session:\n data = await db_async_util.fetch_one(\n session=session,\n model=User,\n filter_by={\"phone\": self.phone},\n )\n if not data or not jwt_util.verify_password(self.password, data.get(\"password\")):\n return None\n new_jwt_key = jwt_util.gen_jwt_key()\n token = jwt_util.gen_jwt(\n payload={\n \"id\": data.get(\"id\"),\n \"phone\": data.get(\"phone\"),\n \"name\": data.get(\"name\"),\n \"age\": data.get(\"age\"),\n \"gender\": data.get(\"gender\"),\n },\n jwt_key=new_jwt_key,\n exp_minutes=24 * 60 * 30,\n )\n # \u66f4\u65b0jwt_key\n await db_async_util.update(\n session=session,\n model=User,\n data={\"jwt_key\": new_jwt_key},\n filter_by={\"phone\": self.phone},\n )\n return token\n\n\nclass UserTokenSvc(UserToken):\n model_config = {\n \"json_schema_extra\": {\n \"title\": \"UserToken\"\n }\n }\n\n async def token(self):\n async with g.db_async_session() as session:\n data = await db_async_util.fetch_one(\n session=session,\n model=User,\n filter_by={\"id\": self.id},\n )\n if not data:\n return None\n new_jwt_key = jwt_util.gen_jwt_key()\n token = jwt_util.gen_jwt(\n payload={\n \"id\": data.get(\"id\"),\n \"phone\": data.get(\"phone\"),\n \"name\": data.get(\"name\"),\n \"age\": data.get(\"age\"),\n \"gender\": data.get(\"gender\"),\n },\n jwt_key=new_jwt_key,\n exp_minutes=self.exp_minutes,\n )\n # \u66f4\u65b0jwt_key\n await db_async_util.update(\n session=session,\n model=User,\n data={\"jwt_key\": new_jwt_key},\n filter_by={\"id\": self.id},\n )\n return token\n",
|
|
37
37
|
"app/services/__init__.py": "\"\"\"\n\u4e1a\u52a1\u903b\u8f91\n\"\"\"\n",
|
|
38
|
-
"app/utils/
|
|
38
|
+
"app/utils/api_key_util.py": "import secrets\n\n_API_KEY_LENGTH = 45\n\n\ndef gen_api_key(prefix: str = \"\", length: int = _API_KEY_LENGTH) -> str:\n api_key = secrets.token_urlsafe(length)[:length]\n if prefix:\n return f\"{prefix}_{api_key}\"\n return api_key\n",
|
|
39
39
|
"app/utils/db_async_util.py": "from sqlalchemy import (\n select,\n func,\n inspect,\n text,\n update as update_,\n delete as delete_,\n)\n\n\ndef format_all(\n rows,\n fields: list[str],\n) -> list[dict]:\n if not rows:\n return list()\n return [dict(zip(fields, row)) for row in rows]\n\n\ndef format_one(\n row,\n fields: list[str],\n) -> dict:\n if not row:\n return {}\n return dict(zip(fields, row))\n\n\ndef model_dict(\n model,\n fields: list[str] = None,\n) -> dict:\n if not model:\n return {}\n if not fields:\n fields = [field.name for field in model.__table__.columns]\n return {field: getattr(model, field) for field in fields}\n\n\nasync def fetch_one(\n session,\n model,\n fields: list[str] = None,\n filter_by: dict = None,\n) -> dict:\n if not fields:\n fields = [field.name for field in model.__table__.columns]\n query = select(*[getattr(model, field) for field in fields if hasattr(model, field)]).select_from(model)\n if filter_by:\n query = query.filter_by(**filter_by)\n result = await session.execute(query)\n return format_one(result.fetchone(), fields)\n\n\nasync def fetch_all(\n session,\n model,\n fields: list[str] = None,\n filter_by: dict = None,\n page: int = None,\n size: int = None,\n) -> list[dict]:\n if not fields:\n fields = [field.name for field in model.__table__.columns]\n query = select(*[getattr(model, field) for field in fields if hasattr(model, field)]).select_from(model)\n if filter_by:\n query = query.filter_by(**filter_by)\n if page and size:\n query = query.offset((page - 1) * size).limit(size)\n result = await session.execute(query)\n return format_all(result.fetchall(), fields)\n\n\nasync def fetch_total(\n session,\n model,\n filter_by: dict = None,\n column: str = None,\n) -> int:\n if column is None:\n primary_key = inspect(model).primary_key\n if primary_key:\n column = primary_key[0]\n else:\n column = 1\n query = select(func.count(column)).select_from(model)\n if filter_by:\n query = query.filter_by(**filter_by)\n result = await session.execute(query)\n return result.scalar()\n\n\nasync def create(\n session,\n model,\n data: dict,\n filter_by: dict = None,\n) -> int:\n try:\n if filter_by:\n result = await fetch_one(session, model, filter_by=filter_by)\n if result:\n return 0\n stmt = model(**data)\n session.add(stmt)\n await session.commit()\n except Exception:\n await session.rollback()\n raise\n return stmt.id\n\n\nasync def update(\n session,\n model,\n data: dict,\n filter_by: dict | None,\n is_exclude_none: bool = True,\n) -> list:\n try:\n if is_exclude_none:\n data = {k: v for k, v in data.items() if v is not None}\n stmt = update_(model).values(**data)\n if filter_by:\n stmt = stmt.filter_by(**filter_by)\n if session.bind.dialect.name == \"postgresql\":\n stmt = stmt.returning(model.id)\n result = await session.execute(stmt)\n updated_ids = [row[0] for row in result]\n else:\n query_stmt = select(model.id).filter_by(**filter_by)\n result = await session.execute(query_stmt)\n updated_ids = result.scalars().all()\n if updated_ids:\n await session.execute(stmt)\n await session.commit()\n except Exception:\n await session.rollback()\n raise\n return updated_ids\n\n\nasync def delete(\n session,\n model,\n filter_by: dict | None,\n) -> list:\n try:\n stmt = delete_(model)\n if filter_by:\n stmt = stmt.filter_by(**filter_by)\n if session.bind.dialect.name == \"postgresql\":\n stmt = stmt.returning(model.id)\n result = await session.execute(stmt)\n deleted_ids = [row[0] for row in result]\n else:\n query_stmt = select(model.id).filter_by(**filter_by)\n result = await session.execute(query_stmt)\n deleted_ids = result.scalars().all()\n if deleted_ids:\n await session.execute(stmt)\n await session.commit()\n except Exception:\n await session.rollback()\n raise\n return deleted_ids\n\n\nasync def sqlfetch_one(\n session,\n sql: str,\n params: dict = None,\n) -> dict:\n result = await session.execute(text(sql), params)\n row = result.fetchone()\n if row is None:\n return {}\n return row._asdict() # noqa\n\n\nasync def sqlfetch_all(\n session,\n sql: str,\n params: dict = None,\n) -> list[dict]:\n result = await session.execute(text(sql), params)\n rows = result.fetchall()\n return [row._asdict() for row in rows] # noqa\n",
|
|
40
|
+
"app/utils/jwt_util.py": "import secrets\nfrom datetime import datetime, timedelta\n\nimport bcrypt\nimport jwt\n\n_JWT_ALGORITHM = \"HS256\"\n\n\ndef gen_jwt(payload: dict, jwt_key: str, exp_minutes: int = 24 * 60 * 30, algorithm: str = _JWT_ALGORITHM):\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, algorithms: tuple = (_JWT_ALGORITHM,)) -> 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=algorithms)\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",
|
|
40
41
|
"app/utils/__init__.py": "\"\"\"\nutils\n\"\"\"\n",
|
|
41
42
|
"app_celery/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=\"env_path\",\n value=str(_CONFIG_DIR.joinpath(\".env\")))\n)\n# #\napp_yaml = Path(\n os.environ.get(\"app_yaml\") or\n _CONFIG_DIR.joinpath(f\"app_{os.environ.setdefault(key='app_env', value='dev')}.yaml\")\n)\nif not app_yaml.is_file():\n raise RuntimeError(f\"\u914d\u7f6e\u6587\u4ef6\u4e0d\u5b58\u5728\uff1a{app_yaml}\")\n\n\nclass Config:\n \"\"\"\u914d\u7f6e\"\"\"\n _yaml_conf: dict = None\n yaml_name: str = app_yaml.name\n #\n celery_broker_url: str\n celery_backend_url: str\n celery_timezone: str = \"Asia/Shanghai\"\n celery_enable_utc: bool = True\n celery_task_serializer: str = \"json\"\n celery_result_serializer: str = \"json\"\n celery_accept_content: list = [\"json\"]\n celery_task_ignore_result: bool = False\n celery_result_expire: int = 86400\n celery_task_track_started: bool = True\n celery_worker_concurrency: int = 8\n celery_worker_prefetch_multiplier: int = 2\n celery_worker_max_tasks_per_child: int = 100\n celery_broker_connection_retry_on_startup: bool = True\n celery_task_reject_on_worker_lost: bool = True\n\n def setup(self):\n self.setattr_from_env_or_yaml()\n return self\n\n def setattr_from_env_or_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 if k in os.environ: # \u4f18\u5148\u73af\u5883\u53d8\u91cf\n v = parse_variable(k=k, v_type=v_type, v_from=os.environ, default=v)\n else:\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._yaml_conf and not reload:\n return self._yaml_conf\n with open(app_yaml, mode=\"r\", encoding=\"utf-8\") as file:\n self._yaml_conf = yaml.load(file, Loader=yaml.FullLoader)\n return self._yaml_conf\n\n\nconfig = Config().setup()\n",
|
|
42
43
|
"app_celery/README.md": "# app-celery\n\n## \u7b80\u4ecb\n\n### producer\uff1a\u751f\u4ea7\u8005\uff08\u53d1\u5e03\u4efb\u52a1\uff09\n\n- register\uff1a\u6ce8\u518c\u4e2d\u5fc3\n - \u5c06`consumer`\u7684`tasks`\u6ce8\u518c\u5230`producer`\u7684`register`\u4e2d\n- publisher\uff1a\u53d1\u5e03\u8005\n - \u9879\u76ee\u4e2d\u901a\u8fc7\u53d1\u5e03\u8005\u6765\u53d1\u5e03\u4efb\u52a1\uff1a\u53ef\u53c2\u8003`app/api/default/aping.py`\uff08\u8fd9\u91cc\u53ea\u662f\u7b80\u5355\u793a\u4f8b\uff0c\u5b9e\u9645\u4e0a\u5e94\u8be5\u5728`services`\u5c42\u8c03\u7528\uff09\n\n### consumer\uff1a\u6d88\u8d39\u8005\uff08\u6267\u884c\u4efb\u52a1\uff09\n\n- tasks: \u4efb\u52a1\n - \u5b9a\u65f6\u4efb\u52a1\uff08beat_xxx\uff09\n - 1\u3002\u521b\u5efa\u5b9a\u65f6\u4efb\u52a1\n - 2\u3002\u53d1\u5e03\u5b9a\u65f6\u4efb\u52a1\uff08\u901a\u8fc7celery\u5185\u90e8\u7684`beat`\u8c03\u7528\uff09\n - \u8fdb\u5165`app_celery`\u7236\u7ea7\u76ee\u5f55\uff0c\u5373\u5de5\u4f5c\u76ee\u5f55\n - \u542f\u52a8\u547d\u4ee4\uff1a\uff08\u66f4\u591a\u53c2\u6570\u8bf7\u81ea\u884c\u6307\u5b9a\uff09\n - \u65b9\u5f0f1\u3002\u76f4\u63a5\u6267\u884c\u811a\u672c: `python runcbeat.py`\n - \u65b9\u5f0f2\u3002\u4f7f\u7528\u547d\u4ee4\u884c\uff1a`celery -A app_celery.consumer beat --loglevel=info --max-interval=5`\n - 3\u3002\u542f\u52a8\u6d88\u8d39\u8005worker\n - \u5f02\u6b65\u4efb\u52a1\uff08xxx)\n - 1\u3002\u521b\u5efa\u5f02\u6b65\u4efb\u52a1\uff0c\u5e76\u6ce8\u518c\u5230`producer`\u7684`register`\uff0c\u6839\u636e\u6ce8\u518c\u7684\u89c4\u5219\u8fdb\u884c`\u4efb\u52a1\u8c03\u7528`\u548c`worker\u542f\u52a8`\n - 2\u3002\u53d1\u5e03\u5f02\u6b65\u4efb\u52a1\uff08\u901a\u8fc7\u751f\u4ea7\u8005\u7684`publisher`\u8c03\u7528\uff09\n - \u53ef\u53c2\u8003`app/api/default/aping.py`\uff08\u8fd9\u91cc\u53ea\u662f\u7b80\u5355\u793a\u4f8b\uff0c\u5b9e\u9645\u4e0a\u5e94\u8be5\u5728`services`\u5c42\u8c03\u7528\uff09\n - 3\u3002\u542f\u52a8\u6d88\u8d39\u8005worker\n- workers: \u5de5\u4f5c\u8005\n - 1\u3002\u521b\u5efaworker\u670d\u52a1\uff0c\u5b9a\u4e49\u961f\u5217\u7b49\u5c5e\u6027\uff08\u4e3a\u65b9\u4fbf\u6269\u5c55\u5efa\u8bae\u4e00\u7c7b\u4efb\u52a1\u4e00\u4e2a\u670d\u52a1\uff09\n - 2\u3002\u542f\u52a8worker\u670d\u52a1\uff1a\n - 1\u3002\u8fdb\u5165`app_celery`\u7236\u7ea7\u76ee\u5f55\uff0c\u5373\u5de5\u4f5c\u76ee\u5f55\n - 2\u3002\u542f\u52a8\u547d\u4ee4\uff1a\uff08\u66f4\u591a\u53c2\u6570\u8bf7\u81ea\u884c\u6307\u5b9a\uff09\n - \u65b9\u5f0f1\u3002\u76f4\u63a5\u6267\u884c\u811a\u672c: `python runcworker.py -n ping`\n - \u65b9\u5f0f2\u3002\u4f7f\u7528\u547d\u4ee4\u884c\uff1a`celery -A app_celery.consumer.workers.ping worker --loglevel=info --concurrency=5`\n\n### \u6ce8\u610f\uff1a\n\n- \u6700\u597d\u4e0e`app`\u89e3\u8026\uff0c\u5373\uff1a\n - \u53ea`app`\u5355\u5411\u8c03\u7528`app_celery`\n - \u4f46`app_celery`\u4e0d\u8c03\u7528`app`",
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
fastapi_scaff/__init__.py,sha256=iRPUsJ0Dm6XebmaUUvQ0TxYIDNVAN0M0Hn9Tbhyq9Sc,120
|
|
2
|
+
fastapi_scaff/__main__.py,sha256=-IlQZJqjzxphIMrrPW_4lDDG5lAXP2j8sS5L6emvZfg,18110
|
|
3
|
+
fastapi_scaff/_api_tpl.json,sha256=CF4ZGfOhPb1goNW2hLvAHW9nRmZbsVEifmFDGY9w49E,7713
|
|
4
|
+
fastapi_scaff/_project_tpl.json,sha256=G3DJF1Zr3vmqS-5ECrHRdyjvKjowYvG0w0yzB-quisM,92637
|
|
5
|
+
fastapi_scaff-0.3.2.dist-info/licenses/LICENSE,sha256=A5H6q7zd1QrL3iVs1KLsBOG0ImV-t9PpPspM4x-4Ea8,1069
|
|
6
|
+
fastapi_scaff-0.3.2.dist-info/METADATA,sha256=-kPs4wy80CUMZdLHMPh2qwdebKtmCCr2--oKY_yl6C8,3792
|
|
7
|
+
fastapi_scaff-0.3.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
8
|
+
fastapi_scaff-0.3.2.dist-info/entry_points.txt,sha256=kzs28nmpRWVCmWmZav3X7u7YOIOEir3sCkLnvQKTJbY,62
|
|
9
|
+
fastapi_scaff-0.3.2.dist-info/top_level.txt,sha256=LeyfUxMRhdbRHcYoH37ftfdspyZ8V3Uut2YBaTCzq2k,14
|
|
10
|
+
fastapi_scaff-0.3.2.dist-info/RECORD,,
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
fastapi_scaff/__init__.py,sha256=ytCH_krcYT8riZWEw_zK8DvKL3Cbc2OPjA4sZ0tPtIw,120
|
|
2
|
-
fastapi_scaff/__main__.py,sha256=-aiQDTbnoFG2S_Z2zybI-yhSYhovOyeDAmFdE9f72UY,18337
|
|
3
|
-
fastapi_scaff/_api_tpl.json,sha256=dwFNehzYWjxBkRtXzeuyfEjllFDDJonpKpO2MH3WD4Y,7713
|
|
4
|
-
fastapi_scaff/_project_tpl.json,sha256=M6mXc0Z148Vrj2LmP-yJt5n-pS0wolyKii5Jd1IwAYw,92776
|
|
5
|
-
fastapi_scaff-0.3.1.dist-info/licenses/LICENSE,sha256=A5H6q7zd1QrL3iVs1KLsBOG0ImV-t9PpPspM4x-4Ea8,1069
|
|
6
|
-
fastapi_scaff-0.3.1.dist-info/METADATA,sha256=Jwo38u7402RkF_Gac7VOGrGt0iTvoU-vhtbRRn0u8Vw,3792
|
|
7
|
-
fastapi_scaff-0.3.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
8
|
-
fastapi_scaff-0.3.1.dist-info/entry_points.txt,sha256=kzs28nmpRWVCmWmZav3X7u7YOIOEir3sCkLnvQKTJbY,62
|
|
9
|
-
fastapi_scaff-0.3.1.dist-info/top_level.txt,sha256=LeyfUxMRhdbRHcYoH37ftfdspyZ8V3Uut2YBaTCzq2k,14
|
|
10
|
-
fastapi_scaff-0.3.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|