diff --git a/{{cookiecutter.project_slug}}/README.md b/{{cookiecutter.project_slug}}/README.md index 1b79f88..ed9409a 100644 --- a/{{cookiecutter.project_slug}}/README.md +++ b/{{cookiecutter.project_slug}}/README.md @@ -8,6 +8,14 @@ The only dependencies for this project should be docker and docker-compose. ### Quick Start +1. 创建配置文件 + +```bash +cp config.toml.template config.toml +``` + +2. 启动项目 + Starting the project with hot-reloading enabled (the first time it will take a while): diff --git a/{{cookiecutter.project_slug}}/app/__main__.py b/{{cookiecutter.project_slug}}/app/__main__.py new file mode 100644 index 0000000..26d2b1d --- /dev/null +++ b/{{cookiecutter.project_slug}}/app/__main__.py @@ -0,0 +1,11 @@ +from app.core import config + + +def main(): + """Main entry point for the application.""" + print(f"Welcome to {config.APP_NAME}!") + print(f"Debug mode: {config.DEBUG}") + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/{{cookiecutter.project_slug}}/core/__init__.py b/{{cookiecutter.project_slug}}/app/core/__init__.py similarity index 100% rename from {{cookiecutter.project_slug}}/core/__init__.py rename to {{cookiecutter.project_slug}}/app/core/__init__.py diff --git a/{{cookiecutter.project_slug}}/app/core/config.py b/{{cookiecutter.project_slug}}/app/core/config.py new file mode 100644 index 0000000..02d6bec --- /dev/null +++ b/{{cookiecutter.project_slug}}/app/core/config.py @@ -0,0 +1,37 @@ +from pathlib import Path +from typing import Optional, Tuple, Type + +from pydantic_settings import ( + BaseSettings, + PydanticBaseSettingsSource, + SettingsConfigDict, + TomlConfigSettingsSource, +) + +class Settings(BaseSettings): + """Application settings.""" + + # 应用配置 + APP_NAME: str = "{{ cookiecutter.project_name }}" + DEBUG: bool = False + SECRET_KEY: str = "{{ cookiecutter.secret_key }}" + + # 路径配置 + BASE_DIR: Path = Path(__file__).parent.parent.parent + + model_config = SettingsConfigDict(toml_file=BASE_DIR / "config.toml") + + @classmethod + def settings_customise_sources( + cls, + settings_cls: Type[BaseSettings], + init_settings: PydanticBaseSettingsSource, + env_settings: PydanticBaseSettingsSource, + dotenv_settings: PydanticBaseSettingsSource, + file_secret_settings: PydanticBaseSettingsSource, + ) -> Tuple[PydanticBaseSettingsSource, ...]: + return (TomlConfigSettingsSource(settings_cls),) + + +# 创建全局设置实例 +config = Settings() diff --git a/{{cookiecutter.project_slug}}/utils/__init__.py b/{{cookiecutter.project_slug}}/app/utils/__init__.py similarity index 100% rename from {{cookiecutter.project_slug}}/utils/__init__.py rename to {{cookiecutter.project_slug}}/app/utils/__init__.py diff --git a/{{cookiecutter.project_slug}}/app/utils/stable.py b/{{cookiecutter.project_slug}}/app/utils/stable.py new file mode 100644 index 0000000..a7ad940 --- /dev/null +++ b/{{cookiecutter.project_slug}}/app/utils/stable.py @@ -0,0 +1,48 @@ +import asyncio +import datetime +from functools import wraps + +from loguru import logger + + +def retry_async(max_retries=3, initial_delay=1, exceptions=(Exception,)): + def decorator(func): + @wraps(func) + async def wrapper(*args, **kwargs): + attempt = 0 + delay = initial_delay + while attempt < max_retries: + try: + return await func(*args, **kwargs) + except exceptions as e: + logger.info( + f"{e=}, attempt {attempt + 1}/{max_retries}, will retry in {delay} seconds" + ) + logger.exception(f"{e=}") + attempt += 1 + if attempt >= max_retries: + logger.info("Max retries reached, aborting") + raise e + await asyncio.sleep(delay) + delay *= 2 # 延迟时间加倍 + + return wrapper + + return decorator + + +if __name__ == "__main__": + + @retry_async() + async def main(): + now = datetime.datetime.now() + logger.info(f"{now=}") + # Fail twice then succeed + if main.counter < 2: + main.counter += 1 + raise Exception(now) + return now + + main.counter = 0 + + asyncio.run(main()) diff --git a/{{cookiecutter.project_slug}}/config.toml.template b/{{cookiecutter.project_slug}}/config.toml.template new file mode 100644 index 0000000..45e9403 --- /dev/null +++ b/{{cookiecutter.project_slug}}/config.toml.template @@ -0,0 +1,5 @@ +# Application configuration +app_name = "{{ cookiecutter.project_name }}" +debug = false + +# Add your custom configuration here \ No newline at end of file diff --git a/{{cookiecutter.project_slug}}/core/config.py b/{{cookiecutter.project_slug}}/core/config.py deleted file mode 100644 index 8a1df45..0000000 --- a/{{cookiecutter.project_slug}}/core/config.py +++ /dev/null @@ -1,5 +0,0 @@ -import os - -PROJECT_NAME = "{{cookiecutter.project_name}}" - -BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) diff --git a/{{cookiecutter.project_slug}}/main.py b/{{cookiecutter.project_slug}}/main.py index 7761001..0fc8a8e 100644 --- a/{{cookiecutter.project_slug}}/main.py +++ b/{{cookiecutter.project_slug}}/main.py @@ -1,4 +1,5 @@ -from core import BASE_DIR +from app.core import config if __name__ == "__main__": - print(BASE_DIR) + print(config.BASE_DIR) + diff --git a/{{cookiecutter.project_slug}}/pyproject.toml b/{{cookiecutter.project_slug}}/pyproject.toml new file mode 100644 index 0000000..a688d3d --- /dev/null +++ b/{{cookiecutter.project_slug}}/pyproject.toml @@ -0,0 +1,26 @@ +[build-system] +requires = ["setuptools>=42"] +build-backend = "setuptools.build_meta" + +[project] +name = "{{ cookiecutter.project_slug }}" +version = "0.1.0" +description = "{{ cookiecutter.project_short_description }}" +authors = [ + {name = "{{ cookiecutter.github_username }}", email = "{{ cookiecutter.email }}"} +] +dependencies = [ + "loguru~=0.7.2", + "pydantic~=2.10.5", + "pydantic-settings[toml]~=2.6.1" +] + +[project.optional-dependencies] +dev = [ + "pytest>=7.0.0" +] + +[tool.pytest.ini_options] +testpaths = ["tests"] +python_files = ["test_*.py"] +addopts = "-ra -q" diff --git a/{{cookiecutter.project_slug}}/requirements-dev.txt b/{{cookiecutter.project_slug}}/requirements-dev.txt new file mode 100644 index 0000000..fcb4969 --- /dev/null +++ b/{{cookiecutter.project_slug}}/requirements-dev.txt @@ -0,0 +1,3 @@ +-r requirements.txt + +pytest>=7.0.0 diff --git a/{{cookiecutter.project_slug}}/requirements.txt b/{{cookiecutter.project_slug}}/requirements.txt index f1d0b55..ccf1287 100644 --- a/{{cookiecutter.project_slug}}/requirements.txt +++ b/{{cookiecutter.project_slug}}/requirements.txt @@ -1,2 +1,3 @@ -loguru -rich \ No newline at end of file +loguru~=0.7.2 +pydantic~=2.10.5 +pydantic-settings[toml]~=2.6.1 diff --git a/{{cookiecutter.project_slug}}/tests/conftest.py b/{{cookiecutter.project_slug}}/tests/conftest.py new file mode 100644 index 0000000..a2dced8 --- /dev/null +++ b/{{cookiecutter.project_slug}}/tests/conftest.py @@ -0,0 +1,7 @@ +import os +import sys +from pathlib import Path + +# Add the src directory to the Python path +src_path = Path(__file__).parent.parent / "app" +sys.path.insert(0, str(src_path)) diff --git a/{{cookiecutter.project_slug}}/tests/test_config.py b/{{cookiecutter.project_slug}}/tests/test_config.py new file mode 100644 index 0000000..5b73b63 --- /dev/null +++ b/{{cookiecutter.project_slug}}/tests/test_config.py @@ -0,0 +1,10 @@ +from pathlib import Path + +import pytest + +from app.core.config import Settings + + +def test_settings_default_values(): + settings = Settings() + assert settings.APP_NAME == "{{ cookiecutter.project_name }}"