Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,14 @@ jobs:
- name: Install dependencies
run: uv sync --dev

- name: Run tests
run: uv run pytest
- name: Run tests with coverage
run: uv run pytest --cov=python_package_template --cov-report=term-missing --cov-report=xml

- name: Lint with ruff
run: uv run ruff check .

- name: Type check with mypy
run: uv run mypy .

- name: Security audit with pip-audit
run: uv run pip-audit
47 changes: 4 additions & 43 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,48 +4,19 @@ REVIEW.md
*.lock
!uv.lock

# AI IDEs & Editors
.aider/
.aider.chat.history.md
.blackbox/
.cline/
.cline_docs/
.codium/
.codeium/
.continue/
.copilot/
.cursor/
.cursor-server/
.devin/
.lm-studio/
.lovable/
.phind/
.supermaven/
.tabnine/
.v0/
.windsurf/
/.*
!.gitignore
!.github
!.env.example

# AI Agent Frameworks & CLI Tools
.antigravity
.autogpt
.gpt-engineer/

# AI Model Runners
.ollama/

# AI Agent Outputs & State Files
.agent_state/
.agentic/
agent_artifacts/
agent_logs/
agent_output/
agent_memory/

# AI Tool Cache & Config
.ai_cache/
.ai_tmp/
.llm/
.llm_cache/
conversation_history/

# Local Model Files
Expand Down Expand Up @@ -116,11 +87,6 @@ dmypy.json
*.swp
*.swo
*~
.DS_Store
.emacs.desktop
.idea/
.vim/
.vscode/

# Merge Conflicts
*.orig
Expand All @@ -129,11 +95,6 @@ dmypy.json
# OS
Thumbs.db

# Secrets & Configuration
.env
.env.local
.env.*.local

# Project Specific
*.db
*.log
Expand Down
4 changes: 3 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
| Testing | pytest |
| Linting & Formatting | ruff |
| Type Checking | mypy |
| Security Audit | pip-audit |

## Project Structure
```
Expand All @@ -40,7 +41,7 @@ pyproject.toml (Dependencies & tool config)

### Testing & Quality
- **Test Coverage:** Every code change requires corresponding tests in `tests/`.
- **Validation Before Commit:** Run the full suite: `uv run pytest`, `uv run ruff check .`, `uv run mypy .`.
- **Validation Before Commit:** Run the full suite: `uv run pytest`, `uv run ruff check .`, `uv run mypy .`, `uv run pip-audit`.

### Operational Constraints
- **No Interactive Prompts:** Mock or bypass any interactive commands.
Expand All @@ -57,5 +58,6 @@ uv run pytest # Run tests
uv run ruff check . # Lint
uv run ruff format . # Auto-format
uv run mypy . # Type check
uv run pip-audit # Security audit
uv run hello-world hello # Test CLI
```
37 changes: 28 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
# python-package-template

A basic template package demonstrating Python packaging best practices using **uv**, **pydantic**, and **pytest**.
This package is intentionally simple to provide a clean starting point for your own projects.

## Overview

This is a minimal but well-structured Python package that serves as a template for building larger projects. It demonstrates:
The tools in this template were chosen to be simple, low friction, effective, and relatively comprehensive.

- Modern Python packaging with `pyproject.toml`
- Type hints and static type checking with **mypy**
- Data validation using **pydantic**
- Code linting with **ruff**
- Testing with **pytest**
- Dependency management with **uv**
| Purpose | Tool |
| ------------------ | --------- |
| Package Management | uv |
| Data Validation | pydantic |
| CLI Framework | typer |
| Testing | pytest |
| Code Quality | ruff |
| Type Checking | mypy |
| Security Audit | pip-audit |

This package is intentionally simple to provide a clean starting point for your own projects.

## Installation

Expand Down Expand Up @@ -126,6 +128,21 @@ uv run ruff format

# Type check
uv run mypy .

