fastapi-scaff 0.2.3__py3-none-any.whl → 0.2.4__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.2.3"
10
+ __version__ = "0.2.4"
@@ -4,7 +4,7 @@
4
4
  "README.md": "# fastapi-scaff\n\n## What is this?\n\n- by: axiner\n- fastapi-scaff\n- This is a fastapi scaff.\n - new project\n - add api\n - about project:\n - auto init project (conf, db, log...)\n - auto register router\n - auto register middleware\n - ...\n - more documents: [\u8bf7\u70b9\u51fb\u94fe\u63a5](https://blog.csdn.net/atpuxiner/article/details/144291336?fromshare=blogdetail&sharetype=blogdetail&sharerId=144291336&sharerefer=PC&sharesource=atpuxiner&sharefrom=from_link)\n\n## Project structure\n\n- ASM: ASM\u6a21\u5f0f\n - A api\n - S services(&schemas)\n - M models\n- \u8c03\u7528\u8fc7\u7a0b: main.py(initializer) -> (middleware) - api - services(&schemas) - (models)\n- \u7ed3\u6784\u5982\u4e0b: (\u547d\u540d\u7ecf\u8fc7\u591a\u6b21\u4fee\u6539\u6572\u5b9a\uff0c\u7b80\u6d01\u6613\u61c2)\n ```\n \u2514\u2500\u2500 fastapi-scaff\n \u251c\u2500\u2500 app (\u5e94\u7528)\n \u2502 \u251c\u2500\u2500 api \u251c\u2500\u2500 (api)\n \u2502 \u2502 \u2514\u2500\u2500 v1 \u2502 \u2514\u2500\u2500 (v1)\n \u2502 \u251c\u2500\u2500 initializer \u251c\u2500\u2500 (\u521d\u59cb\u5316)\n \u2502 \u2502 \u251c\u2500\u2500 conf \u2502 \u251c\u2500\u2500 (\u914d\u7f6e)\n \u2502 \u2502 \u251c\u2500\u2500 db \u2502 \u251c\u2500\u2500 (\u6570\u636e\u5e93)\n \u2502 \u2502 \u251c\u2500\u2500 log \u2502 \u251c\u2500\u2500 (\u65e5\u5fd7)\n \u2502 \u2502 \u2514\u2500\u2500 ... \u2502 \u2514\u2500\u2500 (...)\n \u2502 \u251c\u2500\u2500 middleware \u251c\u2500\u2500 (\u4e2d\u95f4\u4ef6)\n \u2502 \u251c\u2500\u2500 models \u251c\u2500\u2500 (\u6570\u636e\u6a21\u578b)\n \u2502 \u251c\u2500\u2500 schemas \u251c\u2500\u2500 (\u6570\u636e\u7ed3\u6784)\n \u2502 \u251c\u2500\u2500 services \u251c\u2500\u2500 (\u4e1a\u52a1\u903b\u8f91)\n \u2502 \u251c\u2500\u2500 utils \u251c\u2500\u2500 (utils)\n \u2502 \u2514\u2500\u2500 main.py \u2514\u2500\u2500 (main.py)\n \u251c\u2500\u2500 app_celery (\u5e94\u7528-celery)\n \u251c\u2500\u2500 config (\u914d\u7f6e\u76ee\u5f55)\n \u251c\u2500\u2500 deploy (\u90e8\u7f72\u76ee\u5f55)\n \u251c\u2500\u2500 docs (\u6587\u6863\u76ee\u5f55)\n \u251c\u2500\u2500 logs (\u65e5\u5fd7\u76ee\u5f55)\n \u251c\u2500\u2500 tests (\u6d4b\u8bd5\u76ee\u5f55)\n \u251c\u2500\u2500 .gitignore\n \u251c\u2500\u2500 LICENSE\n \u251c\u2500\u2500 README.md\n \u251c\u2500\u2500 requirements.txt\n \u2514\u2500\u2500 runcbeat.py\n \u2514\u2500\u2500 runcworker.py\n \u2514\u2500\u2500 runserver.py\n ```\n\n## Installation\n\nThis package can be installed using pip (Python>=3.11):\n> pip install fastapi-scaff\n\n## Scaff usage\n\n- 1\uff09help document\n - `fastapi-scaff -h`\n- 2\uff09new project\n - `fastapi-scaff new <myproj>`\n- 3\uff09add api\n - `cd to project root dir`\n - `fastapi-scaff add <myapi>`\n\n## Project run\n\n- 1\uff09cd to project root dir\n- 2\uff09modify the configuration, such as for the database\n- 3\uff09`pip install -r requirements.txt`\n- 4\uff09`python runserver.py`\n - more parameters see:\n - about uvicorn: [click here](https://www.uvicorn.org/)\n - about gunicorn: [click here](https://docs.gunicorn.org/en/stable/)\n\n## License\n\nThis project is released under the MIT License (MIT). See [LICENSE](LICENSE)\n",
