|
| 1 | +# Modern Python Project Template |
| 2 | + |
| 3 | +This repository provides a modern Python project template, designed for efficient development with `uv`, `ruff`, `mypy`, `pytest`, and Git hooks powered by `pre-commit`. It aims to provide a solid foundation for new Python projects, promoting code quality, consistency, and reduced errors. This template is designed to be used with Linuxbrew for consistent package management across platforms. |
| 4 | + |
| 5 | +## Features |
| 6 | + |
| 7 | +* **`uv` for Dependency Management:** Fast and efficient dependency resolution and virtual environment management. |
| 8 | +* **`ruff` for Linting and Formatting:** Enforces consistent code style, catches errors early, and provides auto-fixing capabilities. |
| 9 | +* **`mypy` for Type Checking:** Adds static typing to your Python code, preventing type-related errors. |
| 10 | +* **`pytest` for Testing:** Provides a robust and flexible testing framework. |
| 11 | +* **`pre-commit` for Git Hooks:** Automates code quality checks before every commit. |
| 12 | +* **`pyproject.toml` for Configuration:** Centralized project configuration using the official `pyproject.toml` standard. |
| 13 | +* **Clean Project Structure:** A well-defined structure with separate `src` and `tests` directories. |
| 14 | +* **Cross-Platform Compatibility:** Designed for use with Linuxbrew to provide a consistent development environment across Windows (via WSL), macOS, and Linux. |
| 15 | + |
| 16 | +## Getting Started |
| 17 | + |
| 18 | +Follow these steps to set up this project template for development: |
| 19 | + |
| 20 | +### 1. Platform Specific Initialization |
| 21 | + |
| 22 | +Before cloning the repository, make sure you have Linuxbrew installed (or Homebrew on macOS). |
| 23 | + |
| 24 | +### Windows/WSL Setup |
| 25 | + |
| 26 | +1. **Install WSL:** Follow the official Microsoft instructions to install WSL and choose a Linux distribution (e.g., Ubuntu). |
| 27 | + |
| 28 | +2. **Open a *Direct* WSL Terminal:** |
| 29 | + * **Do NOT use `wsl` from a Windows terminal (like PowerShell or Command Prompt).** |
| 30 | + * **In VS Code:** Open a *new* integrated terminal by clicking the "+" button on the terminals tab, and select your WSL distribution (e.g., "Ubuntu", "Debian") from the dropdown menu. |
| 31 | + * **From Start Menu:** Alternatively, launch your WSL distribution's terminal directly from the Windows Start Menu (e.g., "Ubuntu", "Debian"). |
| 32 | + |
| 33 | + **All subsequent steps in this section must be executed within this *direct* WSL terminal.** |
| 34 | + |
| 35 | +3. **Install Base Dependencies (Within WSL):** |
| 36 | + ```bash |
| 37 | + sudo apt update && sudo apt install -y build-essential curl file git |
| 38 | + ``` |
| 39 | +4. **Install Linuxbrew:** |
| 40 | + ```bash |
| 41 | + /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" |
| 42 | + ``` |
| 43 | +5. **Add Linuxbrew to `PATH`:** |
| 44 | + Add the following lines to your `~/.bashrc` or `~/.zshrc` (depending on your shell): |
| 45 | + ```bash |
| 46 | + export PATH="/home/linuxbrew/.linuxbrew/bin:$PATH" |
| 47 | + export MANPATH="/home/linuxbrew/.linuxbrew/share/man:$MANPATH" |
| 48 | + export INFOPATH="/home/linuxbrew/.linuxbrew/share/info:$INFOPATH" |
| 49 | + ``` |
| 50 | + Then run `source ~/.bashrc` or `source ~/.zshrc`. |
| 51 | +6. **Install Python via Linuxbrew:** `brew install python` |
| 52 | + |
| 53 | +#### macOS |
| 54 | + |
| 55 | +1. **Install Homebrew:** If you don't have it already, install Homebrew by running the following command in the terminal: |
| 56 | + ```bash |
| 57 | + /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" |
| 58 | + ``` |
| 59 | +2. **Install Python via Homebrew:** `brew install python` |
| 60 | +
|
| 61 | +#### Linux |
| 62 | +
|
| 63 | +1. **Install Linuxbrew:** Follow the instructions on the Linuxbrew homepage: [https://brew.sh/](https://brew.sh/) |
| 64 | +2. **Install Python via Linuxbrew:** `brew install python` |
| 65 | +
|
| 66 | +### 2. Project Setup |
| 67 | +
|
| 68 | +Once Linuxbrew (or Homebrew on macOS) and Python are installed, you can continue with the following steps: |
| 69 | +
|
| 70 | +1. **Clone the Repository:** |
| 71 | + ```bash |
| 72 | + git clone <repository_url> |
| 73 | + cd <project_name> |
| 74 | + ``` |
| 75 | +
|
| 76 | +2. **Create and Activate Virtual Environment:** |
| 77 | + ```bash |
| 78 | + uv venv |
| 79 | + ``` |
| 80 | + This command creates a virtual environment, if one does not exist already. |
| 81 | +
|
| 82 | +3. **Install Dependencies:** |
| 83 | + ```bash |
| 84 | + uv sync |
| 85 | + ``` |
| 86 | + This command installs all dependencies from your lock file (`uv.lock`), including development dependencies. |
| 87 | +
|
| 88 | +4. **Install Git Hooks:** |
| 89 | + ```bash |
| 90 | + pre-commit install |
| 91 | + ``` |
| 92 | + This command installs the pre-commit hooks that ensure formatting and linting happen automatically before each commit. |
| 93 | +
|
| 94 | +5. **Create a lock file**: |
| 95 | + ```bash |
| 96 | + uv lock |
| 97 | + ``` |
| 98 | + This command creates a lock file which locks all dependencies in the project to a known version. |
| 99 | +
|
| 100 | +## Development |
| 101 | +
|
| 102 | +* **Code in `src`:** Start adding your Python code inside the `src/my_package` directory. |
| 103 | +* **Create tests:** Create your tests in the `tests` directory, and import your code using the name of the package, in this case `my_package`. |
| 104 | +* **Run tests:** Use `pytest` in the root folder to run tests. |
| 105 | + ```bash |
| 106 | + pytest |
| 107 | + ``` |
| 108 | +
|
| 109 | +## Automatic Linting and Formatting |
| 110 | +
|
| 111 | +* `ruff` automatically fixes formatting and linting errors when you save changes. |
| 112 | +
|
| 113 | +## Type Checking |
| 114 | +
|
| 115 | +* `mypy` is run as part of the pre-commit hooks and will catch any type issues. |
| 116 | +
|
| 117 | +## Configuration |
| 118 | +
|
| 119 | +### Core Configuration |
| 120 | +
|
| 121 | +The project's core configuration is located in the `pyproject.toml` file. |
| 122 | + |
| 123 | +* **Project Metadata:** |
| 124 | + * Basic project information like name, version, description, authors, etc. |
| 125 | + * Python version specified in `requires-python` |
| 126 | +* **Dependencies:** |
| 127 | + * Project's dependencies listed in the `[project.dependencies` array. |
| 128 | + * Development dependencies listed in the `[project.optional-dependencies.dev]` array, or you can add additional groups. |
| 129 | +
|
| 130 | +#### `[tool.uv]` |
| 131 | +
|
| 132 | +* `default-groups = ["dev"]`: Sets dev dependencies to be installed by default. |
| 133 | +
|
| 134 | +#### `[tool.ruff]` |
| 135 | +
|
| 136 | +* `line-length = 100`: Sets the maximum line length. |
| 137 | +* `select`: List of rules enabled from `ruff` and different linters. |
| 138 | +* `ignore`: List of rules ignored. |
| 139 | +* `fix = true`: Automatically fix linting errors. |
| 140 | +* `per-file-ignores`: Configure specific files or patterns for ignoring certain rules. |
| 141 | +* `quote-style = "single"`: Sets style to use single quotes. |
| 142 | +
|
| 143 | +#### `[tool.mypy]` |
| 144 | +
|
| 145 | +* `mypy_path = "src"`: The location of your packages for mypy type checking. |
| 146 | +* `python_version = "3.10"`: The Python version being used. |
| 147 | +* `strict = true`: Enables strict type checking options. |
| 148 | +* `plugins = ["pytest_mypy_plugins"]`: Makes pytest plugin available for mypy. |
| 149 | +
|
| 150 | +### Git Hooks Configuration (`.pre-commit-config.yaml`) |
| 151 | +
|
| 152 | +This file configures `pre-commit`, a tool that runs checks before each commit. It includes: |
| 153 | +
|
| 154 | +* **General-Purpose Hooks (`pre-commit-hooks`):** Trailing whitespace removal, end-of-file newline enforcement, YAML validation, etc. |
| 155 | +* **`ruff` Hook (`astral-sh/ruff-pre-commit`):** Automatically fixes `Ruff` errors (`ruff --fix`). |
| 156 | +* **`black` Hook (`psf/black`):** Automatically reformats your code with the black formatter |
| 157 | +* **`mypy` Hook (`pre-commit/mirrors-mypy`):** Checks code for type errors with `mypy` |
| 158 | +
|
| 159 | +### VS Code Configuration (`.vscode/settings.json`) |
| 160 | +
|
| 161 | +This file configures the VS Code editor. |
| 162 | +
|
| 163 | +* **Type Checking:** `python.analysis.typeCheckingMode`: Enables Python's type checker. |
| 164 | +* **Linting:** `python.linting.ruffEnabled`: Enables `ruff` as the linter. |
| 165 | +* **Formatting:** `python.formatting.provider`: Disables default formatter, since pre-commit is being used. |
| 166 | +* **Ruff Settings:** Settings for Ruff language server (e.g., `importStrategy`, `organizeImports`, `fixAll`, `showSyntaxErrors`). |
| 167 | +* `ruff.configurationPreference`: prioritize file based configs. |
| 168 | + |
| 169 | +## Common and Useful Configurations (Not in Base Config) |
| 170 | + |
| 171 | +Here are some additional configurations that are often useful and are commonly used or at least helpful if applicable. |
| 172 | + |
| 173 | +### 1. Custom Ruff Rules and Ignores |
| 174 | + |
| 175 | +* **`[tool.ruff.lint.per-file-ignores]`:** Use this section to ignore rules for specific files. For example: |
| 176 | + |
| 177 | + ```toml |
| 178 | + [tool.ruff.lint.per-file-ignores] |
| 179 | + "tests/*" = ["S101", "ANN"] # Allow asserts and skip annotations in tests. |
| 180 | + "src/my_package/legacy.py" = ["B001", "B002"] # Ignore specific bugbear rules for legacy code |
| 181 | + ``` |
| 182 | + |
| 183 | +* **`extend-select` and `extend-ignore`:** Add additional rules or ignore rules for the project. |
| 184 | + |
| 185 | + ```toml |
| 186 | + [tool.ruff.lint |
| 187 | + extend-select = ["PT"] # adds rules from flake8-pytest |
| 188 | + extend-ignore = ["ANN101", "ANN401"] # extends the ignore rules |
| 189 | + ``` |
| 190 | +
|
| 191 | +* **Rule Specific Options:** Certain rules can be further configured, like for example isort options. |
| 192 | +
|
| 193 | + ```toml |
| 194 | + [tool.ruff.lint.isort] |
| 195 | + force-wrap-aliases = true |
| 196 | + ``` |
| 197 | +
|
| 198 | +### 2. Custom Mypy Options |
| 199 | +
|
| 200 | +* **`ignore_missing_imports`:** If you are having issues with dependencies and type checking. Set to `true`. |
| 201 | +
|
| 202 | + ```toml |
| 203 | + [tool.mypy] |
| 204 | + ignore_missing_imports = true |
| 205 | + ``` |
| 206 | +
|
| 207 | +* **`disallow_untyped_defs`:** If you want to require type annotations in function definitions, use `disallow_untyped_defs = true`. |
| 208 | +
|
| 209 | + ```toml |
| 210 | + [tool.mypy] |
| 211 | + disallow_untyped_defs = true |
| 212 | + ``` |
| 213 | +
|
| 214 | +* **`disallow_any_explicit`**: If you want to disallow explicit use of `Any` types in the code. |
| 215 | +
|
| 216 | + ```toml |
| 217 | + [tool.mypy] |
| 218 | + disallow_any_explicit = true |
| 219 | + ``` |
| 220 | +
|
| 221 | +* **`warn_unreachable`:** If you want to enable warning for unreachable code |
| 222 | +
|
| 223 | + ```toml |
| 224 | + [tool.mypy] |
| 225 | + warn_unreachable = true |
| 226 | + ``` |
| 227 | +
|
| 228 | + See [Mypy configuration docs](https://mypy.readthedocs.io/en/stable/config_file.html) for a complete listing of available settings. |
| 229 | +
|
| 230 | +### 3. `uv` Configuration |
| 231 | +
|
| 232 | +* **`constraint-dependencies`:** If you want to pin a transitive dependency to a specific version use `constraint-dependencies`. |
| 233 | +
|
| 234 | + ```toml |
| 235 | + [tool.uv] |
| 236 | + constraint-dependencies = ["cryptography<42.0.0"] |
| 237 | + ``` |
| 238 | +
|
| 239 | +* **`override-dependencies`:** Forces a dependency to use a specific version |
| 240 | +
|
| 241 | + ```toml |
| 242 | + [tool.uv] |
| 243 | + override-dependencies = ["cryptography==42.0.0"] |
| 244 | + ``` |
| 245 | +
|
| 246 | +* **`index`:** You can specify additional package indexes to get dependencies from |
| 247 | +
|
| 248 | + ```toml |
| 249 | + [[tool.uv.index]] |
| 250 | + name = "pytorch" |
| 251 | + url = "https://download.pytorch.org/whl/cu121" |
| 252 | + explicit = true |
| 253 | +
|
| 254 | + [tool.uv.sources] |
| 255 | + torch = { index = "pytorch" } |
| 256 | + ``` |
| 257 | +
|
| 258 | +### 4. Test Configuration |
| 259 | +
|
| 260 | +* `pytest.ini`: Create a `pytest.ini` in your root folder to configure options for `pytest`. |
| 261 | +
|
| 262 | + ```ini |
| 263 | + [pytest] |
| 264 | + testpaths = tests |
| 265 | + addopts = |
| 266 | + --cov=src/my_package |
| 267 | + --cov-report term-missing |
| 268 | + -vv |
| 269 | + ``` |
| 270 | +
|
| 271 | + This will make sure that pytest runs against all tests in the tests folder |
| 272 | +
|
| 273 | + It will also add coverage info for `src/my_package` |
| 274 | +
|
| 275 | + It will also run in verbose mode |
| 276 | +
|
| 277 | +### 5. `pre-commit` Config |
| 278 | +
|
| 279 | +You can add additional hooks from other repositories. |
| 280 | +
|
| 281 | +You can configure existing hooks as needed. For example if you want to exclude files from ruff you can use: |
| 282 | +
|
| 283 | +```yaml |
| 284 | +- id: ruff |
| 285 | + args: ["--fix", "--exclude", "path/to/exclude"] |
| 286 | +``` |
| 287 | +
|
| 288 | +## Contributing |
| 289 | +
|
| 290 | +If you would like to add features to this template, or suggest improvements, feel free to create a pull request. |
0 commit comments