Initial commit
This commit is contained in:
20
.claude-plugin/plugin.json
Normal file
20
.claude-plugin/plugin.json
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"name": "pythonic",
|
||||||
|
"description": "Python project setup and development workflows following modern best practices with uv, ruff, pytest, and FastAPI",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"author": {
|
||||||
|
"name": "Joseph Platta"
|
||||||
|
},
|
||||||
|
"skills": [
|
||||||
|
"./skills"
|
||||||
|
],
|
||||||
|
"agents": [
|
||||||
|
"./agents"
|
||||||
|
],
|
||||||
|
"commands": [
|
||||||
|
"./commands"
|
||||||
|
],
|
||||||
|
"hooks": [
|
||||||
|
"./hooks"
|
||||||
|
]
|
||||||
|
}
|
||||||
3
README.md
Normal file
3
README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# pythonic
|
||||||
|
|
||||||
|
Python project setup and development workflows following modern best practices with uv, ruff, pytest, and FastAPI
|
||||||
376
agents/python-project-setup.md
Normal file
376
agents/python-project-setup.md
Normal file
@@ -0,0 +1,376 @@
|
|||||||
|
---
|
||||||
|
description: Orchestrate complete Python project setup with modern tooling and best practices
|
||||||
|
---
|
||||||
|
|
||||||
|
# Python Project Setup Agent
|
||||||
|
|
||||||
|
This agent handles the end-to-end setup of a new Python project following modern best practices with uv, ruff, pytest, and comprehensive project structure.
|
||||||
|
|
||||||
|
## When to Invoke
|
||||||
|
|
||||||
|
Automatically invoke this agent when:
|
||||||
|
- User requests to "create a new Python project"
|
||||||
|
- User asks to "initialize a Python project"
|
||||||
|
- User mentions "start a Python project from scratch"
|
||||||
|
- User requests "Python project setup" or "Python scaffolding"
|
||||||
|
|
||||||
|
## Agent Capabilities
|
||||||
|
|
||||||
|
This agent orchestrates the complete project setup process by:
|
||||||
|
1. Gathering project requirements from the user
|
||||||
|
2. Initializing the project structure
|
||||||
|
3. Configuring modern tooling (uv, ruff, pytest, mypy)
|
||||||
|
4. Setting up development environment
|
||||||
|
5. Creating documentation and configuration files
|
||||||
|
6. Verifying the setup is functional
|
||||||
|
|
||||||
|
## Tools Available
|
||||||
|
|
||||||
|
This agent has access to all standard Claude Code tools:
|
||||||
|
- **Bash**: For running commands (uv, git, etc.)
|
||||||
|
- **Write**: For creating configuration files
|
||||||
|
- **Read**: For verifying files
|
||||||
|
- **Edit**: For modifying generated files
|
||||||
|
- **Glob/Grep**: For searching when needed
|
||||||
|
|
||||||
|
## Skills to Leverage
|
||||||
|
|
||||||
|
The agent should utilize these plugin skills:
|
||||||
|
- `python-project-setup`: Project structure and initialization
|
||||||
|
- `python-code-quality`: Ruff and mypy configuration
|
||||||
|
- `python-testing`: Pytest setup and test creation
|
||||||
|
|
||||||
|
## Setup Process
|
||||||
|
|
||||||
|
### Phase 1: Discovery and Planning
|
||||||
|
|
||||||
|
1. **Ask the User Questions**
|
||||||
|
- Project name
|
||||||
|
- Python version (default: latest stable 3.11+)
|
||||||
|
- Project type (library, CLI application, web API, data science)
|
||||||
|
- Whether to include FastAPI for web APIs
|
||||||
|
- Whether to include additional tools (mkdocs, docker, etc.)
|
||||||
|
|
||||||
|
2. **Validate Requirements**
|
||||||
|
- Check if uv is installed
|
||||||
|
- Check if pyenv is installed (optional but recommended)
|
||||||
|
- Check if git is initialized
|
||||||
|
|
||||||
|
### Phase 2: Project Initialization
|
||||||
|
|
||||||
|
1. **Create Project Structure**
|
||||||
|
```bash
|
||||||
|
uv init <project-name>
|
||||||
|
cd <project-name>
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Set Python Version**
|
||||||
|
- Create `.python-version` file with specified version
|
||||||
|
- Verify Python version is available
|
||||||
|
|
||||||
|
3. **Initialize Git (if not already)**
|
||||||
|
```bash
|
||||||
|
git init
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 3: Directory Structure
|
||||||
|
|
||||||
|
Create the following directories:
|
||||||
|
```
|
||||||
|
<project-name>/
|
||||||
|
├── .vscode/
|
||||||
|
├── src/<project_name>/
|
||||||
|
│ ├── __init__.py
|
||||||
|
│ ├── main.py
|
||||||
|
│ ├── models/
|
||||||
|
│ ├── services/
|
||||||
|
│ └── utils/
|
||||||
|
├── tests/
|
||||||
|
│ ├── __init__.py
|
||||||
|
│ └── conftest.py
|
||||||
|
├── docs/
|
||||||
|
└── .github/
|
||||||
|
└── workflows/
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 4: Dependencies
|
||||||
|
|
||||||
|
1. **Install Core Dependencies**
|
||||||
|
```bash
|
||||||
|
# Development tools
|
||||||
|
uv add --dev pytest
|
||||||
|
uv add --dev pytest-cov
|
||||||
|
uv add --dev ruff
|
||||||
|
uv add --dev mypy
|
||||||
|
|
||||||
|
# Runtime dependencies
|
||||||
|
uv add pydantic
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Install Optional Dependencies** (based on project type)
|
||||||
|
- For web APIs: `uv add fastapi uvicorn[standard]`
|
||||||
|
- For documentation: `uv add --dev mkdocs mkdocs-material`
|
||||||
|
- For async: `uv add --dev pytest-asyncio`
|
||||||
|
|
||||||
|
### Phase 5: Configuration Files
|
||||||
|
|
||||||
|
1. **Create `pyproject.toml`**
|
||||||
|
- Use template from `python-project-setup` skill
|
||||||
|
- Configure ruff linting and formatting rules
|
||||||
|
- Configure pytest settings
|
||||||
|
- Configure mypy type checking
|
||||||
|
- Add project metadata
|
||||||
|
|
||||||
|
2. **Create `.gitignore`**
|
||||||
|
- Use template from `python-project-setup` skill
|
||||||
|
- Include Python-specific patterns
|
||||||
|
|
||||||
|
3. **Create `.vscode/settings.json`**
|
||||||
|
- Use template from `python-project-setup` skill
|
||||||
|
- Configure Python interpreter path
|
||||||
|
- Enable ruff for linting and formatting
|
||||||
|
- Configure pytest integration
|
||||||
|
|
||||||
|
4. **Create `README.md`**
|
||||||
|
- Use template from `python-project-setup` skill
|
||||||
|
- Include project description, installation, usage
|
||||||
|
- Add development setup instructions
|
||||||
|
|
||||||
|
### Phase 6: Initial Code
|
||||||
|
|
||||||
|
1. **Create Main Module** (`src/<project_name>/main.py`)
|
||||||
|
```python
|
||||||
|
"""Main module for <project-name>."""
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
"""Main entry point."""
|
||||||
|
print("Hello from <project-name>!")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Create Initial Test** (`tests/test_main.py`)
|
||||||
|
```python
|
||||||
|
"""Tests for main module."""
|
||||||
|
from <project_name>.main import main
|
||||||
|
|
||||||
|
def test_main():
|
||||||
|
"""Test main function runs without error."""
|
||||||
|
main() # Should not raise
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 7: CI/CD Setup
|
||||||
|
|
||||||
|
1. **Create GitHub Actions Workflow** (`.github/workflows/test.yml`)
|
||||||
|
```yaml
|
||||||
|
name: Tests
|
||||||
|
on: [push, pull_request]
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
python-version: ["3.11", "3.12"]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Install uv
|
||||||
|
run: curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||||
|
- name: Set up Python
|
||||||
|
run: uv python install ${{ matrix.python-version }}
|
||||||
|
- name: Install dependencies
|
||||||
|
run: uv sync
|
||||||
|
- name: Run ruff
|
||||||
|
run: uv run ruff check .
|
||||||
|
- name: Run mypy
|
||||||
|
run: uv run mypy src
|
||||||
|
- name: Run tests
|
||||||
|
run: uv run pytest --cov
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 8: Verification
|
||||||
|
|
||||||
|
1. **Sync Dependencies**
|
||||||
|
```bash
|
||||||
|
uv sync
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Run Quality Checks**
|
||||||
|
```bash
|
||||||
|
uv run ruff check .
|
||||||
|
uv run ruff format .
|
||||||
|
uv run mypy src
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Run Tests**
|
||||||
|
```bash
|
||||||
|
uv run pytest -v
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Verify Structure**
|
||||||
|
- Check all directories exist
|
||||||
|
- Check all configuration files are present
|
||||||
|
- Check imports work correctly
|
||||||
|
|
||||||
|
### Phase 9: Summary
|
||||||
|
|
||||||
|
Provide the user with:
|
||||||
|
1. **What was created**: List of directories and files
|
||||||
|
2. **Next steps**: How to start developing
|
||||||
|
3. **Useful commands**:
|
||||||
|
```bash
|
||||||
|
# Run the application
|
||||||
|
uv run python -m <project_name>
|
||||||
|
|
||||||
|
# Run tests
|
||||||
|
uv run pytest
|
||||||
|
|
||||||
|
# Run linting
|
||||||
|
uv run ruff check .
|
||||||
|
|
||||||
|
# Format code
|
||||||
|
uv run ruff format .
|
||||||
|
|
||||||
|
# Type check
|
||||||
|
uv run mypy src
|
||||||
|
|
||||||
|
# Add dependencies
|
||||||
|
uv add <package>
|
||||||
|
uv add --dev <dev-package>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
### Missing uv
|
||||||
|
If uv is not installed:
|
||||||
|
```bash
|
||||||
|
# Provide installation instructions
|
||||||
|
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### Permission Issues
|
||||||
|
If directory creation fails, check permissions and suggest:
|
||||||
|
```bash
|
||||||
|
# Try with appropriate permissions
|
||||||
|
mkdir -p <directory>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Version Conflicts
|
||||||
|
If Python version is not available:
|
||||||
|
```bash
|
||||||
|
# Install with pyenv
|
||||||
|
pyenv install <version>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Success Criteria
|
||||||
|
|
||||||
|
The setup is complete when:
|
||||||
|
- [ ] All directories and files are created
|
||||||
|
- [ ] Dependencies are installed successfully
|
||||||
|
- [ ] Linting passes without errors
|
||||||
|
- [ ] Type checking passes without errors
|
||||||
|
- [ ] All tests pass
|
||||||
|
- [ ] Git repository is initialized
|
||||||
|
- [ ] README provides clear documentation
|
||||||
|
|
||||||
|
## Project Type Variations
|
||||||
|
|
||||||
|
### Web API (FastAPI)
|
||||||
|
Additional setup:
|
||||||
|
- Install FastAPI and uvicorn
|
||||||
|
- Create `src/<project_name>/api/` directory
|
||||||
|
- Create `src/<project_name>/api/main.py` with sample endpoint
|
||||||
|
- Add API documentation to README
|
||||||
|
|
||||||
|
### CLI Application
|
||||||
|
Additional setup:
|
||||||
|
- Add entry point to `pyproject.toml` `[project.scripts]`
|
||||||
|
- Create CLI module with argparse or Click
|
||||||
|
- Add usage examples to README
|
||||||
|
|
||||||
|
### Library
|
||||||
|
Additional setup:
|
||||||
|
- Focus on clean API design
|
||||||
|
- Comprehensive docstrings
|
||||||
|
- More extensive testing
|
||||||
|
- Consider adding type stubs
|
||||||
|
|
||||||
|
### Data Science
|
||||||
|
Additional setup:
|
||||||
|
- Add `notebooks/` directory
|
||||||
|
- Install Jupyter: `uv add --dev jupyter`
|
||||||
|
- Install common packages: pandas, numpy, matplotlib
|
||||||
|
- Create sample notebook
|
||||||
|
|
||||||
|
## Post-Setup Recommendations
|
||||||
|
|
||||||
|
1. **Set up pre-commit hooks** (optional)
|
||||||
|
```bash
|
||||||
|
uv add --dev pre-commit
|
||||||
|
# Create .pre-commit-config.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Configure Dependabot**
|
||||||
|
- Create `.github/dependabot.yml`
|
||||||
|
|
||||||
|
3. **Add more documentation**
|
||||||
|
- API documentation
|
||||||
|
- Architecture decisions
|
||||||
|
- Contributing guidelines
|
||||||
|
|
||||||
|
4. **Consider Docker** (if needed)
|
||||||
|
- Create Dockerfile
|
||||||
|
- Create docker-compose.yml
|
||||||
|
|
||||||
|
## Example Interaction
|
||||||
|
|
||||||
|
```
|
||||||
|
User: I want to create a new Python project called "weather-api" for a FastAPI application
|
||||||
|
|
||||||
|
Agent:
|
||||||
|
I'll help you set up a new Python project with FastAPI. Let me gather some information:
|
||||||
|
|
||||||
|
- Project name: weather-api
|
||||||
|
- Project type: Web API (FastAPI)
|
||||||
|
- Python version: 3.11 (latest stable)
|
||||||
|
- Additional features: API documentation, testing, type checking
|
||||||
|
|
||||||
|
Starting project setup...
|
||||||
|
|
||||||
|
[Phase 1] Initializing project structure...
|
||||||
|
✓ Created project with uv init
|
||||||
|
✓ Created directory structure
|
||||||
|
|
||||||
|
[Phase 2] Installing dependencies...
|
||||||
|
✓ Installed pytest, ruff, mypy
|
||||||
|
✓ Installed FastAPI and uvicorn
|
||||||
|
✓ Installed pydantic
|
||||||
|
|
||||||
|
[Phase 3] Creating configuration files...
|
||||||
|
✓ Created pyproject.toml with tool configurations
|
||||||
|
✓ Created .gitignore
|
||||||
|
✓ Created .vscode/settings.json
|
||||||
|
✓ Created README.md
|
||||||
|
|
||||||
|
[Phase 4] Setting up FastAPI application...
|
||||||
|
✓ Created API module structure
|
||||||
|
✓ Created sample endpoint
|
||||||
|
✓ Created API tests
|
||||||
|
|
||||||
|
[Phase 5] Running verification checks...
|
||||||
|
✓ Linting passed
|
||||||
|
✓ Type checking passed
|
||||||
|
✓ All tests passed (1 passed)
|
||||||
|
|
||||||
|
Setup complete! Your project is ready for development.
|
||||||
|
|
||||||
|
Next steps:
|
||||||
|
1. Activate your environment: cd weather-api
|
||||||
|
2. Run the API: uv run uvicorn weather_api.api.main:app --reload
|
||||||
|
3. Visit http://localhost:8000/docs for API documentation
|
||||||
|
|
||||||
|
Useful commands:
|
||||||
|
- uv run pytest # Run tests
|
||||||
|
- uv run ruff check . # Lint code
|
||||||
|
- uv add <package> # Add dependencies
|
||||||
|
```
|
||||||
34
commands/py-deps.md
Normal file
34
commands/py-deps.md
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
---
|
||||||
|
description: Manage Python dependencies with uv package manager
|
||||||
|
---
|
||||||
|
|
||||||
|
# Manage Python Dependencies
|
||||||
|
|
||||||
|
Add, update, or remove project dependencies using the uv package manager.
|
||||||
|
|
||||||
|
## Tasks to Complete
|
||||||
|
|
||||||
|
1. **Add Dependencies**
|
||||||
|
- For runtime: `uv add <package-name>`
|
||||||
|
- For development: `uv add --dev <package-name>`
|
||||||
|
- Confirm package was added to `pyproject.toml`
|
||||||
|
|
||||||
|
2. **Update Dependencies**
|
||||||
|
- Run `uv sync` to sync all dependencies
|
||||||
|
- Update specific package: `uv add <package-name>@latest`
|
||||||
|
- Report outdated packages if requested
|
||||||
|
|
||||||
|
3. **Remove Dependencies**
|
||||||
|
- Run `uv remove <package-name>` to remove a package
|
||||||
|
- Clean up unused dependencies
|
||||||
|
|
||||||
|
4. **Verify Environment**
|
||||||
|
- Ensure virtual environment is active
|
||||||
|
- Verify all dependencies are installed correctly
|
||||||
|
- Check for dependency conflicts
|
||||||
|
|
||||||
|
## Expected Outcome
|
||||||
|
|
||||||
|
- Up-to-date dependencies in sync with `pyproject.toml`
|
||||||
|
- Clean, conflict-free dependency tree
|
||||||
|
- Clear confirmation of changes made
|
||||||
51
commands/py-init.md
Normal file
51
commands/py-init.md
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
---
|
||||||
|
description: Initialize a new Python project with modern tooling (uv, ruff, pytest)
|
||||||
|
---
|
||||||
|
|
||||||
|
# Initialize Python Project
|
||||||
|
|
||||||
|
Initialize a new Python project following modern best practices. This command sets up the project structure, tooling, and configuration.
|
||||||
|
|
||||||
|
## Tasks to Complete
|
||||||
|
|
||||||
|
1. **Project Initialization**
|
||||||
|
- Run `uv init <project-name>` to create the project
|
||||||
|
- Create `.python-version` file for pyenv compatibility
|
||||||
|
- Initialize git repository if not already present
|
||||||
|
|
||||||
|
2. **Directory Structure**
|
||||||
|
- Create `src/<project_name>/` for main code
|
||||||
|
- Create `tests/` directory for test files
|
||||||
|
- Create `.vscode/` for editor configuration
|
||||||
|
- Create `docs/` for documentation
|
||||||
|
|
||||||
|
3. **Essential Dependencies**
|
||||||
|
- Add `pytest` as dev dependency: `uv add --dev pytest`
|
||||||
|
- Add `ruff` as dev dependency: `uv add --dev ruff`
|
||||||
|
- Add `mypy` as dev dependency: `uv add --dev mypy`
|
||||||
|
- Add `pydantic` as runtime dependency: `uv add pydantic`
|
||||||
|
|
||||||
|
4. **Configuration Files**
|
||||||
|
- Create `pyproject.toml` with ruff and pytest configuration
|
||||||
|
- Create `.gitignore` for Python projects
|
||||||
|
- Create `.vscode/settings.json` with Python and ruff settings
|
||||||
|
|
||||||
|
5. **Documentation**
|
||||||
|
- Create `README.md` with project overview and setup instructions
|
||||||
|
- Create initial documentation structure
|
||||||
|
|
||||||
|
## Expected Outcome
|
||||||
|
|
||||||
|
A fully initialized Python project ready for development with:
|
||||||
|
- Modern package management (uv)
|
||||||
|
- Fast linting and formatting (ruff)
|
||||||
|
- Testing framework (pytest)
|
||||||
|
- Type checking (mypy)
|
||||||
|
- Editor configuration (VSCode)
|
||||||
|
- Version control (git)
|
||||||
|
|
||||||
|
## User Inputs Required
|
||||||
|
|
||||||
|
- Project name
|
||||||
|
- Python version (default: latest stable)
|
||||||
|
- Whether to include FastAPI for API projects
|
||||||
33
commands/py-lint.md
Normal file
33
commands/py-lint.md
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
---
|
||||||
|
description: Run ruff linter and formatter to check code quality
|
||||||
|
---
|
||||||
|
|
||||||
|
# Lint Python Code
|
||||||
|
|
||||||
|
Check and fix code quality issues using ruff, the fast Python linter and formatter.
|
||||||
|
|
||||||
|
## Tasks to Complete
|
||||||
|
|
||||||
|
1. **Run Linter**
|
||||||
|
- Execute `ruff check .` to identify issues
|
||||||
|
- Report all linting errors and warnings
|
||||||
|
- Categorize issues by severity
|
||||||
|
|
||||||
|
2. **Auto-fix Issues** (if requested)
|
||||||
|
- Run `ruff check --fix .` to automatically fix issues
|
||||||
|
- Report which issues were fixed automatically
|
||||||
|
|
||||||
|
3. **Format Code**
|
||||||
|
- Execute `ruff format .` to format all Python files
|
||||||
|
- Ensure PEP 8 compliance
|
||||||
|
- Report which files were reformatted
|
||||||
|
|
||||||
|
4. **Type Checking** (if mypy is installed)
|
||||||
|
- Run `mypy .` to check type annotations
|
||||||
|
- Report type errors with file locations
|
||||||
|
|
||||||
|
## Expected Outcome
|
||||||
|
|
||||||
|
- Clean, well-formatted code following PEP 8
|
||||||
|
- All auto-fixable issues resolved
|
||||||
|
- Clear report of remaining issues requiring manual intervention
|
||||||
30
commands/py-test.md
Normal file
30
commands/py-test.md
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
---
|
||||||
|
description: Run pytest with proper configuration and coverage reporting
|
||||||
|
---
|
||||||
|
|
||||||
|
# Run Python Tests
|
||||||
|
|
||||||
|
Execute the project's test suite using pytest with appropriate options and coverage reporting.
|
||||||
|
|
||||||
|
## Tasks to Complete
|
||||||
|
|
||||||
|
1. **Run Tests**
|
||||||
|
- Execute `uv run pytest` to run all tests
|
||||||
|
- Display test results clearly
|
||||||
|
- Report any failures with details
|
||||||
|
|
||||||
|
2. **Coverage Analysis** (if requested)
|
||||||
|
- Run `uv run pytest --cov` for coverage report
|
||||||
|
- Generate HTML coverage report if needed
|
||||||
|
- Highlight areas needing more test coverage
|
||||||
|
|
||||||
|
3. **Test Filtering** (if requested)
|
||||||
|
- Run specific test files: `uv run pytest tests/test_specific.py`
|
||||||
|
- Run tests matching pattern: `uv run pytest -k "pattern"`
|
||||||
|
- Run only failed tests: `uv run pytest --lf`
|
||||||
|
|
||||||
|
## Expected Outcome
|
||||||
|
|
||||||
|
- Clear test results showing passes/failures
|
||||||
|
- Coverage percentages for each module
|
||||||
|
- Actionable information about test failures
|
||||||
24
hooks/hooks.json
Normal file
24
hooks/hooks.json
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"hooks": {
|
||||||
|
"before-write": {
|
||||||
|
"enabled": false,
|
||||||
|
"command": "uv run ruff format {file_path}",
|
||||||
|
"description": "Auto-format Python files with ruff before writing"
|
||||||
|
},
|
||||||
|
"after-write": {
|
||||||
|
"enabled": false,
|
||||||
|
"command": "uv run ruff check --fix {file_path}",
|
||||||
|
"description": "Auto-fix linting issues after writing Python files"
|
||||||
|
},
|
||||||
|
"before-commit": {
|
||||||
|
"enabled": false,
|
||||||
|
"command": "uv run ruff check . && uv run mypy src && uv run pytest",
|
||||||
|
"description": "Run quality checks before git commits"
|
||||||
|
},
|
||||||
|
"test-on-save": {
|
||||||
|
"enabled": false,
|
||||||
|
"command": "uv run pytest tests/test_{file_name}",
|
||||||
|
"description": "Run related tests when saving Python files"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
109
plugin.lock.json
Normal file
109
plugin.lock.json
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
{
|
||||||
|
"$schema": "internal://schemas/plugin.lock.v1.json",
|
||||||
|
"pluginId": "gh:jwplatta/prompt-library:claude/plugins/pythonic",
|
||||||
|
"normalized": {
|
||||||
|
"repo": null,
|
||||||
|
"ref": "refs/tags/v20251128.0",
|
||||||
|
"commit": "3bd271a3eaa774f3948c904757be2be0468a0bd6",
|
||||||
|
"treeHash": "b8a9acc45ec9f46bfe197943456cf3b514b532d64b6a3794cff358bc78ac4726",
|
||||||
|
"generatedAt": "2025-11-28T10:19:23.602487Z",
|
||||||
|
"toolVersion": "publish_plugins.py@0.2.0"
|
||||||
|
},
|
||||||
|
"origin": {
|
||||||
|
"remote": "git@github.com:zhongweili/42plugin-data.git",
|
||||||
|
"branch": "master",
|
||||||
|
"commit": "aa1497ed0949fd50e99e70d6324a29c5b34f9390",
|
||||||
|
"repoRoot": "/Users/zhongweili/projects/openmind/42plugin-data"
|
||||||
|
},
|
||||||
|
"manifest": {
|
||||||
|
"name": "pythonic",
|
||||||
|
"description": "Python project setup and development workflows following modern best practices with uv, ruff, pytest, and FastAPI",
|
||||||
|
"version": "1.0.0"
|
||||||
|
},
|
||||||
|
"content": {
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"path": "README.md",
|
||||||
|
"sha256": "9838563a182b051ca1993450b2075c25b328cf7ad31ccaa8748256f7612d8c17"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "agents/python-project-setup.md",
|
||||||
|
"sha256": "6e8bafbe19269314bcd2029646ef27f7291ebd169412505c3b446b6b68307fe5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "hooks/hooks.json",
|
||||||
|
"sha256": "ebbef10b9b0f36aba1f36fbe321baed17d2424d3f60690766759d40935c0d64c"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": ".claude-plugin/plugin.json",
|
||||||
|
"sha256": "7bdb436890f02f71f20df90af8591cd404a9756037966a7ffc664402efd40eba"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "commands/py-test.md",
|
||||||
|
"sha256": "4bb5b1c4d0d9ac0114f2b402a2826f74cf27c32bedc6d456280fb4e58b3d2e80"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "commands/py-deps.md",
|
||||||
|
"sha256": "7f151b7d9273a611f0ff978a1a4a4191b7fe64123117360f8d62331ef6a6f8bb"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "commands/py-lint.md",
|
||||||
|
"sha256": "24cd57b6d2768dbf1d9ddb191c5a3ef4aae11dded71cbff685ff999ff93530cd"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "commands/py-init.md",
|
||||||
|
"sha256": "dc779e1d29b9ac2f1a9fe015fe3265eb9e86d0766995973e0877cf056eab47d1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "skills/python-testing/SKILL.md",
|
||||||
|
"sha256": "45df6483b59934e9045b5e90a35662e058814c7efc0a7c42ad6fb0becf71c5f4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "skills/python-testing/pytest-configuration.md",
|
||||||
|
"sha256": "4d4eec92b86b7cec6d8335ffeb547d978b68eedb3b8a7b7e0374cfa46c7d0a78"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "skills/python-code-quality/mypy-configuration.md",
|
||||||
|
"sha256": "1ac9a960b103a8477ae60a23e89142946fc94ab700e32dd70d5da508cb72a156"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "skills/python-code-quality/ruff-configuration.md",
|
||||||
|
"sha256": "bb085d4dcdc8dfc30f3c7ef63774e2498c039559b5ffd792b56111aabaa846e8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "skills/python-code-quality/SKILL.md",
|
||||||
|
"sha256": "6d57263d199e152e02e0897c56bf55f6385645ab82c3c6b1334f2b1161b33f8b"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "skills/python-project-setup/gitignore-template.md",
|
||||||
|
"sha256": "4d6dd9e788166e07134e8b68a7cd0ae5a7c8c0de413680e62397ba4d18ad2bcc"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "skills/python-project-setup/pyproject-toml-template.md",
|
||||||
|
"sha256": "8a7cf6dc8ed3441e99ba4af55870a6ef130a47b065fcd6c86e034192d4bbcfa1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "skills/python-project-setup/project-structure-template.md",
|
||||||
|
"sha256": "3f962a1eee0416117bcd6dddcab5aaa1c4e90206768dab44b2661b242302113a"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "skills/python-project-setup/readme-template.md",
|
||||||
|
"sha256": "b2802a5bf24d8b50faba8a74c204bc427ab6473b4bc1acd152a88eec99aa511e"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "skills/python-project-setup/SKILL.md",
|
||||||
|
"sha256": "d1683bd2ba75f9e66a19e46cf2a207989623d5b4a8a8dea19b911917b8d1755c"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "skills/python-project-setup/vscode-settings-template.json",
|
||||||
|
"sha256": "9d01afcc9033d9a8765a3ba72de599bac729de396a8a1b0247bd788932de6454"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dirSha256": "b8a9acc45ec9f46bfe197943456cf3b514b532d64b6a3794cff358bc78ac4726"
|
||||||
|
},
|
||||||
|
"security": {
|
||||||
|
"scannedAt": null,
|
||||||
|
"scannerVersion": null,
|
||||||
|
"flags": []
|
||||||
|
}
|
||||||
|
}
|
||||||
89
skills/python-code-quality/SKILL.md
Normal file
89
skills/python-code-quality/SKILL.md
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
# Python Code Quality Skill
|
||||||
|
|
||||||
|
Ensure Python code follows best practices with automated linting, formatting, and type checking using ruff and mypy.
|
||||||
|
|
||||||
|
## When to Use This Skill
|
||||||
|
|
||||||
|
Use this skill when:
|
||||||
|
- User requests code linting or formatting
|
||||||
|
- User mentions code quality, PEP 8, or style issues
|
||||||
|
- User wants to fix linting errors
|
||||||
|
- User asks about type checking
|
||||||
|
- Code review processes require quality checks
|
||||||
|
|
||||||
|
## Core Capabilities
|
||||||
|
|
||||||
|
1. **Linting with Ruff**
|
||||||
|
- Check code for style violations
|
||||||
|
- Identify potential bugs and code smells
|
||||||
|
- Enforce PEP 8 compliance
|
||||||
|
- Auto-fix issues where possible
|
||||||
|
|
||||||
|
2. **Code Formatting**
|
||||||
|
- Format code consistently with ruff
|
||||||
|
- Organize imports automatically
|
||||||
|
- Ensure consistent indentation and line length
|
||||||
|
|
||||||
|
3. **Type Checking with Mypy**
|
||||||
|
- Verify type annotations are correct
|
||||||
|
- Catch type-related bugs before runtime
|
||||||
|
- Ensure type safety across the codebase
|
||||||
|
|
||||||
|
4. **Code Quality Metrics**
|
||||||
|
- Complexity analysis
|
||||||
|
- Dead code detection
|
||||||
|
- Unused import identification
|
||||||
|
|
||||||
|
## Context Files
|
||||||
|
|
||||||
|
This skill references the following context files in this directory:
|
||||||
|
- `ruff-configuration.md` - Ruff linting and formatting rules
|
||||||
|
- `mypy-configuration.md` - Type checking configuration
|
||||||
|
- `common-issues.md` - Common Python code quality issues and fixes
|
||||||
|
- `best-practices.md` - Python coding best practices
|
||||||
|
|
||||||
|
## Key Tools and Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Linting
|
||||||
|
ruff check . # Check for issues
|
||||||
|
ruff check --fix . # Auto-fix issues
|
||||||
|
ruff check --watch . # Watch mode
|
||||||
|
|
||||||
|
# Formatting
|
||||||
|
ruff format . # Format all files
|
||||||
|
ruff format --check . # Check if formatting needed
|
||||||
|
|
||||||
|
# Type checking
|
||||||
|
mypy src # Check types
|
||||||
|
mypy --strict src # Strict mode
|
||||||
|
```
|
||||||
|
|
||||||
|
## Common Workflows
|
||||||
|
|
||||||
|
### Pre-commit Quality Check
|
||||||
|
```bash
|
||||||
|
ruff check --fix . && ruff format . && mypy src
|
||||||
|
```
|
||||||
|
|
||||||
|
### CI/CD Pipeline
|
||||||
|
```bash
|
||||||
|
ruff check . # Fail if unfixed issues
|
||||||
|
ruff format --check . # Fail if unformatted
|
||||||
|
mypy src # Fail on type errors
|
||||||
|
```
|
||||||
|
|
||||||
|
## Expected Outcomes
|
||||||
|
|
||||||
|
After using this skill:
|
||||||
|
- All code follows PEP 8 style guidelines
|
||||||
|
- Imports are properly organized
|
||||||
|
- Type hints are correct and comprehensive
|
||||||
|
- Code is consistently formatted
|
||||||
|
- Common bugs and issues are identified
|
||||||
|
|
||||||
|
## Integration with Other Skills
|
||||||
|
|
||||||
|
- Works with `python-project-setup` for initial configuration
|
||||||
|
- Complements `python-testing` for comprehensive quality assurance
|
||||||
|
- Used by `python-project-setup` agent for automated checks
|
||||||
374
skills/python-code-quality/mypy-configuration.md
Normal file
374
skills/python-code-quality/mypy-configuration.md
Normal file
@@ -0,0 +1,374 @@
|
|||||||
|
# Mypy Type Checking Configuration
|
||||||
|
|
||||||
|
Mypy is a static type checker for Python that helps catch bugs before runtime.
|
||||||
|
|
||||||
|
## Basic Configuration
|
||||||
|
|
||||||
|
Add to `pyproject.toml`:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[tool.mypy]
|
||||||
|
python_version = "3.11"
|
||||||
|
warn_return_any = true
|
||||||
|
warn_unused_configs = true
|
||||||
|
disallow_untyped_defs = true
|
||||||
|
disallow_incomplete_defs = true
|
||||||
|
check_untyped_defs = true
|
||||||
|
no_implicit_optional = true
|
||||||
|
warn_redundant_casts = true
|
||||||
|
warn_unused_ignores = true
|
||||||
|
warn_no_return = true
|
||||||
|
strict_equality = true
|
||||||
|
show_error_codes = true
|
||||||
|
show_column_numbers = true
|
||||||
|
|
||||||
|
[[tool.mypy.overrides]]
|
||||||
|
module = "tests.*"
|
||||||
|
disallow_untyped_defs = false
|
||||||
|
|
||||||
|
[[tool.mypy.overrides]]
|
||||||
|
module = "third_party_package.*"
|
||||||
|
ignore_missing_imports = true
|
||||||
|
```
|
||||||
|
|
||||||
|
## Strictness Levels
|
||||||
|
|
||||||
|
### Minimal (Getting Started)
|
||||||
|
```toml
|
||||||
|
[tool.mypy]
|
||||||
|
python_version = "3.11"
|
||||||
|
warn_return_any = false
|
||||||
|
warn_unused_configs = true
|
||||||
|
```
|
||||||
|
|
||||||
|
### Moderate (Recommended)
|
||||||
|
```toml
|
||||||
|
[tool.mypy]
|
||||||
|
python_version = "3.11"
|
||||||
|
warn_return_any = true
|
||||||
|
warn_unused_configs = true
|
||||||
|
disallow_untyped_defs = true
|
||||||
|
check_untyped_defs = true
|
||||||
|
```
|
||||||
|
|
||||||
|
### Strict (Maximum Safety)
|
||||||
|
```toml
|
||||||
|
[tool.mypy]
|
||||||
|
strict = true
|
||||||
|
python_version = "3.11"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Common Configuration Options
|
||||||
|
|
||||||
|
### Type Checking Strictness
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# Require type annotations
|
||||||
|
disallow_untyped_defs = true # Functions must have type hints
|
||||||
|
disallow_untyped_calls = true # Can't call untyped functions
|
||||||
|
disallow_incomplete_defs = true # All params must be typed
|
||||||
|
|
||||||
|
# Optional handling
|
||||||
|
no_implicit_optional = true # Optional must be explicit
|
||||||
|
strict_optional = true # None checking enabled
|
||||||
|
|
||||||
|
# Any type usage
|
||||||
|
disallow_any_unimported = false # Allow Any from untyped imports
|
||||||
|
disallow_any_expr = false # Forbid Any in expressions
|
||||||
|
disallow_any_decorated = false # Forbid Any in decorators
|
||||||
|
disallow_any_explicit = false # Forbid explicit Any
|
||||||
|
```
|
||||||
|
|
||||||
|
### Warning Messages
|
||||||
|
|
||||||
|
```toml
|
||||||
|
warn_return_any = true # Warn on returning Any
|
||||||
|
warn_redundant_casts = true # Warn on unnecessary casts
|
||||||
|
warn_unused_ignores = true # Warn on unused type: ignore
|
||||||
|
warn_unused_configs = true # Warn on unused config options
|
||||||
|
warn_no_return = true # Warn on missing returns
|
||||||
|
warn_unreachable = true # Warn on unreachable code
|
||||||
|
```
|
||||||
|
|
||||||
|
### Error Reporting
|
||||||
|
|
||||||
|
```toml
|
||||||
|
show_error_codes = true # Show error codes in messages
|
||||||
|
show_column_numbers = true # Show column numbers
|
||||||
|
pretty = true # Pretty print errors
|
||||||
|
color_output = true # Colorize output
|
||||||
|
error_summary = true # Show error summary
|
||||||
|
```
|
||||||
|
|
||||||
|
## Type Hints Guide
|
||||||
|
|
||||||
|
### Basic Types
|
||||||
|
|
||||||
|
```python
|
||||||
|
from typing import Any, Optional, Union
|
||||||
|
|
||||||
|
# Simple types
|
||||||
|
def greet(name: str) -> str:
|
||||||
|
return f"Hello, {name}"
|
||||||
|
|
||||||
|
# Optional (can be None)
|
||||||
|
def find_user(user_id: int) -> Optional[User]:
|
||||||
|
return user_map.get(user_id)
|
||||||
|
|
||||||
|
# Modern Optional syntax (Python 3.10+)
|
||||||
|
def find_user(user_id: int) -> User | None:
|
||||||
|
return user_map.get(user_id)
|
||||||
|
|
||||||
|
# Union types
|
||||||
|
def process(value: Union[int, str]) -> bool:
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Modern Union syntax (Python 3.10+)
|
||||||
|
def process(value: int | str) -> bool:
|
||||||
|
return True
|
||||||
|
```
|
||||||
|
|
||||||
|
### Collections
|
||||||
|
|
||||||
|
```python
|
||||||
|
from typing import List, Dict, Set, Tuple
|
||||||
|
|
||||||
|
# Lists
|
||||||
|
def process_items(items: List[str]) -> List[int]:
|
||||||
|
return [len(item) for item in items]
|
||||||
|
|
||||||
|
# Modern syntax (Python 3.9+)
|
||||||
|
def process_items(items: list[str]) -> list[int]:
|
||||||
|
return [len(item) for item in items]
|
||||||
|
|
||||||
|
# Dictionaries
|
||||||
|
def get_config() -> Dict[str, Any]:
|
||||||
|
return {"key": "value"}
|
||||||
|
|
||||||
|
# Modern syntax
|
||||||
|
def get_config() -> dict[str, Any]:
|
||||||
|
return {"key": "value"}
|
||||||
|
|
||||||
|
# Sets
|
||||||
|
def unique_items(items: Set[int]) -> int:
|
||||||
|
return len(items)
|
||||||
|
|
||||||
|
# Tuples (fixed size)
|
||||||
|
def get_coordinates() -> Tuple[float, float]:
|
||||||
|
return (1.0, 2.0)
|
||||||
|
|
||||||
|
# Tuples (variable size)
|
||||||
|
def get_numbers() -> Tuple[int, ...]:
|
||||||
|
return (1, 2, 3, 4, 5)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Callable Types
|
||||||
|
|
||||||
|
```python
|
||||||
|
from typing import Callable
|
||||||
|
|
||||||
|
# Function that takes int and returns str
|
||||||
|
Handler = Callable[[int], str]
|
||||||
|
|
||||||
|
def process(handler: Handler) -> None:
|
||||||
|
result = handler(42)
|
||||||
|
|
||||||
|
# Multiple parameters
|
||||||
|
Callback = Callable[[str, int], bool]
|
||||||
|
|
||||||
|
# No parameters
|
||||||
|
Factory = Callable[[], MyClass]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Generic Types
|
||||||
|
|
||||||
|
```python
|
||||||
|
from typing import TypeVar, Generic
|
||||||
|
|
||||||
|
T = TypeVar('T')
|
||||||
|
|
||||||
|
class Container(Generic[T]):
|
||||||
|
def __init__(self, value: T) -> None:
|
||||||
|
self.value = value
|
||||||
|
|
||||||
|
def get(self) -> T:
|
||||||
|
return self.value
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
container: Container[int] = Container(42)
|
||||||
|
value: int = container.get()
|
||||||
|
```
|
||||||
|
|
||||||
|
### Protocol Types
|
||||||
|
|
||||||
|
```python
|
||||||
|
from typing import Protocol
|
||||||
|
|
||||||
|
class Drawable(Protocol):
|
||||||
|
def draw(self) -> None: ...
|
||||||
|
|
||||||
|
def render(item: Drawable) -> None:
|
||||||
|
item.draw()
|
||||||
|
|
||||||
|
# Any class with draw() method works
|
||||||
|
class Circle:
|
||||||
|
def draw(self) -> None:
|
||||||
|
print("Drawing circle")
|
||||||
|
|
||||||
|
render(Circle()) # Type checks!
|
||||||
|
```
|
||||||
|
|
||||||
|
## Commands Reference
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check all files
|
||||||
|
mypy src
|
||||||
|
|
||||||
|
# Check specific file
|
||||||
|
mypy src/module.py
|
||||||
|
|
||||||
|
# Strict mode
|
||||||
|
mypy --strict src
|
||||||
|
|
||||||
|
# Show error codes
|
||||||
|
mypy --show-error-codes src
|
||||||
|
|
||||||
|
# Generate type stubs
|
||||||
|
stubgen -p mypackage -o stubs
|
||||||
|
|
||||||
|
# Check against installed packages
|
||||||
|
mypy --install-types
|
||||||
|
mypy --non-interactive --install-types
|
||||||
|
```
|
||||||
|
|
||||||
|
## Common Errors and Fixes
|
||||||
|
|
||||||
|
### Error: "Argument has incompatible type"
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Problem
|
||||||
|
def greet(name: str) -> str:
|
||||||
|
return f"Hello, {name}"
|
||||||
|
|
||||||
|
greet(123) # Error: Argument 1 has incompatible type "int"
|
||||||
|
|
||||||
|
# Fix: Pass correct type
|
||||||
|
greet("World")
|
||||||
|
|
||||||
|
# Or: Convert type
|
||||||
|
greet(str(123))
|
||||||
|
```
|
||||||
|
|
||||||
|
### Error: "Function is missing a return statement"
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Problem
|
||||||
|
def get_value() -> int:
|
||||||
|
if condition:
|
||||||
|
return 42
|
||||||
|
# Missing return!
|
||||||
|
|
||||||
|
# Fix: Add return for all paths
|
||||||
|
def get_value() -> int:
|
||||||
|
if condition:
|
||||||
|
return 42
|
||||||
|
return 0
|
||||||
|
```
|
||||||
|
|
||||||
|
### Error: "Need type annotation"
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Problem
|
||||||
|
items = [] # Error: Need type annotation
|
||||||
|
|
||||||
|
# Fix: Add type annotation
|
||||||
|
items: list[str] = []
|
||||||
|
|
||||||
|
# Or: Initialize with values
|
||||||
|
items = ["a", "b", "c"]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Error: "Incompatible return type"
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Problem
|
||||||
|
def get_name() -> str:
|
||||||
|
return None # Error: Incompatible return
|
||||||
|
|
||||||
|
# Fix: Use Optional
|
||||||
|
def get_name() -> str | None:
|
||||||
|
return None
|
||||||
|
```
|
||||||
|
|
||||||
|
## Ignoring Errors
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Ignore single line
|
||||||
|
result = unsafe_function() # type: ignore
|
||||||
|
|
||||||
|
# Ignore with reason
|
||||||
|
result = unsafe_function() # type: ignore[arg-type]
|
||||||
|
|
||||||
|
# Ignore entire file
|
||||||
|
# mypy: ignore-errors
|
||||||
|
```
|
||||||
|
|
||||||
|
## Third-Party Package Stubs
|
||||||
|
|
||||||
|
### Installing Type Stubs
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install stubs for requests
|
||||||
|
uv add --dev types-requests
|
||||||
|
|
||||||
|
# Common stubs
|
||||||
|
uv add --dev types-requests
|
||||||
|
uv add --dev types-redis
|
||||||
|
uv add --dev types-PyYAML
|
||||||
|
```
|
||||||
|
|
||||||
|
### Handling Missing Stubs
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[[tool.mypy.overrides]]
|
||||||
|
module = "untyped_package.*"
|
||||||
|
ignore_missing_imports = true
|
||||||
|
```
|
||||||
|
|
||||||
|
## VSCode Integration
|
||||||
|
|
||||||
|
Add to `.vscode/settings.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"python.linting.mypyEnabled": true,
|
||||||
|
"python.linting.enabled": true,
|
||||||
|
"python.analysis.typeCheckingMode": "basic"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
1. **Start gradual**: Enable mypy gradually, module by module
|
||||||
|
2. **Use strict mode**: `strict = true` for new projects
|
||||||
|
3. **Add type hints everywhere**: Functions, methods, variables
|
||||||
|
4. **Use Protocol for duck typing**: Better than inheritance
|
||||||
|
5. **Leverage modern syntax**: Use `list` instead of `List` (Python 3.9+)
|
||||||
|
6. **Install type stubs**: For all third-party packages
|
||||||
|
7. **Run in CI**: Fail builds on type errors
|
||||||
|
8. **Document with types**: Types serve as documentation
|
||||||
|
|
||||||
|
## Incremental Adoption
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# Start with specific directories
|
||||||
|
[tool.mypy]
|
||||||
|
files = ["src/critical_module"]
|
||||||
|
strict = true
|
||||||
|
|
||||||
|
# Gradually expand
|
||||||
|
files = ["src/critical_module", "src/api"]
|
||||||
|
|
||||||
|
# Eventually cover everything
|
||||||
|
files = ["src"]
|
||||||
|
```
|
||||||
229
skills/python-code-quality/ruff-configuration.md
Normal file
229
skills/python-code-quality/ruff-configuration.md
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
# Ruff Configuration Guide
|
||||||
|
|
||||||
|
Ruff is a fast Python linter and formatter that replaces multiple tools (isort, flake8, pyupgrade, etc.).
|
||||||
|
|
||||||
|
## Basic Configuration
|
||||||
|
|
||||||
|
Add to `pyproject.toml`:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[tool.ruff]
|
||||||
|
line-length = 100
|
||||||
|
target-version = "py311"
|
||||||
|
src = ["src", "tests"]
|
||||||
|
|
||||||
|
[tool.ruff.lint]
|
||||||
|
select = [
|
||||||
|
"E", # pycodestyle errors
|
||||||
|
"W", # pycodestyle warnings
|
||||||
|
"F", # pyflakes
|
||||||
|
"I", # isort
|
||||||
|
"B", # flake8-bugbear
|
||||||
|
"C4", # flake8-comprehensions
|
||||||
|
"UP", # pyupgrade
|
||||||
|
"ARG", # flake8-unused-arguments
|
||||||
|
"SIM", # flake8-simplify
|
||||||
|
"TCH", # flake8-type-checking
|
||||||
|
"RUF", # Ruff-specific rules
|
||||||
|
]
|
||||||
|
|
||||||
|
ignore = [
|
||||||
|
"E501", # line too long (handled by formatter)
|
||||||
|
"B008", # do not perform function calls in argument defaults
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.ruff.lint.per-file-ignores]
|
||||||
|
"tests/**/*.py" = [
|
||||||
|
"ARG", # Unused function arguments allowed in tests
|
||||||
|
"S101", # Assert allowed in tests
|
||||||
|
]
|
||||||
|
"__init__.py" = [
|
||||||
|
"F401", # Unused imports allowed in __init__.py
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.ruff.format]
|
||||||
|
quote-style = "double"
|
||||||
|
indent-style = "space"
|
||||||
|
line-ending = "auto"
|
||||||
|
|
||||||
|
[tool.ruff.lint.isort]
|
||||||
|
known-first-party = ["project_name"]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Rule Categories
|
||||||
|
|
||||||
|
### Error Detection (E, W, F)
|
||||||
|
- **E**: PEP 8 style errors
|
||||||
|
- **W**: PEP 8 style warnings
|
||||||
|
- **F**: PyFlakes logical errors
|
||||||
|
|
||||||
|
### Code Quality (B, C4, SIM)
|
||||||
|
- **B**: Bugbear - likely bugs and design problems
|
||||||
|
- **C4**: Comprehensions - simplify list/dict/set comprehensions
|
||||||
|
- **SIM**: Simplify - code simplification suggestions
|
||||||
|
|
||||||
|
### Modernization (UP)
|
||||||
|
- **UP**: PyUpgrade - use modern Python syntax
|
||||||
|
- `typing.List` → `list`
|
||||||
|
- `Optional[str]` → `str | None` (Python 3.10+)
|
||||||
|
|
||||||
|
### Organization (I)
|
||||||
|
- **I**: isort - import sorting and organization
|
||||||
|
|
||||||
|
### Performance (ARG, TCH)
|
||||||
|
- **ARG**: Detect unused function arguments
|
||||||
|
- **TCH**: Type checking imports (move to TYPE_CHECKING block)
|
||||||
|
|
||||||
|
## Common Rules
|
||||||
|
|
||||||
|
### E/W Series (PEP 8)
|
||||||
|
- `E111`: Indentation is not a multiple of 4
|
||||||
|
- `E201`: Whitespace after '('
|
||||||
|
- `E202`: Whitespace before ')'
|
||||||
|
- `E203`: Whitespace before ':'
|
||||||
|
- `E302`: Expected 2 blank lines
|
||||||
|
- `E303`: Too many blank lines
|
||||||
|
- `W291`: Trailing whitespace
|
||||||
|
|
||||||
|
### F Series (PyFlakes)
|
||||||
|
- `F401`: Unused import
|
||||||
|
- `F811`: Redefinition of unused name
|
||||||
|
- `F821`: Undefined name
|
||||||
|
- `F841`: Local variable assigned but never used
|
||||||
|
|
||||||
|
### B Series (Bugbear)
|
||||||
|
- `B002`: Using * in exception handling
|
||||||
|
- `B006`: Mutable default argument
|
||||||
|
- `B007`: Loop variable not used
|
||||||
|
- `B008`: Function calls in default arguments
|
||||||
|
- `B904`: Use `raise ... from ...` for exception chaining
|
||||||
|
|
||||||
|
### UP Series (PyUpgrade)
|
||||||
|
- `UP006`: Use `list` instead of `typing.List`
|
||||||
|
- `UP007`: Use `X | Y` instead of `typing.Union[X, Y]`
|
||||||
|
- `UP032`: Use f-strings instead of `.format()`
|
||||||
|
|
||||||
|
## Commands Reference
|
||||||
|
|
||||||
|
### Linting
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check all files
|
||||||
|
ruff check .
|
||||||
|
|
||||||
|
# Check specific file
|
||||||
|
ruff check src/module.py
|
||||||
|
|
||||||
|
# Auto-fix issues
|
||||||
|
ruff check --fix .
|
||||||
|
|
||||||
|
# Show fixes without applying
|
||||||
|
ruff check --diff .
|
||||||
|
|
||||||
|
# Watch mode (re-run on file changes)
|
||||||
|
ruff check --watch .
|
||||||
|
|
||||||
|
# Output JSON format
|
||||||
|
ruff check --output-format=json .
|
||||||
|
```
|
||||||
|
|
||||||
|
### Formatting
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Format all files
|
||||||
|
ruff format .
|
||||||
|
|
||||||
|
# Format specific file
|
||||||
|
ruff format src/module.py
|
||||||
|
|
||||||
|
# Check if formatting needed (CI)
|
||||||
|
ruff format --check .
|
||||||
|
|
||||||
|
# Show diff without applying
|
||||||
|
ruff format --diff .
|
||||||
|
```
|
||||||
|
|
||||||
|
### Configuration Testing
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Show active configuration
|
||||||
|
ruff check --show-settings
|
||||||
|
|
||||||
|
# Show all available rules
|
||||||
|
ruff rule --all
|
||||||
|
|
||||||
|
# Show specific rule documentation
|
||||||
|
ruff rule F401
|
||||||
|
```
|
||||||
|
|
||||||
|
## Per-File Ignores
|
||||||
|
|
||||||
|
Ignore specific rules for specific files:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[tool.ruff.lint.per-file-ignores]
|
||||||
|
"tests/**/*.py" = ["ARG", "S101"] # Tests can use assert and unused args
|
||||||
|
"__init__.py" = ["F401"] # Unused imports OK in __init__
|
||||||
|
"scripts/**/*.py" = ["T201"] # print() OK in scripts
|
||||||
|
"migrations/**/*.py" = ["E501"] # Long lines OK in migrations
|
||||||
|
```
|
||||||
|
|
||||||
|
## VSCode Integration
|
||||||
|
|
||||||
|
Add to `.vscode/settings.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"[python]": {
|
||||||
|
"editor.defaultFormatter": "charliermarsh.ruff",
|
||||||
|
"editor.formatOnSave": true,
|
||||||
|
"editor.codeActionsOnSave": {
|
||||||
|
"source.organizeImports": "explicit",
|
||||||
|
"source.fixAll": "explicit"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ruff.enable": true,
|
||||||
|
"ruff.lint.enable": true,
|
||||||
|
"ruff.format.enable": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Performance Tips
|
||||||
|
|
||||||
|
Ruff is extremely fast (10-100x faster than alternatives):
|
||||||
|
- Written in Rust for performance
|
||||||
|
- Parallel processing by default
|
||||||
|
- Incremental linting in watch mode
|
||||||
|
- Caching for repeated runs
|
||||||
|
|
||||||
|
## Migration from Other Tools
|
||||||
|
|
||||||
|
### From Black
|
||||||
|
Ruff's formatter is compatible with Black:
|
||||||
|
```bash
|
||||||
|
ruff format . # Replaces: black .
|
||||||
|
```
|
||||||
|
|
||||||
|
### From isort
|
||||||
|
```bash
|
||||||
|
ruff check --select I --fix . # Replaces: isort .
|
||||||
|
```
|
||||||
|
|
||||||
|
### From flake8
|
||||||
|
```bash
|
||||||
|
ruff check . # Replaces: flake8 .
|
||||||
|
```
|
||||||
|
|
||||||
|
### From pyupgrade
|
||||||
|
```bash
|
||||||
|
ruff check --select UP --fix . # Replaces: pyupgrade .
|
||||||
|
```
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
1. **Run formatter last**: `ruff check --fix . && ruff format .`
|
||||||
|
2. **Use in CI**: Check both linting and formatting
|
||||||
|
3. **Configure per-file ignores**: For tests, init files, migrations
|
||||||
|
4. **Enable auto-fix**: Most issues can be fixed automatically
|
||||||
|
5. **Use with pre-commit**: Run checks before commits
|
||||||
|
6. **Review rule changes**: Stay updated with new releases
|
||||||
76
skills/python-project-setup/SKILL.md
Normal file
76
skills/python-project-setup/SKILL.md
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
# Python Project Setup Skill
|
||||||
|
|
||||||
|
Set up new Python projects following modern best practices with uv, ruff, pytest, and proper project structure.
|
||||||
|
|
||||||
|
## When to Use This Skill
|
||||||
|
|
||||||
|
Use this skill when:
|
||||||
|
- User requests to create a new Python project
|
||||||
|
- User wants to initialize Python tooling in an existing directory
|
||||||
|
- User mentions setting up a Python development environment
|
||||||
|
- User asks about Python project structure or best practices
|
||||||
|
|
||||||
|
## Core Capabilities
|
||||||
|
|
||||||
|
1. **Project Initialization**
|
||||||
|
- Initialize projects with `uv init`
|
||||||
|
- Set up proper directory structure
|
||||||
|
- Configure Python version with pyenv
|
||||||
|
|
||||||
|
2. **Dependency Management**
|
||||||
|
- Install and configure uv package manager
|
||||||
|
- Add essential dependencies (pytest, ruff, mypy, pydantic)
|
||||||
|
- Set up development vs runtime dependencies
|
||||||
|
|
||||||
|
3. **Configuration Files**
|
||||||
|
- Create `pyproject.toml` with tool configurations
|
||||||
|
- Set up `.gitignore` for Python projects
|
||||||
|
- Configure VSCode settings for Python development
|
||||||
|
- Create `.python-version` for version management
|
||||||
|
|
||||||
|
4. **Project Structure**
|
||||||
|
- Create `src/<project_name>/` for source code
|
||||||
|
- Set up `tests/` directory for test files
|
||||||
|
- Create `docs/` for documentation
|
||||||
|
- Set up `.vscode/` for editor configuration
|
||||||
|
|
||||||
|
## Context Files
|
||||||
|
|
||||||
|
This skill references the following context files in this directory:
|
||||||
|
- `project-structure-template.md` - Standard directory layout
|
||||||
|
- `pyproject-toml-template.md` - Configuration template
|
||||||
|
- `vscode-settings-template.json` - Editor configuration
|
||||||
|
- `gitignore-template.md` - Python gitignore patterns
|
||||||
|
- `readme-template.md` - README structure
|
||||||
|
|
||||||
|
## Key Tools and Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Project initialization
|
||||||
|
uv init <project-name>
|
||||||
|
|
||||||
|
# Dependency management
|
||||||
|
uv add pytest --dev
|
||||||
|
uv add ruff --dev
|
||||||
|
uv add mypy --dev
|
||||||
|
uv add pydantic
|
||||||
|
|
||||||
|
# Sync dependencies
|
||||||
|
uv sync
|
||||||
|
```
|
||||||
|
|
||||||
|
## Expected Outcomes
|
||||||
|
|
||||||
|
After using this skill, the user should have:
|
||||||
|
- A fully initialized Python project with modern tooling
|
||||||
|
- Proper project structure following best practices
|
||||||
|
- All essential dependencies installed
|
||||||
|
- Configuration files set up correctly
|
||||||
|
- Version control initialized (git)
|
||||||
|
- Documentation scaffolding in place
|
||||||
|
|
||||||
|
## Integration with Other Skills
|
||||||
|
|
||||||
|
- Works with `python-code-quality` skill for linting setup
|
||||||
|
- Works with `python-testing` skill for test framework configuration
|
||||||
|
- Complements the `python-project-setup` agent for full orchestration
|
||||||
209
skills/python-project-setup/gitignore-template.md
Normal file
209
skills/python-project-setup/gitignore-template.md
Normal file
@@ -0,0 +1,209 @@
|
|||||||
|
# Python .gitignore Template
|
||||||
|
|
||||||
|
```gitignore
|
||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
share/python-wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
MANIFEST
|
||||||
|
|
||||||
|
# PyInstaller
|
||||||
|
*.manifest
|
||||||
|
*.spec
|
||||||
|
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.nox/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*.cover
|
||||||
|
*.py,cover
|
||||||
|
.hypothesis/
|
||||||
|
.pytest_cache/
|
||||||
|
cover/
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
# Django stuff:
|
||||||
|
*.log
|
||||||
|
local_settings.py
|
||||||
|
db.sqlite3
|
||||||
|
db.sqlite3-journal
|
||||||
|
|
||||||
|
# Flask stuff:
|
||||||
|
instance/
|
||||||
|
.webassets-cache
|
||||||
|
|
||||||
|
# Scrapy stuff:
|
||||||
|
.scrapy
|
||||||
|
|
||||||
|
# Sphinx documentation
|
||||||
|
docs/_build/
|
||||||
|
|
||||||
|
# PyBuilder
|
||||||
|
.pybuilder/
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Jupyter Notebook
|
||||||
|
.ipynb_checkpoints
|
||||||
|
|
||||||
|
# IPython
|
||||||
|
profile_default/
|
||||||
|
ipython_config.py
|
||||||
|
|
||||||
|
# pyenv
|
||||||
|
.python-version
|
||||||
|
|
||||||
|
# pipenv
|
||||||
|
Pipfile.lock
|
||||||
|
|
||||||
|
# poetry
|
||||||
|
poetry.lock
|
||||||
|
|
||||||
|
# pdm
|
||||||
|
.pdm.toml
|
||||||
|
|
||||||
|
# PEP 582
|
||||||
|
__pypackages__/
|
||||||
|
|
||||||
|
# Celery stuff
|
||||||
|
celerybeat-schedule
|
||||||
|
celerybeat.pid
|
||||||
|
|
||||||
|
# SageMath parsed files
|
||||||
|
*.sage.py
|
||||||
|
|
||||||
|
# Environments
|
||||||
|
.env
|
||||||
|
.venv
|
||||||
|
env/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
env.bak/
|
||||||
|
venv.bak/
|
||||||
|
|
||||||
|
# Spyder project settings
|
||||||
|
.spyderproject
|
||||||
|
.spyproject
|
||||||
|
|
||||||
|
# Rope project settings
|
||||||
|
.ropeproject
|
||||||
|
|
||||||
|
# mkdocs documentation
|
||||||
|
/site
|
||||||
|
|
||||||
|
# mypy
|
||||||
|
.mypy_cache/
|
||||||
|
.dmypy.json
|
||||||
|
dmypy.json
|
||||||
|
|
||||||
|
# Pyre type checker
|
||||||
|
.pyre/
|
||||||
|
|
||||||
|
# pytype static type analyzer
|
||||||
|
.pytype/
|
||||||
|
|
||||||
|
# Cython debug symbols
|
||||||
|
cython_debug/
|
||||||
|
|
||||||
|
# Ruff
|
||||||
|
.ruff_cache/
|
||||||
|
|
||||||
|
# uv
|
||||||
|
uv.lock
|
||||||
|
|
||||||
|
# IDEs
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# Project specific
|
||||||
|
*.db
|
||||||
|
*.sqlite
|
||||||
|
*.sqlite3
|
||||||
|
data/
|
||||||
|
logs/
|
||||||
|
tmp/
|
||||||
|
temp/
|
||||||
|
```
|
||||||
|
|
||||||
|
## Explanation of Key Patterns
|
||||||
|
|
||||||
|
### Python Runtime Files
|
||||||
|
- `__pycache__/` - Compiled bytecode cache
|
||||||
|
- `*.pyc`, `*.pyo`, `*.pyd` - Compiled Python files
|
||||||
|
- `*.so` - Compiled C extensions
|
||||||
|
|
||||||
|
### Package/Build Artifacts
|
||||||
|
- `dist/`, `build/` - Build output directories
|
||||||
|
- `*.egg-info/` - Package metadata
|
||||||
|
- `.eggs/` - Installed packages directory
|
||||||
|
|
||||||
|
### Virtual Environments
|
||||||
|
- `.venv/`, `venv/`, `env/` - Virtual environment directories
|
||||||
|
- Always create virtual environments, never commit them
|
||||||
|
|
||||||
|
### Testing & Coverage
|
||||||
|
- `.pytest_cache/` - Pytest cache
|
||||||
|
- `.coverage` - Coverage data files
|
||||||
|
- `htmlcov/` - HTML coverage reports
|
||||||
|
|
||||||
|
### Type Checking & Linting
|
||||||
|
- `.mypy_cache/` - Mypy cache
|
||||||
|
- `.ruff_cache/` - Ruff cache
|
||||||
|
- `.pytype/` - Pytype cache
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
- `docs/_build/` - Sphinx build output
|
||||||
|
- `/site` - MkDocs build output
|
||||||
|
|
||||||
|
### Environment Variables
|
||||||
|
- `.env` - Environment variable files (NEVER commit these)
|
||||||
|
- Should contain secrets, API keys, database URLs
|
||||||
|
|
||||||
|
### IDE Files
|
||||||
|
- `.vscode/` - VSCode settings (some teams commit this)
|
||||||
|
- `.idea/` - PyCharm settings
|
||||||
|
- `.DS_Store` - macOS file system metadata
|
||||||
|
|
||||||
|
## Customization Tips
|
||||||
|
|
||||||
|
1. **Commit `.vscode/` if desired**: Remove from .gitignore to share editor settings
|
||||||
|
2. **Project-specific data**: Add custom directories for data files, logs, etc.
|
||||||
|
3. **Lock files**: Consider keeping `uv.lock` for reproducible builds
|
||||||
|
4. **Documentation**: Remove `docs/_build/` if you want to commit built docs
|
||||||
112
skills/python-project-setup/project-structure-template.md
Normal file
112
skills/python-project-setup/project-structure-template.md
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
# Python Project Structure Template
|
||||||
|
|
||||||
|
## Standard Directory Layout
|
||||||
|
|
||||||
|
```
|
||||||
|
project_name/
|
||||||
|
├── .vscode/ # VSCode editor configuration
|
||||||
|
│ ├── settings.json # Python, ruff, mypy settings
|
||||||
|
│ └── launch.json # Debug configurations
|
||||||
|
├── src/ # Source code directory
|
||||||
|
│ └── project_name/ # Main package (use underscores)
|
||||||
|
│ ├── __init__.py # Package initialization
|
||||||
|
│ ├── main.py # Entry point (if applicable)
|
||||||
|
│ ├── models/ # Data models (Pydantic)
|
||||||
|
│ ├── services/ # Business logic
|
||||||
|
│ ├── api/ # API routes (if using FastAPI)
|
||||||
|
│ └── utils/ # Utility functions
|
||||||
|
├── tests/ # Test directory
|
||||||
|
│ ├── __init__.py
|
||||||
|
│ ├── conftest.py # Pytest fixtures
|
||||||
|
│ ├── test_main.py # Test files (prefix with test_)
|
||||||
|
│ └── integration/ # Integration tests
|
||||||
|
├── docs/ # Documentation
|
||||||
|
│ ├── index.md
|
||||||
|
│ └── api/ # API documentation
|
||||||
|
├── .github/ # GitHub configuration
|
||||||
|
│ ├── workflows/ # CI/CD workflows
|
||||||
|
│ │ └── test.yml
|
||||||
|
│ └── dependabot.yml # Dependency updates
|
||||||
|
├── .gitignore # Git ignore patterns
|
||||||
|
├── .python-version # Python version for pyenv
|
||||||
|
├── pyproject.toml # Project metadata and dependencies
|
||||||
|
├── README.md # Project documentation
|
||||||
|
└── uv.lock # Locked dependencies (generated)
|
||||||
|
```
|
||||||
|
|
||||||
|
## File Naming Conventions
|
||||||
|
|
||||||
|
- **Python packages**: Use underscores (e.g., `my_package`)
|
||||||
|
- **Project names**: Use underscores (e.g., `my_project`)
|
||||||
|
- **Test files**: Prefix with `test_` (e.g., `test_models.py`)
|
||||||
|
- **Private modules**: Prefix with `_` (e.g., `_internal.py`)
|
||||||
|
|
||||||
|
## Directory Purpose
|
||||||
|
|
||||||
|
### `src/project_name/`
|
||||||
|
Main application code organized by function:
|
||||||
|
- `models/` - Data classes, Pydantic models, database models
|
||||||
|
- `services/` - Business logic and service layer
|
||||||
|
- `api/` - API endpoints and route handlers (FastAPI)
|
||||||
|
- `utils/` - Helper functions and utilities
|
||||||
|
- `config/` - Configuration management
|
||||||
|
|
||||||
|
### `tests/`
|
||||||
|
Test files mirroring the structure of `src/`:
|
||||||
|
- Unit tests for each module
|
||||||
|
- Integration tests in `integration/` subdirectory
|
||||||
|
- `conftest.py` for shared fixtures
|
||||||
|
- Use pytest conventions
|
||||||
|
|
||||||
|
### `docs/`
|
||||||
|
Project documentation:
|
||||||
|
- User guides
|
||||||
|
- API documentation
|
||||||
|
- Architecture decisions
|
||||||
|
- Setup instructions
|
||||||
|
|
||||||
|
## Module Organization
|
||||||
|
|
||||||
|
Each Python module should follow this pattern:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# frozen_string_literal equivalent in Python
|
||||||
|
"""Module docstring describing purpose."""
|
||||||
|
|
||||||
|
from typing import Any, Optional
|
||||||
|
|
||||||
|
import third_party_package
|
||||||
|
|
||||||
|
from project_name import local_module
|
||||||
|
|
||||||
|
|
||||||
|
class MyClass:
|
||||||
|
"""Class docstring."""
|
||||||
|
|
||||||
|
def __init__(self, param: str) -> None:
|
||||||
|
"""Initialize with param."""
|
||||||
|
self.param = param
|
||||||
|
|
||||||
|
def method(self) -> str:
|
||||||
|
"""Method docstring."""
|
||||||
|
return self.param
|
||||||
|
|
||||||
|
|
||||||
|
def public_function() -> None:
|
||||||
|
"""Public function docstring."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def _private_function() -> None:
|
||||||
|
"""Private helper function."""
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
1. **Use `src/` layout**: Prevents accidental imports of local code
|
||||||
|
2. **Type hints everywhere**: Add type annotations to all functions
|
||||||
|
3. **Docstrings**: Document all public classes and functions
|
||||||
|
4. **Test organization**: Mirror source structure in tests
|
||||||
|
5. **Configuration**: Use environment variables or config files, never hardcode
|
||||||
|
6. **Dependencies**: Separate dev dependencies from runtime dependencies
|
||||||
226
skills/python-project-setup/pyproject-toml-template.md
Normal file
226
skills/python-project-setup/pyproject-toml-template.md
Normal file
@@ -0,0 +1,226 @@
|
|||||||
|
# pyproject.toml Template
|
||||||
|
|
||||||
|
## Complete Configuration Template
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[project]
|
||||||
|
name = "project-name"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Project description"
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.11"
|
||||||
|
license = {text = "MIT"}
|
||||||
|
authors = [
|
||||||
|
{name = "Your Name", email = "you@example.com"}
|
||||||
|
]
|
||||||
|
keywords = ["python", "example"]
|
||||||
|
classifiers = [
|
||||||
|
"Development Status :: 3 - Alpha",
|
||||||
|
"Intended Audience :: Developers",
|
||||||
|
"License :: OSI Approved :: MIT License",
|
||||||
|
"Programming Language :: Python :: 3",
|
||||||
|
"Programming Language :: Python :: 3.11",
|
||||||
|
"Programming Language :: Python :: 3.12",
|
||||||
|
]
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
"pydantic>=2.0.0",
|
||||||
|
# Add runtime dependencies here
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.optional-dependencies]
|
||||||
|
dev = [
|
||||||
|
"pytest>=8.0.0",
|
||||||
|
"pytest-cov>=4.1.0",
|
||||||
|
"ruff>=0.3.0",
|
||||||
|
"mypy>=1.8.0",
|
||||||
|
]
|
||||||
|
api = [
|
||||||
|
"fastapi>=0.110.0",
|
||||||
|
"uvicorn[standard]>=0.27.0",
|
||||||
|
]
|
||||||
|
docs = [
|
||||||
|
"mkdocs>=1.5.0",
|
||||||
|
"mkdocs-material>=9.5.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.urls]
|
||||||
|
Homepage = "https://github.com/username/project-name"
|
||||||
|
Repository = "https://github.com/username/project-name"
|
||||||
|
Documentation = "https://project-name.readthedocs.io"
|
||||||
|
Issues = "https://github.com/username/project-name/issues"
|
||||||
|
|
||||||
|
[project.scripts]
|
||||||
|
# Define CLI entry points
|
||||||
|
project-cli = "project_name.cli:main"
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["hatchling"]
|
||||||
|
build-backend = "hatchling.build"
|
||||||
|
|
||||||
|
[tool.hatch.build.targets.wheel]
|
||||||
|
packages = ["src/project_name"]
|
||||||
|
|
||||||
|
# Ruff configuration
|
||||||
|
[tool.ruff]
|
||||||
|
line-length = 100
|
||||||
|
target-version = "py311"
|
||||||
|
src = ["src", "tests"]
|
||||||
|
|
||||||
|
[tool.ruff.lint]
|
||||||
|
select = [
|
||||||
|
"E", # pycodestyle errors
|
||||||
|
"W", # pycodestyle warnings
|
||||||
|
"F", # pyflakes
|
||||||
|
"I", # isort
|
||||||
|
"B", # flake8-bugbear
|
||||||
|
"C4", # flake8-comprehensions
|
||||||
|
"UP", # pyupgrade
|
||||||
|
"ARG", # flake8-unused-arguments
|
||||||
|
"SIM", # flake8-simplify
|
||||||
|
"TCH", # flake8-type-checking
|
||||||
|
]
|
||||||
|
ignore = [
|
||||||
|
"E501", # line too long (handled by formatter)
|
||||||
|
"B008", # do not perform function calls in argument defaults
|
||||||
|
"B904", # raise from None
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.ruff.lint.per-file-ignores]
|
||||||
|
"tests/**/*.py" = [
|
||||||
|
"ARG", # Unused function arguments allowed in tests
|
||||||
|
"S101", # Assert allowed in tests
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.ruff.format]
|
||||||
|
quote-style = "double"
|
||||||
|
indent-style = "space"
|
||||||
|
line-ending = "auto"
|
||||||
|
|
||||||
|
# Pytest configuration
|
||||||
|
[tool.pytest.ini_options]
|
||||||
|
testpaths = ["tests"]
|
||||||
|
python_files = ["test_*.py"]
|
||||||
|
python_classes = ["Test*"]
|
||||||
|
python_functions = ["test_*"]
|
||||||
|
addopts = [
|
||||||
|
"--strict-markers",
|
||||||
|
"--strict-config",
|
||||||
|
"--showlocals",
|
||||||
|
"-ra",
|
||||||
|
]
|
||||||
|
markers = [
|
||||||
|
"slow: marks tests as slow (deselect with '-m \"not slow\"')",
|
||||||
|
"integration: marks tests as integration tests",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Coverage configuration
|
||||||
|
[tool.coverage.run]
|
||||||
|
source = ["src"]
|
||||||
|
branch = true
|
||||||
|
omit = [
|
||||||
|
"*/tests/*",
|
||||||
|
"*/__init__.py",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.coverage.report]
|
||||||
|
precision = 2
|
||||||
|
show_missing = true
|
||||||
|
skip_covered = false
|
||||||
|
exclude_lines = [
|
||||||
|
"pragma: no cover",
|
||||||
|
"def __repr__",
|
||||||
|
"raise AssertionError",
|
||||||
|
"raise NotImplementedError",
|
||||||
|
"if __name__ == .__main__.:",
|
||||||
|
"if TYPE_CHECKING:",
|
||||||
|
"@abstractmethod",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Mypy configuration
|
||||||
|
[tool.mypy]
|
||||||
|
python_version = "3.11"
|
||||||
|
warn_return_any = true
|
||||||
|
warn_unused_configs = true
|
||||||
|
disallow_untyped_defs = true
|
||||||
|
disallow_incomplete_defs = true
|
||||||
|
check_untyped_defs = true
|
||||||
|
no_implicit_optional = true
|
||||||
|
warn_redundant_casts = true
|
||||||
|
warn_unused_ignores = true
|
||||||
|
warn_no_return = true
|
||||||
|
strict_equality = true
|
||||||
|
|
||||||
|
[[tool.mypy.overrides]]
|
||||||
|
module = "tests.*"
|
||||||
|
disallow_untyped_defs = false
|
||||||
|
|
||||||
|
# UV-specific configuration (if needed)
|
||||||
|
[tool.uv]
|
||||||
|
dev-dependencies = [
|
||||||
|
"pytest>=8.0.0",
|
||||||
|
"pytest-cov>=4.1.0",
|
||||||
|
"ruff>=0.3.0",
|
||||||
|
"mypy>=1.8.0",
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration Sections Explained
|
||||||
|
|
||||||
|
### `[project]`
|
||||||
|
Basic project metadata used by build tools and package indexes.
|
||||||
|
|
||||||
|
### `[project.optional-dependencies]`
|
||||||
|
Groups of optional dependencies for different use cases:
|
||||||
|
- `dev` - Development tools (testing, linting)
|
||||||
|
- `api` - API framework dependencies (FastAPI, uvicorn)
|
||||||
|
- `docs` - Documentation generation tools
|
||||||
|
|
||||||
|
### `[tool.ruff]`
|
||||||
|
Ruff linter and formatter configuration:
|
||||||
|
- Fast Python linter combining multiple tools
|
||||||
|
- Replaces isort, flake8, pyupgrade, and more
|
||||||
|
- Automatic code formatting
|
||||||
|
|
||||||
|
### `[tool.pytest.ini_options]`
|
||||||
|
Pytest test framework configuration:
|
||||||
|
- Test discovery patterns
|
||||||
|
- Default command-line options
|
||||||
|
- Custom test markers
|
||||||
|
|
||||||
|
### `[tool.coverage]`
|
||||||
|
Code coverage settings for pytest-cov:
|
||||||
|
- Which files to measure
|
||||||
|
- Coverage reporting options
|
||||||
|
- Lines to exclude from coverage
|
||||||
|
|
||||||
|
### `[tool.mypy]`
|
||||||
|
Type checking configuration:
|
||||||
|
- Strictness settings
|
||||||
|
- Type checking rules
|
||||||
|
- Per-directory overrides
|
||||||
|
|
||||||
|
## Usage Examples
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install project with dev dependencies
|
||||||
|
uv sync --extra dev
|
||||||
|
|
||||||
|
# Install with API dependencies
|
||||||
|
uv sync --extra api
|
||||||
|
|
||||||
|
# Install all optional dependencies
|
||||||
|
uv sync --all-extras
|
||||||
|
|
||||||
|
# Run tests with coverage
|
||||||
|
uv run pytest --cov
|
||||||
|
|
||||||
|
# Lint code
|
||||||
|
uv run ruff check .
|
||||||
|
|
||||||
|
# Format code
|
||||||
|
uv run ruff format .
|
||||||
|
|
||||||
|
# Type check
|
||||||
|
uv run mypy src
|
||||||
|
```
|
||||||
332
skills/python-project-setup/readme-template.md
Normal file
332
skills/python-project-setup/readme-template.md
Normal file
@@ -0,0 +1,332 @@
|
|||||||
|
# README.md Template
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# Project Name
|
||||||
|
|
||||||
|
Brief one-line description of what this project does.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
A more detailed description of the project, its purpose, and key features.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Feature 1: Description
|
||||||
|
- Feature 2: Description
|
||||||
|
- Feature 3: Description
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- Python 3.11 or higher
|
||||||
|
- uv package manager
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### Using uv (Recommended)
|
||||||
|
|
||||||
|
\`\`\`bash
|
||||||
|
# Clone the repository
|
||||||
|
git clone https://github.com/username/project-name.git
|
||||||
|
cd project-name
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
uv sync
|
||||||
|
|
||||||
|
# Activate virtual environment (optional, uv runs commands automatically)
|
||||||
|
source .venv/bin/activate # On Unix/macOS
|
||||||
|
.venv\Scripts\activate # On Windows
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### Using pip
|
||||||
|
|
||||||
|
\`\`\`bash
|
||||||
|
pip install -e .
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
\`\`\`python
|
||||||
|
from project_name import main_function
|
||||||
|
|
||||||
|
# Example usage
|
||||||
|
result = main_function()
|
||||||
|
print(result)
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Command Line Interface
|
||||||
|
|
||||||
|
\`\`\`bash
|
||||||
|
# Run the main application
|
||||||
|
uv run python -m project_name
|
||||||
|
|
||||||
|
# Or if you installed with pip
|
||||||
|
project-cli --help
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### As a Library
|
||||||
|
|
||||||
|
\`\`\`python
|
||||||
|
from project_name import SomeClass
|
||||||
|
|
||||||
|
# Create an instance
|
||||||
|
instance = SomeClass(param="value")
|
||||||
|
|
||||||
|
# Use the instance
|
||||||
|
result = instance.method()
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
### Setup Development Environment
|
||||||
|
|
||||||
|
\`\`\`bash
|
||||||
|
# Install with development dependencies
|
||||||
|
uv sync --extra dev
|
||||||
|
|
||||||
|
# Or install all extras
|
||||||
|
uv sync --all-extras
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### Running Tests
|
||||||
|
|
||||||
|
\`\`\`bash
|
||||||
|
# Run all tests
|
||||||
|
uv run pytest
|
||||||
|
|
||||||
|
# Run with coverage
|
||||||
|
uv run pytest --cov
|
||||||
|
|
||||||
|
# Run specific test file
|
||||||
|
uv run pytest tests/test_specific.py
|
||||||
|
|
||||||
|
# Run with verbose output
|
||||||
|
uv run pytest -v
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### Code Quality
|
||||||
|
|
||||||
|
\`\`\`bash
|
||||||
|
# Lint code
|
||||||
|
uv run ruff check .
|
||||||
|
|
||||||
|
# Fix auto-fixable issues
|
||||||
|
uv run ruff check --fix .
|
||||||
|
|
||||||
|
# Format code
|
||||||
|
uv run ruff format .
|
||||||
|
|
||||||
|
# Type checking
|
||||||
|
uv run mypy src
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### Pre-commit Checks
|
||||||
|
|
||||||
|
Before committing, ensure all checks pass:
|
||||||
|
|
||||||
|
\`\`\`bash
|
||||||
|
# Run all checks
|
||||||
|
uv run ruff check . && uv run ruff format . && uv run mypy src && uv run pytest
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
\`\`\`
|
||||||
|
project-name/
|
||||||
|
├── src/
|
||||||
|
│ └── project_name/ # Main package
|
||||||
|
│ ├── __init__.py
|
||||||
|
│ ├── main.py # Entry point
|
||||||
|
│ ├── models/ # Data models
|
||||||
|
│ ├── services/ # Business logic
|
||||||
|
│ └── utils/ # Utilities
|
||||||
|
├── tests/ # Test files
|
||||||
|
├── docs/ # Documentation
|
||||||
|
├── pyproject.toml # Project configuration
|
||||||
|
└── README.md # This file
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
### Environment Variables
|
||||||
|
|
||||||
|
Create a `.env` file in the project root:
|
||||||
|
|
||||||
|
\`\`\`env
|
||||||
|
API_KEY=your_api_key_here
|
||||||
|
DATABASE_URL=postgresql://user:pass@localhost/dbname
|
||||||
|
DEBUG=false
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### Configuration File
|
||||||
|
|
||||||
|
Alternatively, create a `config.yaml`:
|
||||||
|
|
||||||
|
\`\`\`yaml
|
||||||
|
api:
|
||||||
|
key: your_api_key
|
||||||
|
timeout: 30
|
||||||
|
|
||||||
|
database:
|
||||||
|
url: postgresql://user:pass@localhost/dbname
|
||||||
|
pool_size: 5
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
## API Documentation
|
||||||
|
|
||||||
|
### Main Classes
|
||||||
|
|
||||||
|
#### `SomeClass`
|
||||||
|
|
||||||
|
Description of the class.
|
||||||
|
|
||||||
|
\`\`\`python
|
||||||
|
from project_name import SomeClass
|
||||||
|
|
||||||
|
instance = SomeClass(param="value")
|
||||||
|
result = instance.method()
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
- `param` (str): Description of parameter
|
||||||
|
|
||||||
|
**Returns:**
|
||||||
|
- `str`: Description of return value
|
||||||
|
|
||||||
|
### Functions
|
||||||
|
|
||||||
|
#### `main_function()`
|
||||||
|
|
||||||
|
Description of the function.
|
||||||
|
|
||||||
|
\`\`\`python
|
||||||
|
from project_name import main_function
|
||||||
|
|
||||||
|
result = main_function()
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
## API Endpoints (if applicable)
|
||||||
|
|
||||||
|
### Start the Server
|
||||||
|
|
||||||
|
\`\`\`bash
|
||||||
|
uv run uvicorn project_name.api.main:app --reload
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### Endpoints
|
||||||
|
|
||||||
|
#### GET `/api/items`
|
||||||
|
|
||||||
|
Get all items.
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
\`\`\`json
|
||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{"id": 1, "name": "Item 1"},
|
||||||
|
{"id": 2, "name": "Item 2"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
#### POST `/api/items`
|
||||||
|
|
||||||
|
Create a new item.
|
||||||
|
|
||||||
|
**Request Body:**
|
||||||
|
\`\`\`json
|
||||||
|
{
|
||||||
|
"name": "New Item"
|
||||||
|
}
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
\`\`\`json
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"name": "New Item"
|
||||||
|
}
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Contributions are welcome! Please follow these steps:
|
||||||
|
|
||||||
|
1. Fork the repository
|
||||||
|
2. Create a feature branch (\`git checkout -b feature/amazing-feature\`)
|
||||||
|
3. Make your changes
|
||||||
|
4. Run tests and linting (\`uv run pytest && uv run ruff check .\`)
|
||||||
|
5. Commit your changes (\`git commit -m 'Add amazing feature'\`)
|
||||||
|
6. Push to the branch (\`git push origin feature/amazing-feature\`)
|
||||||
|
7. Open a Pull Request
|
||||||
|
|
||||||
|
### Code Style
|
||||||
|
|
||||||
|
- Follow PEP 8 guidelines
|
||||||
|
- Use type hints for all functions
|
||||||
|
- Write docstrings for all public APIs
|
||||||
|
- Maintain test coverage above 80%
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
||||||
|
|
||||||
|
## Authors
|
||||||
|
|
||||||
|
- Your Name - [@yourhandle](https://github.com/yourhandle)
|
||||||
|
|
||||||
|
## Acknowledgments
|
||||||
|
|
||||||
|
- Library or resource that inspired this project
|
||||||
|
- Contributors who helped
|
||||||
|
- Any other acknowledgments
|
||||||
|
|
||||||
|
## Changelog
|
||||||
|
|
||||||
|
### [0.1.0] - 2024-01-01
|
||||||
|
|
||||||
|
#### Added
|
||||||
|
- Initial release
|
||||||
|
- Basic functionality
|
||||||
|
|
||||||
|
#### Changed
|
||||||
|
- Nothing yet
|
||||||
|
|
||||||
|
#### Fixed
|
||||||
|
- Nothing yet
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
For support, email support@example.com or open an issue on GitHub.
|
||||||
|
|
||||||
|
## Links
|
||||||
|
|
||||||
|
- [Documentation](https://project-name.readthedocs.io)
|
||||||
|
- [Issue Tracker](https://github.com/username/project-name/issues)
|
||||||
|
- [Changelog](CHANGELOG.md)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Sections Explained
|
||||||
|
|
||||||
|
1. **Title & Description**: Clear project name and one-line summary
|
||||||
|
2. **Overview**: More detailed description of purpose and features
|
||||||
|
3. **Installation**: Step-by-step setup instructions
|
||||||
|
4. **Quick Start**: Minimal example to get started quickly
|
||||||
|
5. **Usage**: Detailed usage examples (CLI and library)
|
||||||
|
6. **Development**: Instructions for contributors
|
||||||
|
7. **Project Structure**: Overview of codebase organization
|
||||||
|
8. **Configuration**: How to configure the application
|
||||||
|
9. **API Documentation**: Documentation of public API
|
||||||
|
10. **Contributing**: Guidelines for contributions
|
||||||
|
11. **License & Authors**: Legal and attribution information
|
||||||
|
12. **Support**: How to get help
|
||||||
|
|
||||||
|
## Tips for Good READMEs
|
||||||
|
|
||||||
|
- **Keep it concise**: Users should understand the project in 30 seconds
|
||||||
|
- **Working examples**: All code examples should be copy-paste ready
|
||||||
|
- **Clear installation**: Step-by-step instructions that actually work
|
||||||
|
- **Visual aids**: Consider adding screenshots, diagrams, or GIFs
|
||||||
|
- **Badges**: Add CI status, coverage, and version badges
|
||||||
|
- **Update regularly**: Keep it in sync with the actual codebase
|
||||||
54
skills/python-project-setup/vscode-settings-template.json
Normal file
54
skills/python-project-setup/vscode-settings-template.json
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
{
|
||||||
|
"python.defaultInterpreterPath": "${workspaceFolder}/.venv/bin/python",
|
||||||
|
"python.analysis.typeCheckingMode": "basic",
|
||||||
|
"python.analysis.autoImportCompletions": true,
|
||||||
|
"python.analysis.diagnosticMode": "workspace",
|
||||||
|
|
||||||
|
"[python]": {
|
||||||
|
"editor.defaultFormatter": "charliermarsh.ruff",
|
||||||
|
"editor.formatOnSave": true,
|
||||||
|
"editor.codeActionsOnSave": {
|
||||||
|
"source.organizeImports": "explicit",
|
||||||
|
"source.fixAll": "explicit"
|
||||||
|
},
|
||||||
|
"editor.rulers": [100]
|
||||||
|
},
|
||||||
|
|
||||||
|
"ruff.enable": true,
|
||||||
|
"ruff.lint.enable": true,
|
||||||
|
"ruff.format.enable": true,
|
||||||
|
"ruff.organizeImports": true,
|
||||||
|
|
||||||
|
"python.testing.pytestEnabled": true,
|
||||||
|
"python.testing.unittestEnabled": false,
|
||||||
|
"python.testing.pytestArgs": [
|
||||||
|
"tests"
|
||||||
|
],
|
||||||
|
|
||||||
|
"files.exclude": {
|
||||||
|
"**/__pycache__": true,
|
||||||
|
"**/*.pyc": true,
|
||||||
|
"**/*.pyo": true,
|
||||||
|
"**/.pytest_cache": true,
|
||||||
|
"**/.mypy_cache": true,
|
||||||
|
"**/.ruff_cache": true,
|
||||||
|
"**/*.egg-info": true
|
||||||
|
},
|
||||||
|
|
||||||
|
"files.watcherExclude": {
|
||||||
|
"**/__pycache__/**": true,
|
||||||
|
"**/.venv/**": true,
|
||||||
|
"**/.pytest_cache/**": true,
|
||||||
|
"**/.mypy_cache/**": true,
|
||||||
|
"**/.ruff_cache/**": true
|
||||||
|
},
|
||||||
|
|
||||||
|
"search.exclude": {
|
||||||
|
"**/.venv": true,
|
||||||
|
"**/node_modules": true,
|
||||||
|
"**/__pycache__": true,
|
||||||
|
"**/.pytest_cache": true,
|
||||||
|
"**/.mypy_cache": true,
|
||||||
|
"**/.ruff_cache": true
|
||||||
|
}
|
||||||
|
}
|
||||||
111
skills/python-testing/SKILL.md
Normal file
111
skills/python-testing/SKILL.md
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
# Python Testing Skill
|
||||||
|
|
||||||
|
Comprehensive testing with pytest including unit tests, integration tests, fixtures, and coverage reporting.
|
||||||
|
|
||||||
|
## When to Use This Skill
|
||||||
|
|
||||||
|
Use this skill when:
|
||||||
|
- User requests to run tests
|
||||||
|
- User wants to create new tests
|
||||||
|
- User asks about test coverage
|
||||||
|
- User mentions pytest, unit tests, or integration tests
|
||||||
|
- Debugging test failures
|
||||||
|
|
||||||
|
## Core Capabilities
|
||||||
|
|
||||||
|
1. **Test Execution**
|
||||||
|
- Run all tests or specific test files
|
||||||
|
- Run tests matching patterns
|
||||||
|
- Run only failed tests
|
||||||
|
- Generate test reports
|
||||||
|
|
||||||
|
2. **Test Coverage**
|
||||||
|
- Measure code coverage
|
||||||
|
- Generate HTML coverage reports
|
||||||
|
- Identify untested code
|
||||||
|
- Set coverage thresholds
|
||||||
|
|
||||||
|
3. **Test Writing**
|
||||||
|
- Create unit tests for functions and classes
|
||||||
|
- Write integration tests
|
||||||
|
- Use pytest fixtures effectively
|
||||||
|
- Implement parametrized tests
|
||||||
|
|
||||||
|
4. **Test Organization**
|
||||||
|
- Structure test files properly
|
||||||
|
- Use conftest.py for shared fixtures
|
||||||
|
- Organize tests by feature or module
|
||||||
|
- Mark tests for selective execution
|
||||||
|
|
||||||
|
## Context Files
|
||||||
|
|
||||||
|
This skill references the following context files in this directory:
|
||||||
|
- `pytest-configuration.md` - Pytest setup and configuration
|
||||||
|
- `test-patterns.md` - Common testing patterns and examples
|
||||||
|
- `fixtures-guide.md` - Using pytest fixtures
|
||||||
|
- `coverage-guide.md` - Code coverage best practices
|
||||||
|
|
||||||
|
## Key Tools and Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run tests
|
||||||
|
uv run pytest # All tests
|
||||||
|
uv run pytest tests/test_file.py # Specific file
|
||||||
|
uv run pytest -k "pattern" # Tests matching pattern
|
||||||
|
uv run pytest --lf # Last failed only
|
||||||
|
|
||||||
|
# Coverage
|
||||||
|
uv run pytest --cov # With coverage
|
||||||
|
uv run pytest --cov --cov-report=html # HTML report
|
||||||
|
uv run pytest --cov --cov-report=term-missing # Show missing lines
|
||||||
|
|
||||||
|
# Output control
|
||||||
|
uv run pytest -v # Verbose
|
||||||
|
uv run pytest -s # Show print statements
|
||||||
|
uv run pytest -x # Stop on first failure
|
||||||
|
```
|
||||||
|
|
||||||
|
## Common Test Patterns
|
||||||
|
|
||||||
|
### Unit Test Example
|
||||||
|
```python
|
||||||
|
def test_addition():
|
||||||
|
assert add(2, 3) == 5
|
||||||
|
assert add(-1, 1) == 0
|
||||||
|
```
|
||||||
|
|
||||||
|
### Fixture Usage
|
||||||
|
```python
|
||||||
|
@pytest.fixture
|
||||||
|
def sample_data():
|
||||||
|
return {"key": "value"}
|
||||||
|
|
||||||
|
def test_with_fixture(sample_data):
|
||||||
|
assert sample_data["key"] == "value"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Parametrized Tests
|
||||||
|
```python
|
||||||
|
@pytest.mark.parametrize("input,expected", [
|
||||||
|
(2, 4),
|
||||||
|
(3, 9),
|
||||||
|
(4, 16),
|
||||||
|
])
|
||||||
|
def test_square(input, expected):
|
||||||
|
assert square(input) == expected
|
||||||
|
```
|
||||||
|
|
||||||
|
## Expected Outcomes
|
||||||
|
|
||||||
|
After using this skill:
|
||||||
|
- Comprehensive test suite covering critical functionality
|
||||||
|
- High test coverage (ideally > 80%)
|
||||||
|
- Well-organized test files
|
||||||
|
- Clear test failure messages
|
||||||
|
- Fast test execution
|
||||||
|
|
||||||
|
## Integration with Other Skills
|
||||||
|
|
||||||
|
- Works with `python-project-setup` for test directory structure
|
||||||
|
- Complements `python-code-quality` for comprehensive QA
|
||||||
|
- Used by `python-project-setup` agent for automated testing
|
||||||
447
skills/python-testing/pytest-configuration.md
Normal file
447
skills/python-testing/pytest-configuration.md
Normal file
@@ -0,0 +1,447 @@
|
|||||||
|
# Pytest Configuration Guide
|
||||||
|
|
||||||
|
Pytest is a powerful testing framework for Python that makes it easy to write simple and scalable tests.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv add --dev pytest
|
||||||
|
uv add --dev pytest-cov # For coverage
|
||||||
|
uv add --dev pytest-mock # For mocking
|
||||||
|
uv add --dev pytest-asyncio # For async tests
|
||||||
|
```
|
||||||
|
|
||||||
|
## Basic Configuration
|
||||||
|
|
||||||
|
Add to `pyproject.toml`:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[tool.pytest.ini_options]
|
||||||
|
testpaths = ["tests"]
|
||||||
|
python_files = ["test_*.py", "*_test.py"]
|
||||||
|
python_classes = ["Test*"]
|
||||||
|
python_functions = ["test_*"]
|
||||||
|
addopts = [
|
||||||
|
"--strict-markers",
|
||||||
|
"--strict-config",
|
||||||
|
"--showlocals",
|
||||||
|
"-ra",
|
||||||
|
]
|
||||||
|
markers = [
|
||||||
|
"slow: marks tests as slow (deselect with '-m \"not slow\"')",
|
||||||
|
"integration: marks tests as integration tests",
|
||||||
|
"unit: marks tests as unit tests",
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Directory Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
project/
|
||||||
|
├── src/
|
||||||
|
│ └── project_name/
|
||||||
|
│ └── module.py
|
||||||
|
├── tests/
|
||||||
|
│ ├── __init__.py
|
||||||
|
│ ├── conftest.py # Shared fixtures
|
||||||
|
│ ├── test_module.py # Unit tests
|
||||||
|
│ ├── integration/
|
||||||
|
│ │ ├── __init__.py
|
||||||
|
│ │ └── test_api.py # Integration tests
|
||||||
|
│ └── fixtures/
|
||||||
|
│ └── sample_data.json
|
||||||
|
```
|
||||||
|
|
||||||
|
## Writing Tests
|
||||||
|
|
||||||
|
### Basic Test Function
|
||||||
|
|
||||||
|
```python
|
||||||
|
def test_simple_addition():
|
||||||
|
"""Test that addition works correctly."""
|
||||||
|
result = add(2, 3)
|
||||||
|
assert result == 5
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test Class
|
||||||
|
|
||||||
|
```python
|
||||||
|
class TestCalculator:
|
||||||
|
"""Tests for Calculator class."""
|
||||||
|
|
||||||
|
def test_addition(self):
|
||||||
|
calc = Calculator()
|
||||||
|
assert calc.add(2, 3) == 5
|
||||||
|
|
||||||
|
def test_subtraction(self):
|
||||||
|
calc = Calculator()
|
||||||
|
assert calc.subtract(5, 3) == 2
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using Assertions
|
||||||
|
|
||||||
|
```python
|
||||||
|
def test_assertions():
|
||||||
|
# Equality
|
||||||
|
assert result == expected
|
||||||
|
|
||||||
|
# Boolean
|
||||||
|
assert is_valid()
|
||||||
|
assert not is_invalid()
|
||||||
|
|
||||||
|
# Membership
|
||||||
|
assert item in collection
|
||||||
|
assert key in dictionary
|
||||||
|
|
||||||
|
# Type checking
|
||||||
|
assert isinstance(obj, MyClass)
|
||||||
|
|
||||||
|
# Exceptions
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
raise_error()
|
||||||
|
|
||||||
|
# Approximate equality (floats)
|
||||||
|
assert result == pytest.approx(expected, rel=1e-5)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fixtures
|
||||||
|
|
||||||
|
### Basic Fixture
|
||||||
|
|
||||||
|
```python
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def sample_user():
|
||||||
|
"""Create a sample user for testing."""
|
||||||
|
return User(name="Alice", age=30)
|
||||||
|
|
||||||
|
def test_user_greeting(sample_user):
|
||||||
|
assert sample_user.greet() == "Hello, I'm Alice"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Fixture Scopes
|
||||||
|
|
||||||
|
```python
|
||||||
|
@pytest.fixture(scope="function") # Default, new instance per test
|
||||||
|
def function_scope():
|
||||||
|
return setup()
|
||||||
|
|
||||||
|
@pytest.fixture(scope="class") # One instance per test class
|
||||||
|
def class_scope():
|
||||||
|
return setup()
|
||||||
|
|
||||||
|
@pytest.fixture(scope="module") # One instance per module
|
||||||
|
def module_scope():
|
||||||
|
return setup()
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session") # One instance per test session
|
||||||
|
def session_scope():
|
||||||
|
return setup()
|
||||||
|
```
|
||||||
|
|
||||||
|
### Fixture Cleanup
|
||||||
|
|
||||||
|
```python
|
||||||
|
@pytest.fixture
|
||||||
|
def resource():
|
||||||
|
# Setup
|
||||||
|
res = acquire_resource()
|
||||||
|
yield res
|
||||||
|
# Teardown
|
||||||
|
res.cleanup()
|
||||||
|
|
||||||
|
# Or using context manager
|
||||||
|
@pytest.fixture
|
||||||
|
def database():
|
||||||
|
with create_database() as db:
|
||||||
|
yield db
|
||||||
|
# Automatic cleanup
|
||||||
|
```
|
||||||
|
|
||||||
|
### Fixture Dependencies
|
||||||
|
|
||||||
|
```python
|
||||||
|
@pytest.fixture
|
||||||
|
def database():
|
||||||
|
return Database()
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def user_repository(database):
|
||||||
|
return UserRepository(database)
|
||||||
|
|
||||||
|
def test_find_user(user_repository):
|
||||||
|
user = user_repository.find(1)
|
||||||
|
assert user is not None
|
||||||
|
```
|
||||||
|
|
||||||
|
## Parametrized Tests
|
||||||
|
|
||||||
|
### Basic Parametrization
|
||||||
|
|
||||||
|
```python
|
||||||
|
@pytest.mark.parametrize("input,expected", [
|
||||||
|
(2, 4),
|
||||||
|
(3, 9),
|
||||||
|
(4, 16),
|
||||||
|
(5, 25),
|
||||||
|
])
|
||||||
|
def test_square(input, expected):
|
||||||
|
assert square(input) == expected
|
||||||
|
```
|
||||||
|
|
||||||
|
### Multiple Parameters
|
||||||
|
|
||||||
|
```python
|
||||||
|
@pytest.mark.parametrize("a,b,expected", [
|
||||||
|
(1, 1, 2),
|
||||||
|
(2, 3, 5),
|
||||||
|
(10, -5, 5),
|
||||||
|
])
|
||||||
|
def test_addition(a, b, expected):
|
||||||
|
assert add(a, b) == expected
|
||||||
|
```
|
||||||
|
|
||||||
|
### Parametrizing Fixtures
|
||||||
|
|
||||||
|
```python
|
||||||
|
@pytest.fixture(params=[1, 2, 3])
|
||||||
|
def number(request):
|
||||||
|
return request.param
|
||||||
|
|
||||||
|
def test_with_different_numbers(number):
|
||||||
|
assert number > 0
|
||||||
|
```
|
||||||
|
|
||||||
|
## Test Markers
|
||||||
|
|
||||||
|
### Built-in Markers
|
||||||
|
|
||||||
|
```python
|
||||||
|
@pytest.mark.skip(reason="Not implemented yet")
|
||||||
|
def test_future_feature():
|
||||||
|
pass
|
||||||
|
|
||||||
|
@pytest.mark.skipif(sys.version_info < (3, 10), reason="Requires Python 3.10+")
|
||||||
|
def test_new_feature():
|
||||||
|
pass
|
||||||
|
|
||||||
|
@pytest.mark.xfail(reason="Known bug")
|
||||||
|
def test_buggy_feature():
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom Markers
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Define in pyproject.toml
|
||||||
|
# markers = ["slow: marks tests as slow"]
|
||||||
|
|
||||||
|
@pytest.mark.slow
|
||||||
|
def test_expensive_operation():
|
||||||
|
# Long-running test
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Run with: pytest -m "not slow"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Exception Testing
|
||||||
|
|
||||||
|
```python
|
||||||
|
def test_raises_value_error():
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
raise ValueError("Invalid value")
|
||||||
|
|
||||||
|
def test_raises_with_message():
|
||||||
|
with pytest.raises(ValueError, match="Invalid"):
|
||||||
|
raise ValueError("Invalid value")
|
||||||
|
|
||||||
|
def test_exception_info():
|
||||||
|
with pytest.raises(ValueError) as exc_info:
|
||||||
|
raise ValueError("Invalid value")
|
||||||
|
assert "Invalid" in str(exc_info.value)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Mocking
|
||||||
|
|
||||||
|
### Using pytest-mock
|
||||||
|
|
||||||
|
```python
|
||||||
|
def test_with_mock(mocker):
|
||||||
|
# Mock a function
|
||||||
|
mock = mocker.patch('module.function')
|
||||||
|
mock.return_value = 42
|
||||||
|
|
||||||
|
result = call_function()
|
||||||
|
assert result == 42
|
||||||
|
mock.assert_called_once()
|
||||||
|
|
||||||
|
def test_mock_method(mocker):
|
||||||
|
# Mock a method
|
||||||
|
mock = mocker.patch.object(MyClass, 'method')
|
||||||
|
mock.return_value = "mocked"
|
||||||
|
|
||||||
|
obj = MyClass()
|
||||||
|
assert obj.method() == "mocked"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using unittest.mock
|
||||||
|
|
||||||
|
```python
|
||||||
|
from unittest.mock import Mock, patch
|
||||||
|
|
||||||
|
def test_with_mock():
|
||||||
|
with patch('module.function') as mock:
|
||||||
|
mock.return_value = 42
|
||||||
|
result = call_function()
|
||||||
|
assert result == 42
|
||||||
|
```
|
||||||
|
|
||||||
|
## Async Tests
|
||||||
|
|
||||||
|
```python
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_async_function():
|
||||||
|
result = await async_operation()
|
||||||
|
assert result == expected
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
async def async_client():
|
||||||
|
client = AsyncClient()
|
||||||
|
yield client
|
||||||
|
await client.close()
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_with_async_fixture(async_client):
|
||||||
|
result = await async_client.get("/endpoint")
|
||||||
|
assert result.status_code == 200
|
||||||
|
```
|
||||||
|
|
||||||
|
## Running Tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# All tests
|
||||||
|
pytest
|
||||||
|
|
||||||
|
# Specific file
|
||||||
|
pytest tests/test_module.py
|
||||||
|
|
||||||
|
# Specific test
|
||||||
|
pytest tests/test_module.py::test_function
|
||||||
|
|
||||||
|
# Specific class
|
||||||
|
pytest tests/test_module.py::TestClass
|
||||||
|
|
||||||
|
# Pattern matching
|
||||||
|
pytest -k "test_user"
|
||||||
|
|
||||||
|
# Markers
|
||||||
|
pytest -m slow # Only slow tests
|
||||||
|
pytest -m "not slow" # Exclude slow tests
|
||||||
|
|
||||||
|
# Last failed
|
||||||
|
pytest --lf
|
||||||
|
|
||||||
|
# Failed first
|
||||||
|
pytest --ff
|
||||||
|
|
||||||
|
# Stop on first failure
|
||||||
|
pytest -x
|
||||||
|
|
||||||
|
# Stop after N failures
|
||||||
|
pytest --maxfail=3
|
||||||
|
|
||||||
|
# Verbose output
|
||||||
|
pytest -v
|
||||||
|
|
||||||
|
# Show print statements
|
||||||
|
pytest -s
|
||||||
|
|
||||||
|
# Show locals in tracebacks
|
||||||
|
pytest --showlocals
|
||||||
|
|
||||||
|
# Parallel execution (requires pytest-xdist)
|
||||||
|
pytest -n auto
|
||||||
|
```
|
||||||
|
|
||||||
|
## conftest.py
|
||||||
|
|
||||||
|
Shared configuration and fixtures:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# tests/conftest.py
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def database():
|
||||||
|
"""Create database for entire test session."""
|
||||||
|
db = create_test_database()
|
||||||
|
yield db
|
||||||
|
db.drop()
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def reset_database(database):
|
||||||
|
"""Reset database before each test."""
|
||||||
|
database.clear()
|
||||||
|
|
||||||
|
def pytest_configure(config):
|
||||||
|
"""Pytest configuration hook."""
|
||||||
|
config.addinivalue_line(
|
||||||
|
"markers", "integration: mark test as integration test"
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
1. **One assertion per test**: Keep tests focused
|
||||||
|
2. **Descriptive names**: Use `test_user_creation_with_valid_data`
|
||||||
|
3. **AAA pattern**: Arrange, Act, Assert
|
||||||
|
4. **Use fixtures**: Avoid duplicating setup code
|
||||||
|
5. **Test edge cases**: Not just happy paths
|
||||||
|
6. **Fast tests**: Keep unit tests under 100ms
|
||||||
|
7. **Independent tests**: Tests should not depend on each other
|
||||||
|
8. **Clear assertions**: Make failures obvious
|
||||||
|
|
||||||
|
## Example Test File
|
||||||
|
|
||||||
|
```python
|
||||||
|
"""Tests for user module."""
|
||||||
|
import pytest
|
||||||
|
from project_name.models import User
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def valid_user_data():
|
||||||
|
"""Valid user data for testing."""
|
||||||
|
return {
|
||||||
|
"name": "Alice",
|
||||||
|
"email": "alice@example.com",
|
||||||
|
"age": 30
|
||||||
|
}
|
||||||
|
|
||||||
|
class TestUser:
|
||||||
|
"""Tests for User model."""
|
||||||
|
|
||||||
|
def test_create_user(self, valid_user_data):
|
||||||
|
"""Test creating a user with valid data."""
|
||||||
|
# Arrange & Act
|
||||||
|
user = User(**valid_user_data)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
assert user.name == "Alice"
|
||||||
|
assert user.email == "alice@example.com"
|
||||||
|
assert user.age == 30
|
||||||
|
|
||||||
|
def test_user_greeting(self, valid_user_data):
|
||||||
|
"""Test user greeting message."""
|
||||||
|
user = User(**valid_user_data)
|
||||||
|
assert user.greet() == "Hello, I'm Alice"
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("age", [-1, 0, 151])
|
||||||
|
def test_invalid_age(self, valid_user_data, age):
|
||||||
|
"""Test that invalid ages raise ValueError."""
|
||||||
|
valid_user_data["age"] = age
|
||||||
|
with pytest.raises(ValueError, match="Invalid age"):
|
||||||
|
User(**valid_user_data)
|
||||||
|
```
|
||||||
Reference in New Issue
Block a user