5
5
  "requirements.txt": "# -*- coding: utf-8 -*-\n# Python>=3.11\nfastapi==0.116.1\nuvicorn==0.35.0\norjson==3.11.1\ntoollib==1.7.8\npython-dotenv==1.1.1\nPyYAML==6.0.2\nloguru==0.7.3\nSQLAlchemy==2.0.42\naiosqlite==0.21.0\nredis==6.4.0\nPyJWT==2.10.1\nbcrypt==4.3.0\ncelery==5.5.3\nredis==6.4.0",
6
6
  "runcbeat.py": "\"\"\"\n@author axiner\n@version v1.0.0\n@created 2025/09/20 10:10\n@abstract runcbeat\uff08\u66f4\u591a\u53c2\u6570\u8bf7\u81ea\u884c\u6307\u5b9a\uff09\n@description\n@history\n\"\"\"\nimport argparse\nimport subprocess\n\n\ndef main(\n loglevel: str = \"info\",\n scheduler: str = None,\n pidfile: str = None,\n max_interval: int = 5,\n):\n parser = argparse.ArgumentParser(description=\"CeleryBeat\u542f\u52a8\u5668\")\n parser.add_argument(\"-l\", \"--loglevel\", type=str, default=\"info\", metavar=\"\", help=\"\u65e5\u5fd7\u7b49\u7ea7\")\n parser.add_argument(\"-S\", \"--scheduler\", type=str, default=None, metavar=\"\", help=\"\u8c03\u5ea6\u5668\u7c7b\u578b\")\n parser.add_argument(\"--pidfile\", type=str, default=None, metavar=\"\", help=\"pid\u6587\u4ef6\")\n parser.add_argument(\"--max-interval\", type=int, default=5, metavar=\"\", help=\"\u68c0\u6d4b\u4efb\u52a1\u95f4\u9694\")\n args = parser.parse_args()\n loglevel = args.loglevel or loglevel\n scheduler = args.scheduler or scheduler\n pidfile = args.pidfile or pidfile\n max_interval = args.max_interval or max_interval\n command = [\n \"celery\",\n \"-A\",\n \"app_celery.consumer\",\n \"beat\",\n f\"--loglevel={loglevel}\",\n f\"--max-interval={max_interval}\",\n ]\n if scheduler:\n command.extend([\"--scheduler\", scheduler])\n if pidfile:\n command.extend([\"--pidfile\", pidfile])\n subprocess.run(command, check=True)\n\n\nif __name__ == '__main__':\n main()\n",
