Initial commit
This commit is contained in:
123
README.md
Normal file
123
README.md
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
# Python Library Template
|
||||||
|
|
||||||
|
A [Cookiecutter](https://github.com/cookiecutter/cookiecutter) template for creating modern Python libraries with uv, ruff, mypy, and pytest.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Modern Python setup** with Python 3.12+ support
|
||||||
|
- **uv** for fast dependency management
|
||||||
|
- **Ruff** for linting and formatting
|
||||||
|
- **MyPy** for type checking
|
||||||
|
- **Pytest** with coverage support
|
||||||
|
- **bump-my-version** for automated version management
|
||||||
|
- **Invoke** tasks for common operations
|
||||||
|
- **Pre-configured** development workflow
|
||||||
|
- **Type hints** support with py.typed marker
|
||||||
|
- **CLAUDE.md** guidance for Claude Code integration
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
1. Install cookiecutter:
|
||||||
|
```bash
|
||||||
|
pipx install cookiecutter
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Generate a new project:
|
||||||
|
```bash
|
||||||
|
cookiecutter https://github.com/your-username/python-lib-template
|
||||||
|
```
|
||||||
|
|
||||||
|
Or locally:
|
||||||
|
```bash
|
||||||
|
cookiecutter /path/to/python-lib-template
|
||||||
|
```
|
||||||
|
|
||||||
|
3. The template will prompt for project details and automatically:
|
||||||
|
- Initialize git repository
|
||||||
|
- Set up uv environment
|
||||||
|
- Run initial linting and formatting
|
||||||
|
- Execute tests to verify setup
|
||||||
|
|
||||||
|
## Template Variables
|
||||||
|
|
||||||
|
| Variable | Description | Example |
|
||||||
|
|----------|-------------|---------|
|
||||||
|
| `project_name` | Human-readable project name | "My Python Library" |
|
||||||
|
| `project_slug` | Repository/directory name | "my-python-library" |
|
||||||
|
| `package_name` | Python package name | "my_python_library" |
|
||||||
|
| `description` | Short project description | "A modern Python library" |
|
||||||
|
| `author_name` | Author's full name | "Your Name" |
|
||||||
|
| `author_email` | Author's email | "your.email@example.com" |
|
||||||
|
| `version` | Initial version | "0.1.0" |
|
||||||
|
| `python_version` | Minimum Python version | "3.12" |
|
||||||
|
|
||||||
|
## Generated Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
your-project/
|
||||||
|
├── CLAUDE.md # Claude Code guidance
|
||||||
|
├── README.md # Project documentation
|
||||||
|
├── pyproject.toml # Project configuration
|
||||||
|
├── tasks.py # Invoke tasks
|
||||||
|
├── src/
|
||||||
|
│ └── your_package/
|
||||||
|
│ ├── __init__.py
|
||||||
|
│ ├── core.py
|
||||||
|
│ └── py.typed
|
||||||
|
├── tests/
|
||||||
|
│ └── test_your_package.py
|
||||||
|
└── examples/
|
||||||
|
└── basic_usage.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development Workflow
|
||||||
|
|
||||||
|
The generated project includes these common tasks:
|
||||||
|
|
||||||
|
### Setup
|
||||||
|
```bash
|
||||||
|
uv sync
|
||||||
|
```
|
||||||
|
|
||||||
|
### Code Quality
|
||||||
|
```bash
|
||||||
|
uv run ruff check --fix # Linting
|
||||||
|
uv run ruff format # Formatting
|
||||||
|
uv run mypy . # Type checking
|
||||||
|
uv run invoke lint # All quality checks
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
```bash
|
||||||
|
uv run pytest # Run tests
|
||||||
|
uv run invoke test # Run tests with coverage
|
||||||
|
```
|
||||||
|
|
||||||
|
### Version Management
|
||||||
|
```bash
|
||||||
|
uv run bump-my-version bump patch # 0.1.0 → 0.1.1
|
||||||
|
uv run bump-my-version bump minor # 0.1.0 → 0.2.0
|
||||||
|
uv run bump-my-version bump major # 0.1.0 → 1.0.0
|
||||||
|
```
|
||||||
|
|
||||||
|
## Template Development
|
||||||
|
|
||||||
|
To modify this template:
|
||||||
|
|
||||||
|
1. Edit files in `{{cookiecutter.project_slug}}/`
|
||||||
|
2. Update variables in `cookiecutter.json`
|
||||||
|
3. Modify the post-generation hook in `hooks/post_gen_project.py`
|
||||||
|
4. Test changes:
|
||||||
|
```bash
|
||||||
|
cookiecutter . --output-dir /tmp
|
||||||
|
```
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- Python 3.12+
|
||||||
|
- uv (automatically installed during generation)
|
||||||
|
- git (for version control)
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT License - see LICENSE file for details.
|
||||||
11
cookiecutter.json
Normal file
11
cookiecutter.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"project_name": "My Python Library",
|
||||||
|
"project_slug": "{{ cookiecutter.project_name.lower().replace(' ', '-').replace('_', '-') }}",
|
||||||
|
"package_name": "{{ cookiecutter.project_slug.replace('-', '_') }}",
|
||||||
|
"description": "A modern Python library",
|
||||||
|
"author_name": "Your Name",
|
||||||
|
"author_email": "your.email@example.com",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"python_version": "3.12",
|
||||||
|
"year": "{% now 'utc', '%Y' %}"
|
||||||
|
}
|
||||||
61
hooks/post_gen_project.py
Executable file
61
hooks/post_gen_project.py
Executable file
@@ -0,0 +1,61 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Post-generation hook for the Python library template."""
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def run_command(cmd: list[str], description: str) -> None:
|
||||||
|
"""Run a command and handle errors."""
|
||||||
|
print(f"Running: {description}")
|
||||||
|
try:
|
||||||
|
result = subprocess.run(cmd, check=True, capture_output=True, text=True)
|
||||||
|
if result.stdout:
|
||||||
|
print(result.stdout)
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
print(f"Error running {description}: {e}")
|
||||||
|
if e.stderr:
|
||||||
|
print(f"Error output: {e.stderr}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Initialize the generated project."""
|
||||||
|
project_dir = Path.cwd()
|
||||||
|
print(f"Setting up project in: {project_dir}")
|
||||||
|
|
||||||
|
# Initialize git repository
|
||||||
|
run_command(["git", "init"], "git init")
|
||||||
|
|
||||||
|
# Sync dependencies with uv
|
||||||
|
run_command(["uv", "sync"], "uv sync")
|
||||||
|
|
||||||
|
# Run initial formatting and linting
|
||||||
|
run_command(["uv", "run", "ruff", "format", "src"], "ruff format")
|
||||||
|
run_command(["uv", "run", "ruff", "check", "--fix", "src"], "ruff check --fix")
|
||||||
|
|
||||||
|
# Run type checking
|
||||||
|
try:
|
||||||
|
run_command(["uv", "run", "mypy", "src"], "mypy type checking")
|
||||||
|
except SystemExit:
|
||||||
|
# mypy might fail on initial template, continue anyway
|
||||||
|
print("MyPy check failed - this is normal for initial template")
|
||||||
|
|
||||||
|
# Run tests to ensure everything works
|
||||||
|
try:
|
||||||
|
run_command(["uv", "run", "pytest"], "pytest")
|
||||||
|
except SystemExit:
|
||||||
|
print("Tests failed - you may need to adjust the generated code")
|
||||||
|
|
||||||
|
print("\n✅ Project setup complete!")
|
||||||
|
print("Next steps:")
|
||||||
|
print("1. Review and customize the generated files")
|
||||||
|
print("2. Add your actual dependencies to pyproject.toml")
|
||||||
|
print("3. Implement your library functionality in src/")
|
||||||
|
print("4. Update tests as needed")
|
||||||
|
print("5. Update README.md with proper documentation")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
69
{{cookiecutter.project_slug}}/CLAUDE.md
Normal file
69
{{cookiecutter.project_slug}}/CLAUDE.md
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
`{{ cookiecutter.project_slug }}` is a Python library for {{ cookiecutter.description.lower() }}.
|
||||||
|
|
||||||
|
## Development Commands
|
||||||
|
|
||||||
|
### Setup
|
||||||
|
```bash
|
||||||
|
uv sync
|
||||||
|
```
|
||||||
|
|
||||||
|
### Code Quality
|
||||||
|
- **Linting and formatting**: `uv run ruff check --fix` and `uv run ruff format`
|
||||||
|
- **Type checking**: `uv run mypy .`
|
||||||
|
- **Combined linting**: `uv run invoke lint` (runs ruff check, ruff format --check, and mypy)
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
- **Run tests**: `uv run pytest`
|
||||||
|
- **Run tests with coverage**: `uv run invoke test`
|
||||||
|
|
||||||
|
### Maintenance
|
||||||
|
- **Clean untracked files**: `uv run invoke clean` (interactive)
|
||||||
|
- **Version bumping**: `uv run bump-my-version bump [patch|minor|major]`
|
||||||
|
|
||||||
|
## Code Architecture
|
||||||
|
|
||||||
|
### Core Components (`src/{{ cookiecutter.package_name }}/core.py`)
|
||||||
|
|
||||||
|
The library implements the main functionality in the core module. Update this section with:
|
||||||
|
|
||||||
|
1. **Key Classes**: Describe main classes and their responsibilities
|
||||||
|
2. **Core Functions**: Document important functions and their purpose
|
||||||
|
3. **Data Flow**: Explain how data flows through the system
|
||||||
|
4. **Dependencies**: List and explain key dependencies
|
||||||
|
|
||||||
|
### Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
src/{{ cookiecutter.package_name }}/
|
||||||
|
├── __init__.py # Package initialization
|
||||||
|
├── core.py # Main functionality
|
||||||
|
└── py.typed # Type hints marker
|
||||||
|
|
||||||
|
examples/
|
||||||
|
└── basic_usage.py # Usage examples
|
||||||
|
|
||||||
|
tests/
|
||||||
|
└── test_{{ cookiecutter.package_name }}.py # Test suite
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development Notes
|
||||||
|
|
||||||
|
- Uses Python {{ cookiecutter.python_version }}+ with modern type hints (PEP 604)
|
||||||
|
- Configured with ruff for linting/formatting and mypy for type checking
|
||||||
|
- Built with uv for dependency management
|
||||||
|
- Includes invoke tasks for common operations
|
||||||
|
- Version {{ cookiecutter.version }} ({{ cookiecutter.year }})
|
||||||
|
|
||||||
|
## Implementation Guidelines
|
||||||
|
|
||||||
|
- Follow test-driven development practices
|
||||||
|
- Use descriptive function names and one-liner docstrings for non-trivial functions
|
||||||
|
- Keep files between 300–500 lines where possible
|
||||||
|
- Don't duplicate code; build upon existing implementations
|
||||||
|
- Always use type hints as supported by Python {{ cookiecutter.python_version }}+
|
||||||
55
{{cookiecutter.project_slug}}/README.md
Normal file
55
{{cookiecutter.project_slug}}/README.md
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
# {{ cookiecutter.project_name }}
|
||||||
|
|
||||||
|
{{ cookiecutter.description }}
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install {{ cookiecutter.project_slug }}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
This project uses `uv` for dependency management and the following tools:
|
||||||
|
|
||||||
|
### Setup
|
||||||
|
```bash
|
||||||
|
uv sync
|
||||||
|
```
|
||||||
|
|
||||||
|
### Code Quality
|
||||||
|
- **Ruff**: Linting and formatting
|
||||||
|
```bash
|
||||||
|
uv run ruff check --fix
|
||||||
|
uv run ruff format
|
||||||
|
```
|
||||||
|
|
||||||
|
- **MyPy**: Type checking
|
||||||
|
```bash
|
||||||
|
uv run mypy .
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
```bash
|
||||||
|
uv run pytest
|
||||||
|
```
|
||||||
|
|
||||||
|
### Version Management
|
||||||
|
- **bump-my-version**: Automated version bumping with git tags
|
||||||
|
```bash
|
||||||
|
uv run bump-my-version bump patch # {{ cookiecutter.version }} → 0.1.1
|
||||||
|
uv run bump-my-version bump minor # {{ cookiecutter.version }} → 0.2.0
|
||||||
|
uv run bump-my-version bump major # {{ cookiecutter.version }} → 1.0.0
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```python
|
||||||
|
from {{ cookiecutter.package_name }} import hello
|
||||||
|
|
||||||
|
print(hello())
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT License - see LICENSE file for details.
|
||||||
22
{{cookiecutter.project_slug}}/examples/basic_usage.py
Normal file
22
{{cookiecutter.project_slug}}/examples/basic_usage.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
"""Basic usage example for {{ cookiecutter.project_name }}."""
|
||||||
|
|
||||||
|
from {{ cookiecutter.package_name }} import hello
|
||||||
|
from {{ cookiecutter.package_name }}.core import process_data, {{ cookiecutter.project_name.replace(' ', '').replace('-', '') }}
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Demonstrate basic usage."""
|
||||||
|
# Basic hello
|
||||||
|
print(hello())
|
||||||
|
|
||||||
|
# Process some data
|
||||||
|
result = process_data("example data")
|
||||||
|
print(result)
|
||||||
|
|
||||||
|
# Use main class
|
||||||
|
instance = {{ cookiecutter.project_name.replace(' ', '').replace('-', '') }}("example")
|
||||||
|
print(instance.run())
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
51
{{cookiecutter.project_slug}}/pyproject.toml
Normal file
51
{{cookiecutter.project_slug}}/pyproject.toml
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
[project]
|
||||||
|
name = "{{ cookiecutter.project_slug }}"
|
||||||
|
version = "{{ cookiecutter.version }}"
|
||||||
|
description = "{{ cookiecutter.description }}"
|
||||||
|
readme = "README.md"
|
||||||
|
authors = [
|
||||||
|
{ name = "{{ cookiecutter.author_name }}", email = "{{ cookiecutter.author_email }}" }
|
||||||
|
]
|
||||||
|
requires-python = ">={{ cookiecutter.python_version }}"
|
||||||
|
dependencies = [
|
||||||
|
# Add your project dependencies here
|
||||||
|
]
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["uv_build>=0.8.8,<0.9.0"]
|
||||||
|
build-backend = "uv_build"
|
||||||
|
|
||||||
|
[dependency-groups]
|
||||||
|
dev = [
|
||||||
|
"bump-my-version>=1.2.1",
|
||||||
|
"invoke>=2.2.0",
|
||||||
|
"mypy>=1.17.1",
|
||||||
|
"pytest>=8.4.1",
|
||||||
|
"pytest-cov>=6.1.0",
|
||||||
|
"ruff>=0.12.8",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.bumpversion]
|
||||||
|
current_version = "{{ cookiecutter.version }}"
|
||||||
|
commit = true
|
||||||
|
tag = true
|
||||||
|
tag_name = "v{new_version}"
|
||||||
|
|
||||||
|
[[tool.bumpversion.files]]
|
||||||
|
filename = "pyproject.toml"
|
||||||
|
search = "version = \"{current_version}\""
|
||||||
|
replace = "version = \"{new_version}\""
|
||||||
|
|
||||||
|
#------------------ruff configuration----------------
|
||||||
|
[tool.ruff.lint]
|
||||||
|
extend-select = ["B", "I", "C4", "TID", "SIM", "PLE", "RUF"]
|
||||||
|
ignore = [
|
||||||
|
"D100", "D101", "D102", "D103", # Missing docstrings
|
||||||
|
"N806", "N803", # Invalid name patterns
|
||||||
|
"G201", # Logging f-string interpolation
|
||||||
|
"ARG001", # Unused function argument
|
||||||
|
"BLE001", # Blind except
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.ruff.lint.per-file-ignores]
|
||||||
|
"tests/*" = ["ARG001"] # Allow unused arguments in tests
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
"""{{ cookiecutter.project_name }} - {{ cookiecutter.description }}"""
|
||||||
|
|
||||||
|
__version__ = "{{ cookiecutter.version }}"
|
||||||
|
|
||||||
|
|
||||||
|
def hello() -> str:
|
||||||
|
"""Return a hello message."""
|
||||||
|
return "Hello from {{ cookiecutter.project_name }}!"
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
"""Core functionality for {{ cookiecutter.project_name }}."""
|
||||||
|
|
||||||
|
|
||||||
|
def process_data(data: str) -> str:
|
||||||
|
"""Process input data and return result."""
|
||||||
|
return f"Processed: {data}"
|
||||||
|
|
||||||
|
|
||||||
|
class {{ cookiecutter.project_name.replace(' ', '').replace('-', '') }}:
|
||||||
|
"""Main class for {{ cookiecutter.project_name }}."""
|
||||||
|
|
||||||
|
def __init__(self, name: str = "default") -> None:
|
||||||
|
self.name = name
|
||||||
|
|
||||||
|
def run(self) -> str:
|
||||||
|
"""Run the main functionality."""
|
||||||
|
return f"Running {{ cookiecutter.project_name }} with {self.name}"
|
||||||
39
{{cookiecutter.project_slug}}/tasks.py
Normal file
39
{{cookiecutter.project_slug}}/tasks.py
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# type: ignore
|
||||||
|
from invoke import task
|
||||||
|
|
||||||
|
|
||||||
|
@task
|
||||||
|
def clean(ctx):
|
||||||
|
"""
|
||||||
|
Remove all files and directories that are not under version control to ensure a pristine working environment.
|
||||||
|
Use caution as this operation cannot be undone and might remove untracked files.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
ctx.run("git clean -nfdx")
|
||||||
|
|
||||||
|
response = (
|
||||||
|
input("Are you sure you want to remove all untracked files? (y/n) [n]: ")
|
||||||
|
.strip()
|
||||||
|
.lower()
|
||||||
|
)
|
||||||
|
if response == "y":
|
||||||
|
ctx.run("git clean -fdx")
|
||||||
|
|
||||||
|
|
||||||
|
@task
|
||||||
|
def lint(ctx):
|
||||||
|
"""
|
||||||
|
Perform static analysis on the source code to check for syntax errors and enforce style consistency.
|
||||||
|
"""
|
||||||
|
ctx.run("ruff check src", pty=True)
|
||||||
|
ctx.run("ruff format --check src", pty=True)
|
||||||
|
ctx.run("mypy src", pty=True)
|
||||||
|
|
||||||
|
|
||||||
|
@task
|
||||||
|
def test(ctx):
|
||||||
|
"""
|
||||||
|
Run tests with coverage information.
|
||||||
|
"""
|
||||||
|
ctx.run("pytest --cov=src --cov-report=term-missing", pty=True)
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
"""Tests for {{ cookiecutter.package_name }}."""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from {{ cookiecutter.package_name }} import hello
|
||||||
|
from {{ cookiecutter.package_name }}.core import process_data, {{ cookiecutter.project_name.replace(' ', '').replace('-', '') }}
|
||||||
|
|
||||||
|
|
||||||
|
def test_hello():
|
||||||
|
"""Test hello function."""
|
||||||
|
result = hello()
|
||||||
|
assert "Hello from {{ cookiecutter.project_name }}" in result
|
||||||
|
|
||||||
|
|
||||||
|
def test_process_data():
|
||||||
|
"""Test process_data function."""
|
||||||
|
result = process_data("test")
|
||||||
|
assert result == "Processed: test"
|
||||||
|
|
||||||
|
|
||||||
|
def test_main_class():
|
||||||
|
"""Test main class."""
|
||||||
|
instance = {{ cookiecutter.project_name.replace(' ', '').replace('-', '') }}("test")
|
||||||
|
result = instance.run()
|
||||||
|
assert "Running {{ cookiecutter.project_name }} with test" in result
|
||||||
Reference in New Issue
Block a user