#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ MyBoot CLI 工具 提供项目模板初始化功能 """ import click import sys from pathlib import Path @click.group() def cli(): """MyBoot 命令行工具 - 项目模板初始化""" pass @cli.command() @click.option('--name', prompt='项目名称', help='项目名称') @click.option('--dir', default='.', help='项目目录(默认为当前目录)') @click.option('--template', type=click.Choice(['basic', 'api', 'full']), default='basic', help='项目模板: basic(基础), api(API项目), full(完整项目)') @click.option('--force', is_flag=True, help='如果目录已存在则覆盖') def init(name: str, dir: str, template: str, force: bool): """初始化新的 MyBoot 项目""" project_dir = Path(dir) / name # 检查目录是否存在 if project_dir.exists() and not force: click.echo(f"❌ 错误: 目录 '{project_dir}' 已存在", err=True) click.echo(" 使用 --force 选项可以覆盖现有目录") sys.exit(1) click.echo(f"📦 正在初始化项目: {name}") click.echo(f" 模板: {template}") click.echo(f" 目录: {project_dir}") click.echo() try: # 创建目录结构 dirs = ['app', 'app/api', 'app/service', 'app/model', 'app/jobs', 'app/client', 'conf', 'tests'] for d in dirs: (project_dir / d).mkdir(parents=True, exist_ok=True) # 创建 __init__.py init_file = project_dir / d / '__init__.py' if not init_file.exists(): init_file.write_text('', encoding='utf-8') click.echo("✓ 创建目录结构") # 创建配置文件 config_content = f"""# {name} 配置文件 # 应用配置 app: name: "{name}" version: "0.1.0" # 服务器配置 server: port: 8000 reload: true workers: 1 keep_alive_timeout: 5 graceful_timeout: 30 response_format: enabled: true exclude_paths: - "/docs" # 日志配置 logging: level: "INFO" # 任务调度配置 scheduler: enabled: true timezone: "UTC" max_workers: 10 """ config_file = project_dir / 'conf' / 'config.yaml' config_file.write_text(config_content, encoding='utf-8') click.echo("✓ 创建配置文件: conf/config.yaml") # 创建主应用文件(放在根目录) app_content = f'''"""主应用文件""" from myboot.core.application import create_app app = create_app(name="{name}") if __name__ == "__main__": app.run() ''' main_file = project_dir / 'main.py' main_file.write_text(app_content, encoding='utf-8') click.echo("✓ 创建主应用文件: main.py") # 根据模板创建不同的文件 if template in ['api', 'full']: # 创建示例路由 api_content = '''"""API 路由示例""" from myboot.core.decorators import rest_controller, get @rest_controller('/api') class HelloController: """Hello 控制器""" @get('/') def hello(self): """Hello World 接口 - GET /api""" return {"message": "Hello, MyBoot!", "status": "success"} @get('/health') def health(self): """健康检查接口 - GET /api/health""" return {"status": "healthy", "service": "running"} ''' routes_file = project_dir / 'app' / 'api' / 'routes.py' routes_file.write_text(api_content, encoding='utf-8') click.echo("✓ 创建示例路由: app/api/routes.py") if template == 'full': # 创建示例服务 service_content = '''"""服务层示例""" from typing import Dict, Any class UserService: """用户服务示例""" def get_user(self, user_id: int) -> Dict[str, Any]: """获取用户信息""" return { "id": user_id, "name": "示例用户", "email": "user@example.com" } def create_user(self, name: str, email: str) -> Dict[str, Any]: """创建用户""" return { "id": 1, "name": name, "email": email, "status": "created" } ''' service_file = project_dir / 'app' / 'service' / 'user_service.py' service_file.write_text(service_content, encoding='utf-8') click.echo("✓ 创建示例服务: app/service/user_service.py") # 创建示例模型 model_content = '''"""数据模型示例""" from pydantic import BaseModel, ConfigDict, EmailStr from typing import Optional class User(BaseModel): """用户模型""" model_config = ConfigDict( json_schema_extra={ "example": { "name": "张三", "email": "zhangsan@example.com" } } ) id: Optional[int] = None name: str email: EmailStr status: str = "active" ''' model_file = project_dir / 'app' / 'model' / 'user.py' model_file.write_text(model_content, encoding='utf-8') click.echo("✓ 创建示例模型: app/model/user.py") # 创建示例客户端 client_content = '''"""客户端示例""" from typing import Dict, Any import requests class ApiClient: """API 客户端示例""" def __init__(self, base_url: str): """初始化客户端""" self.base_url = base_url def get(self, endpoint: str) -> Dict[str, Any]: """GET 请求""" response = requests.get(f"{self.base_url}/{endpoint}") response.raise_for_status() return response.json() def post(self, endpoint: str, data: Dict[str, Any]) -> Dict[str, Any]: """POST 请求""" response = requests.post(f"{self.base_url}/{endpoint}", json=data) response.raise_for_status() return response.json() ''' client_file = project_dir / 'app' / 'client' / 'api_client.py' client_file.write_text(client_content, encoding='utf-8') click.echo("✓ 创建示例客户端: app/client/api_client.py") # 创建示例定时任务 job_content = '''"""定时任务示例""" from myboot.core.decorators import cron @cron("0 */5 * * * *") # 每5分钟执行一次 def cleanup_task(): """清理任务示例""" print("执行清理任务...") @cron("0 0 * * * *") # 每小时执行一次 def hourly_task(): """每小时任务示例""" print("执行每小时任务...") ''' job_file = project_dir / 'app' / 'jobs' / 'tasks.py' job_file.write_text(job_content, encoding='utf-8') click.echo("✓ 创建示例任务: app/jobs/tasks.py") # 创建 README readme_content = f"""# {name} MyBoot 项目 ## 快速开始 ### 安装依赖 ```bash pip install myboot ``` ### 运行应用 ```bash # 使用默认设置启动 python main.py # 或者使用 myboot 命令(如果已安装) myboot run ``` ### 开发模式 ```bash # 启用自动重载 python main.py --reload # 或者 myboot dev --reload ``` ## 项目结构 ``` {name}/ ├── main.py # 应用入口 ├── pyproject.toml # 项目配置文件 ├── .gitignore # Git 忽略文件 ├── app/ # 应用代码 │ ├── api/ # API 路由 │ ├── service/ # 业务逻辑层 │ ├── model/ # 数据模型 │ ├── jobs/ # 定时任务 │ └── client/ # 客户端(第三方API调用等) ├── conf/ # 配置文件 │ └── config.yaml # 主配置文件 └── tests/ # 测试代码 ``` ## 配置说明 配置文件位于 `conf/config.yaml`,支持以下配置: - **app**: 应用配置(名称、版本等) - **server**: 服务器配置(端口、工作进程等) - **logging**: 日志配置 - **scheduler**: 任务调度配置 ## 更多信息 - [MyBoot 文档](https://github.com/your-org/myboot) - [快速开始指南](https://github.com/your-org/myboot/docs) """ readme_file = project_dir / 'README.md' readme_file.write_text(readme_content, encoding='utf-8') click.echo("✓ 创建 README: README.md") # 创建 .gitignore gitignore_content = """# Python __pycache__/ *.py[cod] *$py.class *.so .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ *.egg-info/ .installed.cfg *.egg # Virtual Environment venv/ ENV/ env/ .venv # IDE .vscode/ .idea/ *.swp *.swo *~ # Logs *.log logs/ # OS .DS_Store Thumbs.db # MyBoot .pytest_cache/ .coverage htmlcov/ """ gitignore_file = project_dir / '.gitignore' gitignore_file.write_text(gitignore_content, encoding='utf-8') click.echo("✓ 创建 .gitignore") # 创建 pyproject.toml project_name = name.lower().replace(' ', '-').replace('_', '-') pyproject_content = f"""[project] name = "{project_name}" version = "0.1.0" description = "{name} - MyBoot 项目" authors = [ {{name = "Your Name", email = "your.email@example.com"}} ] readme = "README.md" license = {{text = "MIT"}} requires-python = ">=3.9" keywords = ["myboot", "web", "api"] classifiers = [ "Development Status :: 3 - Alpha", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", ] dependencies = [ "myboot>=0.1.0", ] [project.optional-dependencies] dev = [ "pytest>=7.0", "pytest-cov>=4.0", "pytest-asyncio>=0.21.0", "black>=23.0", "isort>=5.12", "flake8>=6.0", ] [build-system] requires = ["hatchling"] build-backend = "hatchling.build" [tool.hatch.build.targets.wheel] packages = ["app"] [tool.black] line-length = 88 target-version = ['py39', 'py310', 'py311', 'py312'] include = '\\.pyi?$' [tool.isort] profile = "black" line_length = 88 known_first_party = ["app"] [tool.pytest.ini_options] testpaths = ["tests"] python_files = ["test_*.py", "*_test.py"] python_classes = ["Test*"] python_functions = ["test_*"] addopts = [ "--strict-markers", "--verbose", ] """ pyproject_file = project_dir / 'pyproject.toml' pyproject_file.write_text(pyproject_content, encoding='utf-8') click.echo("✓ 创建 pyproject.toml") # 创建 Docker 支持文件(issue #1) dockerfile_content = f"""# 多阶段构建:构建层安装依赖,运行层只保留运行所需内容 FROM python:3.12-slim AS builder COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv WORKDIR /app COPY pyproject.toml ./ # 如有 uv.lock 一并复制可获得可复现构建(无锁文件时此层退化为解析安装) RUN uv venv /opt/venv && \\ VIRTUAL_ENV=/opt/venv uv pip install --no-cache . FROM python:3.12-slim ENV VIRTUAL_ENV=/opt/venv \\ PATH="/opt/venv/bin:$PATH" \\ PYTHONUNBUFFERED=1 WORKDIR /app COPY --from=builder /opt/venv /opt/venv COPY . . EXPOSE 8000 # 配置可用环境变量覆盖(__ 为层级分隔符),如 SERVER__PORT=8080 CMD ["python", "main.py"] """ dockerfile = project_dir / 'Dockerfile' dockerfile.write_text(dockerfile_content, encoding='utf-8') click.echo("✓ 创建 Dockerfile") dockerignore_content = """__pycache__/ *.py[cod] .venv/ venv/ .git/ .gitignore .pytest_cache/ .coverage htmlcov/ *.log logs/ .vscode/ .idea/ Dockerfile docker-compose.yaml """ dockerignore_file = project_dir / '.dockerignore' dockerignore_file.write_text(dockerignore_content, encoding='utf-8') click.echo("✓ 创建 .dockerignore") compose_content = f"""services: {project_name}: build: . ports: - "8000:8000" environment: # MyBoot 配置支持环境变量覆盖,__ 为层级分隔符 - LOGGING__LEVEL=INFO # - SERVER__WORKERS=4 # - CONFIG_FILE=/app/conf/config.prod.yaml restart: unless-stopped """ compose_file = project_dir / 'docker-compose.yaml' compose_file.write_text(compose_content, encoding='utf-8') click.echo("✓ 创建 docker-compose.yaml") click.echo() click.echo(f"✅ 项目 '{name}' 初始化完成!") click.echo() click.echo("下一步:") click.echo(f" cd {name}") click.echo(" python main.py") click.echo() click.echo("Docker 部署:") click.echo(f" docker build -t {project_name} .") click.echo(f" docker run -p 8000:8000 {project_name}") click.echo() except Exception as e: click.echo(f"❌ 初始化失败: {e}", err=True) import traceback if '--debug' in sys.argv: traceback.print_exc() sys.exit(1) @cli.command() def info(): """显示 MyBoot 信息""" click.echo("🎯 MyBoot - 类似 Spring Boot 的企业级Python快速开发框架") click.echo() click.echo("✨ 主要特性:") click.echo(" • 快速启动和自动配置") click.echo(" • 约定优于配置") click.echo(" • 高性能 Hypercorn 服务器") click.echo(" • Web API 开发") click.echo(" • 定时任务调度") click.echo(" • 日志管理") click.echo(" • 配置管理") click.echo() click.echo("🚀 快速开始:") click.echo(" myboot init # 初始化新项目") click.echo(" myboot init --template api # 使用 API 模板") click.echo(" myboot init --template full # 使用完整模板") click.echo() if __name__ == '__main__': cli()