PYTHON
scaffold.py🐍python
"""
Project scaffolding commands.
"""
from pathlib import Path
import click
from rich.console import Console
console = Console()
# Project templates
TEMPLATES = {
'python': {
'description': 'Basic Python project',
'files': {
'README.md': '# {name}\n\nA Python project.\n',
'pyproject.toml': '''[project]
name = "{name}"
version = "0.1.0"
description = ""
readme = "README.md"
requires-python = ">=3.10"
dependencies = []
[project.optional-dependencies]
dev = ["pytest", "black", "mypy"]
''',
'src/{name}/__init__.py': '"""Package {name}."""\n',
'src/{name}/main.py': '''"""Main module."""
def main():
"""Entry point."""
print("Hello from {name}!")
if __name__ == "__main__":
main()
''',
'tests/__init__.py': '',
'tests/test_main.py': '''"""Tests for main module."""
from {name}.main import main
def test_main():
"""Test main function runs."""
main() # Should not raise
''',
'.gitignore': '''__pycache__/
*.py[cod]
.venv/
dist/
*.egg-info/
.mypy_cache/
.pytest_cache/
''',
}
},
'fastapi': {
'description': 'FastAPI project',
'files': {
'README.md': '# {name}\n\nA FastAPI project.\n\n## Run\n\n```bash\nuvicorn app.main:app --reload\n```\n',
'requirements.txt': '''fastapi>=0.109.0
uvicorn[standard]>=0.27.0
pydantic>=2.0.0
''',
'app/__init__.py': '',
'app/main.py': '''"""FastAPI application."""
from fastapi import FastAPI
app = FastAPI(title="{name}")
@app.get("/")
async def root():
return {{"message": "Welcome to {name}"}}
@app.get("/health")
async def health():
return {{"status": "healthy"}}
''',
'Dockerfile': '''FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app/ ./app/
EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
''',
'.gitignore': '''__pycache__/
*.py[cod]
.venv/
.env
''',
}
},
'cli': {
'description': 'CLI tool with Click',
'files': {
'README.md': '# {name}\n\nA command-line tool.\n\n## Install\n\n```bash\npip install -e .\n```\n',
'pyproject.toml': '''[project]
name = "{name}"
version = "0.1.0"
dependencies = ["click>=8.0.0", "rich>=13.0.0"]
[project.scripts]
{name} = "{name}.cli:main"
''',
'{name}/__init__.py': '',
'{name}/cli.py': '''"""CLI entry point."""
import click
from rich.console import Console
console = Console()
@click.group()
@click.version_option()
def main():
"""A helpful CLI tool."""
pass
@main.command()
@click.argument("name", default="World")
def hello(name: str):
"""Say hello."""
console.print(f"[green]Hello, {{name}}![/green]")
if __name__ == "__main__":
main()
''',
'.gitignore': '''__pycache__/
*.egg-info/
dist/
''',
}
}
}
@click.command()
@click.argument('template', type=click.Choice(list(TEMPLATES.keys())))
@click.argument('name')
@click.option('--path', '-p', default='.', help='Where to create project')
def new(template: str, name: str, path: str):
"""
Create a new project from template.
Templates: python, fastapi, cli
"""
base_path = Path(path) / name
if base_path.exists():
console.print(f"[red]Error: {base_path} already exists[/red]")
return
template_data = TEMPLATES[template]
console.print(f"Creating [cyan]{template_data['description']}[/cyan]: {name}")
# Create files
for file_path, content in template_data['files'].items():
# Replace placeholders
file_path = file_path.format(name=name)
content = content.format(name=name)
full_path = base_path / file_path
full_path.parent.mkdir(parents=True, exist_ok=True)
full_path.write_text(content)
console.print(f" [green]Created[/green] {file_path}")
console.print(f"\n[bold green]✓ Project created![/bold green]")
console.print(f"\nNext steps:")
console.print(f" cd {name}")
if template == 'python':
console.print(" python -m venv .venv")
console.print(" source .venv/bin/activate")
console.print(" pip install -e '.[dev]'")
elif template == 'fastapi':
console.print(" pip install -r requirements.txt")
console.print(" uvicorn app.main:app --reload")
elif template == 'cli':
console.print(" pip install -e .")
console.print(f" {name} --help")