7
- "runcworker.py": "\"\"\"\n@author axiner\n@version v1.0.0\n@created 2025/09/20 10:10\n@abstract runcworker\uff08\u66f4\u591a\u53c2\u6570\u8bf7\u81ea\u884c\u6307\u5b9a\uff09\n@description\n@history\n\"\"\"\nimport argparse\nimport platform\nimport subprocess\nfrom os import cpu_count\n\n\ndef main(\n name: str, # `app_celery/consumer/workers`\u4e0b\u7684\u6a21\u5757\u540d\n loglevel: str = \"info\",\n concurrency: int = None,\n pool: str = None,\n queues: str = None,\n):\n parser = argparse.ArgumentParser(description=\"CeleryWorker\u542f\u52a8\u5668\")\n parser.add_argument(\"-n\", \"--name\", type=str, metavar=\"\", help=\"\u540d\u79f0\")\n parser.add_argument(\"-l\", \"--loglevel\", type=str, default=\"info\", metavar=\"\", help=\"\u65e5\u5fd7\u7b49\u7ea7\")\n parser.add_argument(\"-c\", \"--concurrency\", type=int, default=None, metavar=\"\", help=\"\u5e76\u53d1\u6570\")\n parser.add_argument(\"-P\", \"--pool\", type=str, default=None, metavar=\"\", help=\"\u5e76\u53d1\u6a21\u578b\")\n parser.add_argument(\"-Q\", \"--queues\", type=str, default=None, metavar=\"\", help=\"\u961f\u5217\")\n args = parser.parse_args()\n name = args.name or name\n loglevel = args.loglevel or loglevel\n concurrency = args.concurrency or concurrency\n pool = args.pool or pool\n queues = args.queues or queues\n if pool is None:\n if platform.system().lower().startswith(\"win\"):\n pool = 'gevent'\n if not concurrency:\n concurrency = 100\n else:\n pool = 'prefork'\n if not concurrency:\n concurrency = cpu_count()\n command = [\n \"celery\",\n \"-A\",\n f\"app_celery.consumer.workers.{name}\",\n \"worker\",\n f\"--loglevel={loglevel}\",\n f\"--concurrency={concurrency}\",\n f\"--pool={pool}\",\n ]\n if queues:\n command.extend([\"--queues\", queues])\n subprocess.run(\n command,\n check=True,\n )\n\n\nif __name__ == '__main__':\n main(\n name=\"ping\",\n )\n",
7
+ "runcworker.py": "\"\"\"\n@author axiner\n@version v1.0.0\n@created 2025/09/20 10:10\n@abstract runcworker\uff08\u66f4\u591a\u53c2\u6570\u8bf7\u81ea\u884c\u6307\u5b9a\uff09\n@description\n@history\n\"\"\"\nimport argparse\nimport platform\nimport subprocess\nfrom os import cpu_count\n\n\ndef main(\n name: str, # `app_celery/consumer/workers`\u4e0b\u7684\u6a21\u5757\u540d\n loglevel: str = \"info\",\n concurrency: int = None,\n pool: str = None,\n):\n parser = argparse.ArgumentParser(description=\"CeleryWorker\u542f\u52a8\u5668\")\n parser.add_argument(\"-n\", \"--name\", type=str, metavar=\"\", help=\"\u540d\u79f0\")\n parser.add_argument(\"-l\", \"--loglevel\", type=str, default=\"info\", metavar=\"\", help=\"\u65e5\u5fd7\u7b49\u7ea7\")\n parser.add_argument(\"-c\", \"--concurrency\", type=int, default=None, metavar=\"\", help=\"\u5e76\u53d1\u6570\")\n parser.add_argument(\"-P\", \"--pool\", type=str, default=None, metavar=\"\", help=\"\u5e76\u53d1\u6a21\u578b\")\n args = parser.parse_args()\n name = args.name or name\n loglevel = args.loglevel or loglevel\n concurrency = args.concurrency or concurrency\n pool = args.pool or pool\n if pool is None:\n if platform.system().lower().startswith(\"win\"):\n pool = 'gevent'\n if not concurrency:\n concurrency = 100\n else:\n pool = 'prefork'\n if not concurrency:\n concurrency = cpu_count()\n command = [\n \"celery\",\n \"-A\",\n f\"app_celery.consumer.workers.{name}\",\n \"worker\",\n f\"--loglevel={loglevel}\",\n f\"--concurrency={concurrency}\",\n f\"--pool={pool}\",\n ]\n subprocess.run(\n command,\n check=True,\n )\n\n\nif __name__ == '__main__':\n main(\n name=\"ping\",\n )\n",
8
8
  "runserver.py": "\"\"\"\n@author axiner\n@version v1.0.0\n@created 2024/07/29 22:22\n@abstract runserver\uff08\u66f4\u591a\u53c2\u6570\u8bf7\u81ea\u884c\u6307\u5b9a\uff09\n@description\n@history\n\"\"\"\nimport argparse\nimport subprocess\nimport sys\n\nimport uvicorn\n\n\ndef run_by_unicorn(\n host: str,\n port: int,\n workers: int,\n log_level: str,\n is_reload: bool,\n):\n log_config = {\n \"version\": 1,\n \"disable_existing_loggers\": False,\n \"formatters\": {\n \"default\": {\n \"()\": \"uvicorn.logging.DefaultFormatter\",\n \"fmt\": \"%(asctime)s %(levelname)s %(filename)s:%(lineno)d %(message)s\",\n \"use_colors\": None\n },\n \"access\": {\n \"()\": \"uvicorn.logging.AccessFormatter\",\n \"fmt\": \"%(asctime)s %(levelname)s %(client_addr)s - \\\"%(request_line)s\\\" %(status_code)s\"\n }\n },\n \"handlers\": {\n \"default\": {\n \"formatter\": \"default\",\n \"class\": \"logging.StreamHandler\",\n \"stream\": \"ext://sys.stderr\"\n },\n \"access\": {\n \"formatter\": \"access\",\n \"class\": \"logging.StreamHandler\",\n \"stream\": \"ext://sys.stdout\"\n }\n },\n \"loggers\": {\n \"uvicorn\": {\n \"handlers\": [\n \"default\"\n ],\n \"level\": \"INFO\",\n \"propagate\": False\n },\n \"uvicorn.error\": {\n \"level\": \"INFO\"\n },\n \"uvicorn.access\": {\n \"handlers\": [\n \"access\"\n ],\n \"level\": \"INFO\",\n \"propagate\": False\n }\n }\n }\n uvicorn.run(\n app=\"app.main:app\",\n host=host,\n port=port,\n workers=workers,\n log_level=log_level,\n log_config=log_config,\n reload=is_reload,\n )\n\n\ndef run_by_gunicorn(\n host: str,\n port: int,\n workers: int,\n log_level: str,\n is_reload: bool,\n):\n cmd = (\n \"gunicorn app.main:app \"\n \"--worker-class=uvicorn.workers.UvicornWorker \"\n \"--bind={host}:{port} \"\n \"--workers={workers} \"\n \"--log-level={log_level} \"\n \"--access-logfile=- \"\n \"--error-logfile=- \"\n .format(\n host=host,\n port=port,\n workers=workers,\n log_level=log_level,\n )\n )\n if is_reload:\n cmd += f\" --reload\"\n subprocess.run(cmd, shell=True)\n\n\ndef main(\n host: str,\n port: int,\n workers: int,\n log_level: str,\n is_reload: bool,\n is_gunicorn: bool,\n):\n parser = argparse.ArgumentParser(description=\"App\u542f\u52a8\u5668\")\n parser.add_argument(\"--host\", type=str, metavar=\"\", help=\"host\")\n parser.add_argument(\"--port\", type=int, metavar=\"\", help=\"port\")\n parser.add_argument(\"--workers\", type=int, metavar=\"\", help=\"\u8fdb\u7a0b\u6570\")\n parser.add_argument(\"--log-level\", type=str, metavar=\"\", help=\"\u65e5\u5fd7\u7b49\u7ea7\")\n parser.add_argument(\"--is-reload\", action=\"store_true\", help=\"\u662f\u5426reload\")\n parser.add_argument(\"--is-gunicorn\", action=\"store_true\", help=\"\u662f\u5426gunicorn\")\n args = parser.parse_args()\n kwargs = {\n \"host\": args.host or host,\n \"port\": args.port or port,\n \"workers\": args.workers or workers,\n \"log_level\": args.log_level or log_level,\n \"is_reload\": args.is_reload or is_reload,\n }\n if (args.is_gunicorn or is_gunicorn) and not sys.platform.lower().startswith(\"win\"):\n try:\n import gunicorn # noqa\n except ImportError:\n sys.stderr.write(\"gunicorn\u672a\u627e\u5230\uff0c\u6b63\u5728\u5c1d\u8bd5\u81ea\u52a8\u5b89\u88c5...\\n\")\n try:\n subprocess.run(\n [\"pip\", \"install\", \"gunicorn\"],\n check=True,\n stdout=subprocess.PIPE,\n stderr=subprocess.PIPE)\n sys.stderr.write(\"gunicorn\u5b89\u88c5\u6210\u529f\\n\")\n except subprocess.CalledProcessError as e:\n sys.stderr.write(f\"gunicorn\u5b89\u88c5\u5931\u8d25: {e.stderr.decode().strip()}\\n\")\n raise\n run_by_gunicorn(**kwargs)\n else:\n run_by_unicorn(**kwargs)\n\n\nif __name__ == '__main__':\n main(\n host=\"0.0.0.0\",\n port=8000,\n workers=3,\n log_level=\"debug\",\n is_reload=False, # \u9002\u7528\u4e8edev\n is_gunicorn=False, # \u4e0d\u652f\u6301win\n )\n",
