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