832 lines
22 KiB
Markdown
832 lines
22 KiB
Markdown
---
|
|
title: User Project Conventions
|
|
date: 2025-11-17
|
|
source: Extracted from user's production projects
|
|
projects_analyzed:
|
|
- pre-commit-pep723-linter-wrapper (PyPI/GitHub)
|
|
- python_picotool (GitLab)
|
|
- usb_powertools (GitLab)
|
|
- picod (GitLab)
|
|
- i2c_analyzer (GitLab)
|
|
---
|
|
|
|
# User Project Conventions
|
|
|
|
Conventions extracted from actual production projects. The model MUST follow these patterns when creating new Python projects.
|
|
|
|
## Asset Files Available
|
|
|
|
The following template files are available in the skill's `assets/` directory for use in new projects:
|
|
|
|
| File | Purpose | Usage |
|
|
| ------------------------- | ---------------------------------------------------- | ----------------------------------------------------- |
|
|
| `version.py` | Dual-mode version management (hatch-vcs + fallback) | Copy to `packages/{package_name}/version.py` |
|
|
| `hatch_build.py` | Build hook for binary/asset handling | Copy to `scripts/hatch_build.py` |
|
|
| `.markdownlint.json` | Markdown linting configuration (most rules disabled) | Copy to project root |
|
|
| `.pre-commit-config.yaml` | Standard pre-commit hooks configuration | Copy to project root, run `uv run pre-commit install` |
|
|
| `.editorconfig` | Editor formatting settings | Copy to project root |
|
|
|
|
The model MUST copy these files when creating new Python projects to ensure consistency with established conventions documented below.
|
|
|
|
## 1. Version Management
|
|
|
|
### Pattern: Dual-mode version.py (STANDARD - 5/5 projects)
|
|
|
|
**Location**: `packages/{package_name}/version.py`
|
|
|
|
**Pattern**: Hatch-VCS with importlib.metadata fallback
|
|
|
|
**Implementation**:
|
|
|
|
```python
|
|
"""Compute the version number and store it in the `__version__` variable.
|
|
|
|
Based on <https://github.com/maresb/hatch-vcs-footgun-example>.
|
|
"""
|
|
|
|
# /// script
|
|
# List dependencies for linting only
|
|
# dependencies = [
|
|
# "hatchling>=1.14.0",
|
|
# ]
|
|
# ///
|
|
import os
|
|
|
|
|
|
def _get_hatch_version() -> str | None:
|
|
"""Compute the most up-to-date version number in a development environment.
|
|
|
|
Returns `None` if Hatchling is not installed, e.g. in a production environment.
|
|
|
|
For more details, see <https://github.com/maresb/hatch-vcs-footgun-example/>.
|
|
"""
|
|
try:
|
|
from hatchling.metadata.core import ProjectMetadata
|
|
from hatchling.plugin.manager import PluginManager
|
|
from hatchling.utils.fs import locate_file
|
|
except ImportError:
|
|
# Hatchling is not installed, so probably we are not in
|
|
# a development environment.
|
|
return None
|
|
|
|
pyproject_toml = locate_file(__file__, "pyproject.toml")
|
|
if pyproject_toml is None:
|
|
raise RuntimeError("pyproject.toml not found although hatchling is installed")
|
|
root = os.path.dirname(pyproject_toml)
|
|
metadata = ProjectMetadata(root=root, plugin_manager=PluginManager())
|
|
# Version can be either statically set in pyproject.toml or computed dynamically:
|
|
return str(metadata.core.version or metadata.hatch.version.cached)
|
|
|
|
|
|
def _get_importlib_metadata_version() -> str:
|
|
"""Compute the version number using importlib.metadata.
|
|
|
|
This is the official Pythonic way to get the version number of an installed
|
|
package. However, it is only updated when a package is installed. Thus, if a
|
|
package is installed in editable mode, and a different version is checked out,
|
|
then the version number will not be updated.
|
|
"""
|
|
from importlib.metadata import version
|
|
|
|
__version__ = version(__package__ or __name__)
|
|
return __version__
|
|
|
|
|
|
__version__ = _get_hatch_version() or _get_importlib_metadata_version()
|
|
```
|
|
|
|
**pyproject.toml Configuration** (STANDARD - 5/5 projects):
|
|
|
|
```toml
|
|
[project]
|
|
dynamic = ["version"]
|
|
|
|
[tool.hatch.version]
|
|
source = "vcs"
|
|
|
|
[build-system]
|
|
requires = ["hatchling", "hatch-vcs"]
|
|
build-backend = "hatchling.build"
|
|
```
|
|
|
|
\***\*init**.py Export Pattern\*\* (STANDARD - 5/5 projects):
|
|
|
|
```python
|
|
from .version import __version__
|
|
|
|
__all__ = ["__version__"] # Plus other exports
|
|
```
|
|
|
|
## 2. Package Structure
|
|
|
|
### Pattern: src-layout with packages/ directory (STANDARD - 5/5 projects)
|
|
|
|
**Directory Structure**:
|
|
|
|
```text
|
|
project_root/
|
|
├── packages/
|
|
│ └── {package_name}/
|
|
│ ├── __init__.py # Exports public API + __version__
|
|
│ ├── version.py # Version management
|
|
│ ├── {modules}.py
|
|
│ └── tests/ # Co-located tests
|
|
├── scripts/
|
|
│ └── hatch_build.py # Custom build hook (if needed)
|
|
├── pyproject.toml
|
|
└── README.md
|
|
```
|
|
|
|
**pyproject.toml Package Mapping** (STANDARD - 5/5 projects):
|
|
|
|
```toml
|
|
[tool.hatch.build.targets.wheel]
|
|
packages = ["packages/{package_name}"]
|
|
|
|
[tool.hatch.build.targets.wheel.sources]
|
|
"packages/{package_name}" = "{package_name}"
|
|
```
|
|
|
|
### Pattern: **init**.py exports with **all** (STANDARD - 5/5 projects)
|
|
|
|
The model must export public API + `__version__` in `__init__.py` with explicit `__all__` list.
|
|
|
|
**Minimal Example** (usb_powertools):
|
|
|
|
```python
|
|
"""Package docstring."""
|
|
|
|
from .version import __version__
|
|
|
|
__all__ = ["__version__"]
|
|
```
|
|
|
|
**Full API Example** (pep723_loader):
|
|
|
|
```python
|
|
"""Package docstring."""
|
|
|
|
from .pep723_checker import Pep723Checker
|
|
from .version import __version__
|
|
|
|
__all__ = ["Pep723Checker", "__version__"]
|
|
```
|
|
|
|
**Evidence**: All 5 projects use this pattern consistently.
|
|
|
|
## 3. Build Configuration
|
|
|
|
### Pattern: Custom hatch_build.py Hook (STANDARD - 3/5 projects with binaries)
|
|
|
|
**Location**: `scripts/hatch_build.py`
|
|
|
|
**Purpose**: Execute binary build scripts (`build-binaries.sh` or `build-binaries.py`) before packaging.
|
|
|
|
**Standard Implementation** (usb_powertools, picod, i2c_analyzer identical):
|
|
|
|
```python
|
|
"""Custom hatchling build hook for binary compilation.
|
|
|
|
This hook runs before the build process to compile platform-specific binaries
|
|
if build scripts are present in the project.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import shutil
|
|
import subprocess
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
from hatchling.builders.config import BuilderConfig
|
|
from hatchling.builders.hooks.plugin.interface import BuildHookInterface
|
|
|
|
|
|
class BinaryBuildHook(BuildHookInterface[BuilderConfig]):
|
|
"""Build hook that runs binary compilation scripts before packaging.
|
|
|
|
This hook checks for the following scripts in order:
|
|
1. scripts/build-binaries.sh
|
|
2. scripts/build-binaries.py
|
|
|
|
If either script exists, it is executed before the build process.
|
|
If neither exists, the hook silently continues without error.
|
|
"""
|
|
|
|
PLUGIN_NAME = "binary-build"
|
|
|
|
def initialize(self, version: str, build_data: dict[str, Any]) -> None:
|
|
"""Run binary build scripts if they exist."""
|
|
shell_script = Path(self.root) / "scripts" / "build-binaries.sh"
|
|
if shell_script.exists() and shell_script.is_file():
|
|
self._run_shell_script(shell_script)
|
|
return
|
|
|
|
python_script = Path(self.root) / "scripts" / "build-binaries.py"
|
|
if python_script.exists() and python_script.is_file():
|
|
self._run_python_script(python_script)
|
|
return
|
|
|
|
self.app.display_info("No binary build scripts found, skipping binary compilation")
|
|
|
|
def _run_shell_script(self, script_path: Path) -> None:
|
|
"""Execute a shell script for binary building."""
|
|
self.app.display_info(f"Running binary build script: {script_path}")
|
|
|
|
if not (bash := shutil.which("bash")):
|
|
raise RuntimeError("bash not found - cannot execute shell script")
|
|
|
|
try:
|
|
result = subprocess.run([bash, str(script_path)], cwd=self.root, capture_output=True, text=True, check=True)
|
|
if result.stdout:
|
|
self.app.display_info(result.stdout)
|
|
if result.stderr:
|
|
self.app.display_warning(result.stderr)
|
|
except subprocess.CalledProcessError as e:
|
|
self.app.display_error(f"Binary build script failed with exit code {e.returncode}")
|
|
if e.stdout:
|
|
self.app.display_info(f"stdout: {e.stdout}")
|
|
if e.stderr:
|
|
self.app.display_error(f"stderr: {e.stderr}")
|
|
raise
|
|
|
|
def _run_python_script(self, script_path: Path) -> None:
|
|
"""Execute a Python script for binary building.
|
|
|
|
Executes the script directly using its shebang, which honors PEP 723
|
|
inline metadata for dependency management via uv.
|
|
"""
|
|
self.app.display_info(f"Running binary build script: {script_path}")
|
|
|
|
try:
|
|
result = subprocess.run([script_path, "--clean"], cwd=self.root, capture_output=True, text=True, check=True)
|
|
if result.stdout:
|
|
self.app.display_info(result.stdout)
|
|
if result.stderr:
|
|
self.app.display_warning(result.stderr)
|
|
except subprocess.CalledProcessError as e:
|
|
self.app.display_error(f"Binary build script failed with exit code {e.returncode}")
|
|
if e.stdout:
|
|
self.app.display_info(f"stdout: {e.stdout}")
|
|
if e.stderr:
|
|
self.app.display_error(f"stderr: {e.stderr}")
|
|
raise
|
|
```
|
|
|
|
**pyproject.toml Configuration**:
|
|
|
|
```toml
|
|
[tool.hatch.build.targets.sdist.hooks.custom]
|
|
path = "scripts/hatch_build.py"
|
|
|
|
[tool.hatch.build]
|
|
artifacts = ["builds/*/binary_name"] # If binaries included
|
|
```
|
|
|
|
## 4. Pre-commit Configuration
|
|
|
|
### Standard Hook Set (STANDARD - 5/5 projects)
|
|
|
|
**File**: `.pre-commit-config.yaml`
|
|
|
|
**Core Hooks** (appear in all projects):
|
|
|
|
```yaml
|
|
repos:
|
|
- repo: https://github.com/mxr/sync-pre-commit-deps
|
|
rev: v0.0.3
|
|
hooks:
|
|
- id: sync-pre-commit-deps
|
|
|
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
rev: v6.0.0
|
|
hooks:
|
|
- id: trailing-whitespace
|
|
exclude: \.lock$
|
|
- id: end-of-file-fixer
|
|
exclude: \.lock$
|
|
- id: check-yaml
|
|
- id: check-json
|
|
- id: check-toml
|
|
- id: check-added-large-files
|
|
args: ["--maxkb=10000"] # 10MB limit
|
|
- id: check-case-conflict
|
|
- id: check-merge-conflict
|
|
- id: check-symlinks
|
|
- id: mixed-line-ending
|
|
args: ["--fix=lf"]
|
|
- id: check-executables-have-shebangs
|
|
- id: check-shebang-scripts-are-executable
|
|
|
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
rev: v0.13.3+
|
|
hooks:
|
|
- id: ruff
|
|
name: Lint Python with ruff
|
|
args: [--fix, --exit-non-zero-on-fix]
|
|
- id: ruff-format
|
|
name: Format Python with ruff
|
|
|
|
- repo: https://github.com/pre-commit/mirrors-prettier
|
|
rev: v4.0.0-alpha.8
|
|
hooks:
|
|
- id: prettier
|
|
name: Format YAML, JSON, and Markdown files
|
|
types_or: [yaml, json, markdown]
|
|
exclude: \.lock$
|
|
|
|
- repo: https://github.com/pecigonzalo/pre-commit-shfmt
|
|
rev: v2.2.0
|
|
hooks:
|
|
- id: shell-fmt-go
|
|
args: ["--apply-ignore", -w, -i, "4", -ci]
|
|
|
|
- repo: https://github.com/shellcheck-py/shellcheck-py
|
|
rev: v0.11.0.1
|
|
hooks:
|
|
- id: shellcheck
|
|
|
|
default_language_version:
|
|
python: python3
|
|
|
|
exclude: |
|
|
(?x)^(
|
|
\.git/|
|
|
\.venv/|
|
|
__pycache__/|
|
|
\.mypy_cache/|
|
|
\.cache/|
|
|
\.pytest_cache/|
|
|
\.lock$|
|
|
typings/
|
|
)
|
|
```
|
|
|
|
### Pattern: pep723-loader for Type Checking (STANDARD - 3/5 projects)
|
|
|
|
Projects using `pep723-loader` wrapper for mypy/basedpyright:
|
|
|
|
```yaml
|
|
- repo: local
|
|
hooks:
|
|
- id: mypy
|
|
name: mypy
|
|
entry: uv run -q --no-sync --with pep723-loader --with mypy pep723-loader mypy
|
|
language: system
|
|
types: [python]
|
|
pass_filenames: true
|
|
|
|
- id: pyright
|
|
name: basedpyright
|
|
entry: uv run -q --no-sync --with pep723-loader --with basedpyright pep723-loader basedpyright
|
|
language: system
|
|
types: [python]
|
|
pass_filenames: true
|
|
require_serial: true
|
|
```
|
|
|
|
### Pattern: Markdown Linting (STANDARD - 4/5 projects)
|
|
|
|
```yaml
|
|
- repo: https://github.com/DavidAnson/markdownlint-cli2
|
|
rev: v0.18.1
|
|
hooks:
|
|
- id: markdownlint-cli2
|
|
language_version: "latest"
|
|
args: ["--fix"]
|
|
```
|
|
|
|
**Evidence**: pre-commit-pep723-linter-wrapper, usb_powertools, picod all use this pattern.
|
|
|
|
## 5. Ruff Configuration
|
|
|
|
### Standard Configuration (STANDARD - 5/5 projects)
|
|
|
|
**pyproject.toml Section**:
|
|
|
|
```toml
|
|
[tool.ruff]
|
|
target-version = "py311"
|
|
line-length = 120
|
|
fix = true
|
|
preview = true # Optional, 3/5 projects use
|
|
|
|
[tool.ruff.format]
|
|
docstring-code-format = true
|
|
quote-style = "double"
|
|
line-ending = "lf"
|
|
skip-magic-trailing-comma = true
|
|
preview = true
|
|
|
|
[tool.ruff.lint]
|
|
extend-select = [
|
|
"E", # pycodestyle errors
|
|
"W", # pycodestyle warnings
|
|
"F", # pyflakes
|
|
"I", # isort
|
|
"UP", # pyupgrade
|
|
"YTT", # flake8-2020
|
|
"S", # flake8-bandit
|
|
"B", # flake8-bugbear
|
|
"A", # flake8-builtins
|
|
"C4", # flake8-comprehensions
|
|
"T10", # flake8-debugger
|
|
"SIM", # flake8-simplify
|
|
"C90", # mccabe
|
|
"PGH", # pygrep-hooks
|
|
"RUF", # ruff-specific
|
|
"TRY", # tryceratops
|
|
"DOC", # pydocstyle docstrings (4/5 projects)
|
|
"D", # pydocstyle (4/5 projects)
|
|
]
|
|
|
|
ignore = [
|
|
"COM812", # Missing trailing comma
|
|
"COM819", # Missing trailing comma
|
|
"D107", # Missing docstring in __init__
|
|
"D415", # First line should end with a period
|
|
"E111", # Indentation is not a multiple of four
|
|
"E117", # Over-indented for visual indent
|
|
"E203", # whitespace before ':'
|
|
"E402", # Module level import not at top of file
|
|
"E501", # Line length exceeds maximum limit
|
|
"ISC001", # isort configuration is missing
|
|
"ISC002", # isort configuration is missing
|
|
"Q000", # Remove bad quotes
|
|
"Q001", # Remove bad quotes
|
|
"Q002", # Remove bad quotes
|
|
"Q003", # Remove bad quotes
|
|
"TRY003", # Exception message should not be too long
|
|
"S404", # module is possibly insecure
|
|
"S603", # subprocess-without-shell-equals-true
|
|
"S606", # start-process-with-no-shell
|
|
"DOC201", # Missing return section in docstring
|
|
"DOC501", # Missing raises section
|
|
"DOC502", # Missing raises section
|
|
"T201", # Allow print statements (4/5 projects)
|
|
]
|
|
|
|
unfixable = ["F401", "S404", "S603", "S606", "DOC501"]
|
|
|
|
[tool.ruff.lint.pycodestyle]
|
|
max-line-length = 120
|
|
|
|
[tool.ruff.lint.isort]
|
|
combine-as-imports = true
|
|
split-on-trailing-comma = false
|
|
force-single-line = false
|
|
force-wrap-aliases = false
|
|
|
|
[tool.ruff.lint.flake8-quotes]
|
|
docstring-quotes = "double"
|
|
|
|
[tool.ruff.lint.pydocstyle]
|
|
convention = "google"
|
|
|
|
[tool.ruff.lint.mccabe]
|
|
max-complexity = 10
|
|
|
|
[tool.ruff.lint.per-file-ignores]
|
|
"**/tests/*" = ["S101", "S603", "S607", "D102", "D200", "D100"]
|
|
"**/test_*.py" = ["S101", "S603", "S607", "D102", "D200", "D100"]
|
|
```
|
|
|
|
**Evidence**: All 5 projects use this exact configuration with minor variations.
|
|
|
|
## 6. Mypy Configuration
|
|
|
|
### Standard Configuration (STANDARD - 5/5 projects)
|
|
|
|
```toml
|
|
[tool.mypy]
|
|
python_version = "3.11"
|
|
strict = true
|
|
strict_equality = true
|
|
extra_checks = true
|
|
warn_unused_configs = true
|
|
warn_redundant_casts = true
|
|
warn_unused_ignores = true
|
|
ignore_missing_imports = true
|
|
show_error_codes = true
|
|
pretty = true
|
|
disable_error_code = ["call-arg"]
|
|
```
|
|
|
|
**Per-module overrides pattern**:
|
|
|
|
```toml
|
|
[[tool.mypy.overrides]]
|
|
module = "tests.*"
|
|
disable_error_code = ["misc"]
|
|
```
|
|
|
|
## 7. Basedpyright Configuration
|
|
|
|
### Standard Configuration (STANDARD - 5/5 projects)
|
|
|
|
```toml
|
|
[tool.basedpyright]
|
|
pythonVersion = "3.11"
|
|
typeCheckingMode = "standard"
|
|
reportMissingImports = false
|
|
reportMissingTypeStubs = false
|
|
reportUnnecessaryTypeIgnoreComment = "error"
|
|
reportPrivateImportUsage = false
|
|
include = ["packages"]
|
|
extraPaths = ["packages", "scripts", "tests", "."]
|
|
exclude = ["**/node_modules", "**/__pycache__", ".*", "__*", "**/typings"]
|
|
ignore = ["**/typings"]
|
|
venvPath = "."
|
|
venv = ".venv"
|
|
```
|
|
|
|
**Evidence**: All 5 projects use this configuration.
|
|
|
|
## 8. Pytest Configuration
|
|
|
|
### Standard Configuration (STANDARD - 5/5 projects)
|
|
|
|
```toml
|
|
[tool.pytest.ini_options]
|
|
addopts = [
|
|
"--cov=packages/{package_name}",
|
|
"--cov-report=term-missing",
|
|
"-v",
|
|
]
|
|
testpaths = ["packages/{package_name}/tests"]
|
|
python_files = ["test_*.py"]
|
|
python_classes = ["Test*"]
|
|
python_functions = ["test_*"]
|
|
pythonpath = [".", "packages/"]
|
|
markers = [
|
|
"hardware: tests that require USB hardware",
|
|
"slow: tests that take significant time to run",
|
|
"integration: integration tests",
|
|
]
|
|
|
|
[tool.coverage.run]
|
|
omit = ["*/tests/*"]
|
|
|
|
[tool.coverage.report]
|
|
show_missing = true
|
|
fail_under = 70
|
|
```
|
|
|
|
**Evidence**: All projects follow this pattern with minor marker variations.
|
|
|
|
## 9. Formatting Configuration Files
|
|
|
|
### .markdownlint.json (STANDARD - 5/5 projects)
|
|
|
|
**All projects use identical configuration**:
|
|
|
|
```json
|
|
{
|
|
"MD003": false,
|
|
"MD007": { "indent": 2 },
|
|
"MD001": false,
|
|
"MD022": false,
|
|
"MD024": false,
|
|
"MD013": false,
|
|
"MD036": false,
|
|
"MD025": false,
|
|
"MD031": false,
|
|
"MD041": false,
|
|
"MD029": false,
|
|
"MD033": false,
|
|
"MD046": false,
|
|
"blanks-around-fences": false,
|
|
"blanks-around-headings": false,
|
|
"blanks-around-lists": false,
|
|
"code-fence-style": false,
|
|
"emphasis-style": false,
|
|
"heading-start-left": false,
|
|
"heading-style": false,
|
|
"hr-style": false,
|
|
"line-length": false,
|
|
"list-indent": false,
|
|
"list-marker-space": false,
|
|
"no-blanks-blockquote": false,
|
|
"no-hard-tabs": false,
|
|
"no-missing-space-atx": false,
|
|
"no-missing-space-closed-atx": false,
|
|
"no-multiple-blanks": false,
|
|
"no-multiple-space-atx": false,
|
|
"no-multiple-space-blockquote": false,
|
|
"no-multiple-space-closed-atx": false,
|
|
"no-trailing-spaces": false,
|
|
"ol-prefix": false,
|
|
"strong-style": false,
|
|
"ul-indent": false
|
|
}
|
|
```
|
|
|
|
**Evidence**: Identical across all 5 projects.
|
|
|
|
### .editorconfig (COMMON - 2/5 projects have it)
|
|
|
|
**Standard Pattern** (python_picotool, picod):
|
|
|
|
```ini
|
|
# EditorConfig: https://editorconfig.org/
|
|
|
|
root = true
|
|
|
|
[*]
|
|
charset = utf-8
|
|
end_of_line = lf
|
|
insert_final_newline = true
|
|
trim_trailing_whitespace = true
|
|
max_line_length = 120
|
|
|
|
[*.md]
|
|
indent_style = space
|
|
indent_size = 4
|
|
trim_trailing_whitespace = false
|
|
|
|
[*.py]
|
|
indent_style = space
|
|
indent_size = 4
|
|
|
|
[*.{yml,yaml}]
|
|
indent_style = space
|
|
indent_size = 2
|
|
|
|
[*.sh]
|
|
indent_style = space
|
|
indent_size = 4
|
|
|
|
[*.toml]
|
|
indent_style = space
|
|
indent_size = 2
|
|
|
|
[*.json]
|
|
indent_style = space
|
|
indent_size = 2
|
|
|
|
[COMMIT_EDITMSG]
|
|
max_line_length = 72
|
|
```
|
|
|
|
**Evidence**:
|
|
|
|
## 10. Semantic Release Configuration
|
|
|
|
### Standard Configuration (STANDARD - 5/5 projects)
|
|
|
|
```toml
|
|
[tool.semantic_release]
|
|
version_toml = []
|
|
major_on_zero = true
|
|
allow_zero_version = true
|
|
tag_format = "v{version}"
|
|
build_command = "uv build"
|
|
|
|
[tool.semantic_release.branches.main]
|
|
match = "(main|master)"
|
|
prerelease = false
|
|
|
|
[tool.semantic_release.commit_parser_options]
|
|
allowed_tags = [
|
|
"build",
|
|
"chore",
|
|
"ci",
|
|
"docs",
|
|
"feat",
|
|
"fix",
|
|
"perf",
|
|
"style",
|
|
"refactor",
|
|
"test",
|
|
]
|
|
minor_tags = ["feat"]
|
|
patch_tags = ["fix", "perf", "refactor"]
|
|
```
|
|
|
|
**Evidence**: All 5 projects use this configuration identically.
|
|
|
|
## 11. Dependency Groups
|
|
|
|
### Standard dev Dependencies (STANDARD - 5/5 projects)
|
|
|
|
```toml
|
|
[dependency-groups]
|
|
dev = [
|
|
"basedpyright>=1.21.1",
|
|
"hatch-vcs>=0.5.0",
|
|
"hatchling>=1.14.0",
|
|
"mypy>=1.18.2",
|
|
"pre-commit>=4.3.0",
|
|
"pytest>=8.4.2",
|
|
"pytest-asyncio>=1.2.0",
|
|
"pytest-cov>=6.0.0",
|
|
"pytest-mock>=3.14.0",
|
|
"ruff>=0.9.4",
|
|
"python-semantic-release>=10.4.1",
|
|
"generate-changelog>=0.16.0",
|
|
]
|
|
```
|
|
|
|
**Common Pattern**: All projects include mypy, basedpyright, ruff, pytest, pre-commit, hatchling tools.
|
|
|
|
**Evidence**: All 5 projects have dev dependency groups with these core tools.
|
|
|
|
## 12. GitLab Project-Specific Patterns
|
|
|
|
### Pattern: Custom PyPI Index (STANDARD - 4/4 GitLab projects)
|
|
|
|
```toml
|
|
[tool.uv]
|
|
publish-url = "{{gitlab_instance_url}}/api/v4/projects/{{project_id}}/packages/pypi"
|
|
|
|
[[tool.uv.index]]
|
|
name = "pypi"
|
|
url = "https://pypi.org/simple"
|
|
default = true
|
|
|
|
[[tool.uv.index]]
|
|
name = "gitlab"
|
|
url = "{{gitlab_instance_url}}/api/v4/groups/{{group_id}}/-/packages/pypi/simple"
|
|
explicit = true
|
|
default = false
|
|
```
|
|
|
|
## 13. Project Metadata Standards
|
|
|
|
### Pattern: Author and Maintainer (STANDARD - 5/5 projects)
|
|
|
|
```toml
|
|
[project]
|
|
authors = [{ name = "{{author_name_from_git_config_user_name}}", email = "{{author_email_from_git_config_user_email}}" }]
|
|
maintainers = [{ name = "{{author_name_from_git_config_user_name}}", email = "{{author_email_from_git_config_user_email}}" }]
|
|
```
|
|
|
|
**Observation**: Email addresses differ between GitHub projects (personal email) and GitLab projects (corporate email).
|
|
|
|
### Pattern: Classifiers (STANDARD - 5/5 projects)
|
|
|
|
**Common classifiers across all projects**:
|
|
|
|
```toml
|
|
classifiers = [
|
|
"Development Status :: 4 - Beta",
|
|
"Intended Audience :: Developers",
|
|
"Operating System :: POSIX :: Linux" or "Operating System :: OS Independent",
|
|
"Programming Language :: Python :: 3",
|
|
"Programming Language :: Python :: 3.11",
|
|
"Programming Language :: Python :: 3.12",
|
|
]
|
|
```
|
|
|
|
### Pattern: Keywords (STANDARD - 5/5 projects)
|
|
|
|
All projects include domain-specific keywords related to their purpose.
|
|
|
|
### Pattern: requires-python (STANDARD - 5/5 projects)
|
|
|
|
**Two variants**:
|
|
|
|
- GitHub: `>=3.10`
|
|
- GitLab: `>=3.11,<3.13`
|
|
|
|
## 14. CLI Entry Points
|
|
|
|
### Pattern: Typer-based CLI (STANDARD - 5/5 projects)
|
|
|
|
```toml
|
|
[project.scripts]
|
|
{package_name} = "{package_name}.cli:main" or "{package_name}.cli:app"
|
|
|
|
[project]
|
|
dependencies = [
|
|
"typer>=0.19.2",
|
|
]
|
|
```
|
|
|
|
**Evidence**: All 5 projects use Typer for CLI implementation.
|
|
|
|
## Summary of Standard Patterns
|
|
|
|
**STANDARD** (5/5 projects):
|
|
|
|
- Dual-mode version.py with hatch-vcs
|
|
- packages/ directory structure
|
|
- **all** exports in **init**.py
|
|
- Ruff formatting with 120 char line length
|
|
- Mypy strict mode
|
|
- Basedpyright type checking
|
|
- Pre-commit hooks (sync-deps, ruff, prettier, shellcheck, shfmt)
|
|
- .markdownlint.json (identical config)
|
|
- Semantic release configuration
|
|
- Typer-based CLI
|
|
- pytest with coverage
|
|
|
|
**COMMON** (3-4/5 projects):
|
|
|
|
- pep723-loader for type checking in pre-commit
|
|
- Custom hatch_build.py hook
|
|
- .editorconfig
|
|
- GitLab custom PyPI index
|
|
|
|
The model must follow STANDARD patterns for all new Python projects. COMMON patterns should be used when applicable (e.g., hatch_build.py only if binaries needed).
|