9
9
  "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 api,\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.app_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.yaml_name}'\")\n g.logger.info(f\"Application title '{g.config.app_title}'\")\n g.logger.info(f\"Application version '{g.config.app_version}'\")\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.app_title,\n summary=g.config.app_summary,\n description=g.config.app_description,\n version=g.config.app_version,\n debug=g.config.app_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# #\napi.register_routers(app)\nmiddleware.register_middlewares(app)\n",
10
10
  "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",
@@ -40,14 +40,14 @@
40
40
  "app/utils/db_async.py": "from sqlalchemy import (\n select,\n func,\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 query_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 query_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 query_total(\n session,\n model,\n filter_by: dict = None,\n) -> int:\n query = select(func.count()).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 query_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",
41
41
  "app/utils/__init__.py": "\"\"\"\nutils\n\"\"\"\n",
42
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",
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 --queues=ping`\n\n### \u6ce8\u610f\uff1a\n\n- \u6700\u597d\u4e0e`app`\u89e3\u8026\uff0c\u5373\uff1a\n - \u53ea\u6709`app`\u5355\u5411\u8c03\u7528`app_celery`\n - \u4f46`app_celery`\u4e0d\u8c03\u7528`app`",
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\u6709`app`\u5355\u5411\u8c03\u7528`app_celery`\n - \u4f46`app_celery`\u4e0d\u8c03\u7528`app`",
44
44
  "app_celery/requirements.txt": "# -*- coding: utf-8 -*-\n# Python>=3.11\ntoollib==1.7.8\npython-dotenv==1.1.1\nPyYAML==6.0.2\npydantic==2.11.9\ncelery==5.5.3\nredis==6.4.0\ngevent==25.9.1",
45
45
  "app_celery/__init__.py": "\"\"\"\n@author axiner\n@version v0.0.1\n@created 2025/09/20 10:10\n@abstract app-celery\n@description\n@history\n\"\"\"\nfrom celery import Celery\n\nfrom app_celery.conf import config\n\n\ndef make_celery(include: list = None, configs: dict = None):\n app = Celery(\n main=\"app_celery\",\n broker=config.celery_broker_url,\n backend=config.celery_backend_url,\n include=include,\n )\n app.conf.update(\n timezone=config.celery_timezone,\n enable_utc=config.celery_enable_utc,\n task_serializer=config.celery_task_serializer,\n result_serializer=config.celery_result_serializer,\n accept_content=config.celery_accept_content,\n celery_task_ignore_result=config.celery_task_ignore_result,\n celery_result_expire=config.celery_result_expire,\n celery_task_track_started=config.celery_task_track_started,\n worker_concurrency=config.celery_worker_concurrency,\n worker_prefetch_multiplier=config.celery_worker_prefetch_multiplier,\n worker_max_tasks_per_child=config.celery_worker_max_tasks_per_child,\n broker_connection_retry_on_startup=config.celery_broker_connection_retry_on_startup,\n task_reject_on_worker_lost=config.celery_task_reject_on_worker_lost,\n )\n if configs:\n app.conf.update(configs)\n return app\n",
46
46
  "app_celery/consumer/__init__.py": "\"\"\"\n\u6d88\u8d39\u8005\n\"\"\"\nimport re\nfrom pathlib import Path\n\nfrom app_celery import make_celery\n\n\ndef autodiscover_task_modules(\n task_name: str = \"tasks\",\n task_module: str = \"app_celery.consumer.tasks\",\n) -> list:\n \"\"\"\n \u81ea\u52a8\u53d1\u73b0\u4efb\u52a1\u6a21\u5757\n - \u53ef\u5728\u6a21\u5757\u4e2d\u52a0\u5165`_active = False`\u6765\u53d6\u6d88\u6fc0\u6d3b\n \"\"\"\n task_modules = []\n active_pat = re.compile(r\"^_active\\s*=\\s*False\\s*(?:#.*)?$\", re.MULTILINE)\n for p in Path(__file__).parent.joinpath(task_name).rglob(\"*.py\"):\n if p.stem == \"__init__\":\n continue\n if active_pat.search(p.read_text(encoding=\"utf-8\")):\n continue\n task_modules.append(f\"{task_module}.{p.stem}\")\n return task_modules\n\n\ncelery_app = make_celery(\n include=autodiscover_task_modules()\n)\n",
47
47
  "app_celery/consumer/tasks/beat_ping.py": "import logging\n\nfrom celery.schedules import crontab\n\nfrom app_celery.consumer import celery_app\n\nlogger = logging.getLogger(__name__)\n\ncelery_app.conf.beat_schedule.setdefault(\n 'beat_ping', {\n 'task': 'app_celery.consumer.tasks.beat_ping.ping',\n 'schedule': crontab(minute='*/2'), # \u6bcfx\u5206\u949f\u6267\u884c\u4e00\u6b21\n 'options': {'queue': 'beat_ping'}\n }\n)\n\n\n@celery_app.task(\n bind=True,\n autoretry_for=(Exception,),\n max_retries=3,\n retry_backoff=True,\n retry_backoff_max=300,\n retry_jitter=True,\n time_limit=360,\n soft_time_limit=300,\n acks_late=True,\n)\ndef ping(self, text: str = \"\u8fd9\u662f\u4e00\u4e2a\u5b9a\u65f6\u4efb\u52a1\u6d4b\u8bd5\"):\n logger.info(f\"pong: {text}\")\n",
48
48
  "app_celery/consumer/tasks/ping.py": "import logging\n\nfrom app_celery.consumer import celery_app\n\nlogger = logging.getLogger(__name__)\n\n\n@celery_app.task(\n bind=True,\n autoretry_for=(Exception,),\n max_retries=3,\n retry_backoff=True,\n retry_backoff_max=300,\n retry_jitter=True,\n time_limit=360,\n soft_time_limit=300,\n acks_late=True,\n)\ndef ping(self, text: str = \"\u8fd9\u662f\u4e00\u4e2a\u5f02\u6b65\u4efb\u52a1\u6d4b\u8bd5\"):\n logger.info(f\"pong: {text}\")\n",
49
49
  "app_celery/consumer/tasks/__init__.py": "\"\"\"\n\u5f02\u6b65\u4efb\u52a1\n\"\"\"\n",
50
- "app_celery/consumer/workers/beats.py": "from app_celery.consumer import celery_app\n\ncelery_app.conf.update( # \u5efa\u8bae\u6240\u6709\u7684\u5b9a\u65f6\u4efb\u52a1\u90fd\u653e\u5728\u8fd9\u4e2aworker\u4e2d\u542f\u52a8\n task_queues={\n \"beat_ping\": {\n \"exchange_type\": \"direct\",\n \"exchange\": \"beat_ping\",\n \"routing_key\": \"beat_ping\",\n },\n },\n task_routes={\n \"app_celery.consumer.tasks.beat_ping.ping\": {\"queue\": \"beat_ping\"},\n }\n)\n",
50
+ "app_celery/consumer/workers/beat_ping.py": "from app_celery.consumer import celery_app\n\ncelery_app.conf.update(\n task_queues={\n \"beat_ping\": {\n \"exchange_type\": \"direct\",\n \"exchange\": \"beat_ping\",\n \"routing_key\": \"beat_ping\",\n },\n },\n task_routes={\n \"app_celery.consumer.tasks.beat_ping.ping\": {\"queue\": \"beat_ping\"},\n }\n)\n",
51
51
  "app_celery/consumer/workers/ping.py": "from app_celery.consumer import celery_app\n\ncelery_app.conf.update(\n task_queues={\n \"ping\": {\n \"exchange_type\": \"direct\",\n \"exchange\": \"ping\",\n \"routing_key\": \"ping\",\n },\n },\n task_routes={\n \"app_celery.consumer.tasks.ping.ping\": {\"queue\": \"ping\"},\n }\n)\n",
52
52
  "app_celery/consumer/workers/__init__.py": "\"\"\"\n\u5de5\u4f5c\u8005\n\"\"\"",
53
53
  "app_celery/producer/publisher.py": "import logging\n\nfrom app_celery.producer import celery_app\nfrom app_celery.producer.registry import AllTasks\n\nlogger = logging.getLogger(__name__)\n\n\ndef publish(task_label: str, *args, **kwargs):\n \"\"\"\u53d1\u5e03\u4efb\u52a1\"\"\"\n if task_label not in AllTasks:\n raise ValueError(f\"UNKNOWN TASK: {task_label}\")\n task_params = AllTasks[task_label]\n result = celery_app.send_task(\n name=task_params.name,\n queue=task_params.queue,\n args=args,\n kwargs=kwargs,\n **task_params.options,\n )\n logger.info(f\"PUBLISH TASK: {task_label} | ID={result.id} | QUEUE={task_params.queue}\")\n return result.id\n",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastapi-scaff
3
- Version: 0.2.3
3
+ Version: 0.2.4
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=6pCpB2QWhGMY5b9DyZddQ5Dhwpd3N62kXT1AxMx1IOs,120
2
+ fastapi_scaff/__main__.py,sha256=T5ODMU9Ge5jDz0F1EXjbvKhNZPMWWy2gmln8RoXwtPY,13220
3
+ fastapi_scaff/_api_tpl.json,sha256=8oQC2cb9yD1azuOpxvTeY98hxq0U7lXJB0cd_D6jCe4,6795
4
+ fastapi_scaff/_project_tpl.json,sha256=FEoe8HQdEnVCyIh6x03dwwGBQKSaJ3ATCJyrDXVXPhA,89822
5
+ fastapi_scaff-0.2.4.dist-info/licenses/LICENSE,sha256=A5H6q7zd1QrL3iVs1KLsBOG0ImV-t9PpPspM4x-4Ea8,1069
6
+ fastapi_scaff-0.2.4.dist-info/METADATA,sha256=0XBiA5nXk3t6ewkBARRUgKGrS98fGRQS5tRCOZf_J6I,3503
7
+ fastapi_scaff-0.2.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
8
+ fastapi_scaff-0.2.4.dist-info/entry_points.txt,sha256=kzs28nmpRWVCmWmZav3X7u7YOIOEir3sCkLnvQKTJbY,62
9
+ fastapi_scaff-0.2.4.dist-info/top_level.txt,sha256=LeyfUxMRhdbRHcYoH37ftfdspyZ8V3Uut2YBaTCzq2k,14
10
+ fastapi_scaff-0.2.4.dist-info/RECORD,,
@@ -1,10 +0,0 @@
1
- fastapi_scaff/__init__.py,sha256=S-JLukhbRpwd4EJ_8xvt99TED0dRgYRSeRWHnexIY5s,120
2
- fastapi_scaff/__main__.py,sha256=T5ODMU9Ge5jDz0F1EXjbvKhNZPMWWy2gmln8RoXwtPY,13220
3
- fastapi_scaff/_api_tpl.json,sha256=8oQC2cb9yD1azuOpxvTeY98hxq0U7lXJB0cd_D6jCe4,6795
4
- fastapi_scaff/_project_tpl.json,sha256=BWvtn3eVXo4i9nuL1S1Yp5PEy6R0YNF7SurqdtveAGk,90181
5
- fastapi_scaff-0.2.3.dist-info/licenses/LICENSE,sha256=A5H6q7zd1QrL3iVs1KLsBOG0ImV-t9PpPspM4x-4Ea8,1069
6
- fastapi_scaff-0.2.3.dist-info/METADATA,sha256=1YAaMLAHNNPvML2RXPwZkhczlujy17S7iIjAWfQ7Fk4,3503
7
- fastapi_scaff-0.2.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
8
- fastapi_scaff-0.2.3.dist-info/entry_points.txt,sha256=kzs28nmpRWVCmWmZav3X7u7YOIOEir3sCkLnvQKTJbY,62
9
- fastapi_scaff-0.2.3.dist-info/top_level.txt,sha256=LeyfUxMRhdbRHcYoH37ftfdspyZ8V3Uut2YBaTCzq2k,14
10
- fastapi_scaff-0.2.3.dist-info/RECORD,,