# Security audit
uv run pip-audit
```

### Git Configuration

The `.gitignore` file ignores all dot files (`.*`) by default, with exceptions for `.gitignore`, `.github`, and `.env.example`. If you want to commit other dot files (e.g., `.devin/`, `.cursor/`, etc.), add them to the negation list in `.gitignore`:

```
/.*
!.gitignore
!.github
!.env.example
!.myfile # Add your custom dot files here
```

## Project Structure
Expand Down Expand Up @@ -174,6 +191,7 @@ Both files enforce the same code standards and project structure—only the auto
- **Configuration**: Externalize settings using the `Config` class
- **Testing**: Comprehensive test suite with pytest
- **Code quality**: Automated linting with ruff and type checking with mypy
- **Security**: Dependency vulnerability scanning with pip-audit

## Python Best Practices Used

Expand All @@ -184,6 +202,7 @@ Both files enforce the same code standards and project structure—only the auto
- ✅ **Configuration**: Externalized config using pydantic BaseModel
- ✅ **Linting**: Code quality checks with ruff
- ✅ **Dependency management**: Explicit dependencies in pyproject.toml
- ✅ **Security**: Automated vulnerability scanning with pip-audit
- ✅ **Python versions**: Supports Python 3.10+

## License
Expand Down
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ dev = [
"ruff",
"mypy",
"pytest-cov>=7.1.0",
"pip-audit",
]

[project.scripts]
Expand Down Expand Up @@ -84,7 +85,7 @@ disallow_untyped_defs = true # Require type hints on all function definitions

[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = "--cov=python_package_template --cov-report=term-missing --cov-fail-under=80"
addopts = "--cov=python_package_template --cov-report=term-missing --cov-fail-under=95"

[tool.coverage.run]
source = ["python_package_template"]
Expand Down
4 changes: 2 additions & 2 deletions python_package_template/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
and a hello world example.
"""

from python_package_template.config import Config
from python_package_template.config import DEFAULT_CONFIG, Config
from python_package_template.hello import HelloWorld

__version__ = "0.1.1"
__all__ = ["Config", "HelloWorld"]
__all__ = ["DEFAULT_CONFIG", "Config", "HelloWorld"]
9 changes: 7 additions & 2 deletions python_package_template/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@
"""

import typer
from pydantic import ValidationError

from python_package_template import __version__
from python_package_template.config import Config
from python_package_template.config import DEFAULT_CONFIG, Config
from python_package_template.hello import HelloWorld

app = typer.Typer(help="Python package template CLI")
Expand Down Expand Up @@ -46,7 +47,11 @@ def hello(
name: The name to greet.

"""
config = Config(name=name)
try:
config = DEFAULT_CONFIG if name == "World" else Config(name=name)
except ValidationError as e:
typer.echo(f"Error: Invalid input - {e}", err=True)
raise typer.Exit(code=1) from None
hello_world = HelloWorld(config)
greeting = hello_world.greet()
typer.echo(greeting)
Expand Down
4 changes: 4 additions & 0 deletions python_package_template/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,7 @@ class Config(BaseModel):
name: str = Field(default="World", min_length=1, description="The name to greet")

model_config = {"title": "Hello World Config", "frozen": True}


# Singleton instance for default configuration
DEFAULT_CONFIG = Config()
11 changes: 10 additions & 1 deletion tests/test_hello.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from pydantic import ValidationError
from typer.testing import CliRunner

from python_package_template import Config, HelloWorld
from python_package_template import DEFAULT_CONFIG, Config, HelloWorld
from python_package_template.cli import app


Expand Down Expand Up @@ -61,6 +61,14 @@ def test_config_invalid_type() -> None:
Config(name=123) # type: ignore[arg-type]


def test_default_config_singleton() -> None:
"""Test that DEFAULT_CONFIG is a proper singleton with default values."""
assert DEFAULT_CONFIG.name == "World"
# Verify it's the same object when accessed multiple times
from python_package_template.config import DEFAULT_CONFIG as default_config_again
assert DEFAULT_CONFIG is default_config_again


# CLI Tests
runner = CliRunner()

Expand Down Expand Up @@ -104,6 +112,7 @@ def test_cli_hello_empty_name() -> None:
"""Test CLI hello command with empty string name raises validation error."""
result = runner.invoke(app, ["hello", "--name", ""])
assert result.exit_code != 0
assert "Error: Invalid input" in result.output


def test_main_entry_point() -> None:
Expand Down
Loading
Loading