* fix: Fix installation conflict between onnxruntime and onnxruntime-gpu * fix: Fix CI, notebooks, type hints, and packaging issues found in audit * feat: Add new release config * ci: Automate release pipeline and document release process
7.3 KiB
Contributing to UniFace
Thank you for considering contributing to UniFace! We welcome contributions of all kinds.
How to Contribute
Reporting Issues
- Use GitHub Issues to report bugs or suggest features
- Include clear descriptions and reproducible examples
- Check existing issues before creating new ones
Pull Requests
- Fork the repository
- Create a new branch for your feature
- Write clear, documented code with type hints
- Add tests for new functionality
- Ensure all tests pass and pre-commit hooks are satisfied
- Submit a pull request with a clear description
Development Setup
git clone https://github.com/yakhyo/uniface.git
cd uniface
pip install -e ".[dev]"
Setting Up Pre-commit Hooks
We use pre-commit to ensure code quality and consistency. Install and configure it:
# Install pre-commit
pip install pre-commit
# Install the git hooks
pre-commit install
# (Optional) Run against all files
pre-commit run --all-files
Once installed, pre-commit will automatically run on every commit to check:
- Code formatting and linting (Ruff)
- Security issues (Bandit)
- General file hygiene (trailing whitespace, YAML/TOML validity, etc.)
Note: All PRs are automatically checked by CI. The merge button will only be available after all checks pass.
Code Style
This project uses Ruff for linting and formatting, following modern Python best practices. Pre-commit handles all formatting automatically.
Style Guidelines
General Rules
- Line length: 120 characters maximum
- Python version: 3.10+ (use modern syntax)
- Quote style: Single quotes for strings, double quotes for docstrings
Type Hints
Use modern Python 3.10+ type hints (PEP 585 and PEP 604):
# Preferred (modern)
def process(items: list[str], config: dict[str, int] | None = None) -> tuple[int, str]:
...
# Avoid (legacy)
from typing import List, Dict, Optional, Tuple
def process(items: List[str], config: Optional[Dict[str, int]] = None) -> Tuple[int, str]:
...
Docstrings
Use Google-style docstrings for all public APIs:
def create_detector(method: str = 'retinaface', **kwargs: Any) -> BaseDetector:
"""Factory function to create face detectors.
Args:
method: Detection method. Options: 'retinaface', 'scrfd', 'yolov5face', 'yolov8face'.
**kwargs: Detector-specific parameters.
Returns:
Initialized detector instance.
Raises:
ValueError: If method is not supported.
Example:
>>> from uniface import create_detector
>>> detector = create_detector('retinaface', confidence_threshold=0.8)
>>> faces = detector.detect(image)
>>> print(f"Found {len(faces)} faces")
"""
Import Order
Imports are automatically sorted by Ruff with the following order:
- Future imports (
from __future__ import annotations) - Standard library (
os,sys,typing, etc.) - Third-party (
numpy,cv2,onnxruntime, etc.) - First-party (
uniface.*) - Local (relative imports like
.base,.models)
from __future__ import annotations
import os
from typing import Any
import cv2
import numpy as np
from uniface.constants import RetinaFaceWeights
from uniface.log import Logger
from .base import BaseDetector
Code Comments
- Add comments for complex logic, magic numbers, and non-obvious behavior
- Avoid comments that merely restate the code
- Use
# TODO:with issue links for planned improvements
# RetinaFace FPN strides and corresponding anchor sizes per level
steps = [8, 16, 32]
min_sizes = [[16, 32], [64, 128], [256, 512]]
# Add small epsilon to prevent division by zero
similarity = np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b) + 1e-5)
Running Tests
# Run all tests
pytest tests/
# Run with verbose output
pytest tests/ -v
# Run specific test file
pytest tests/test_factory.py
# Run with coverage
pytest tests/ --cov=uniface --cov-report=html
Adding New Features
When adding a new model or feature:
- Create the model class in the appropriate submodule (e.g.,
uniface/detection/) - Add weight constants to
uniface/constants.pywith URLs and SHA256 hashes - Export in
__init__.pyfiles at both module and package levels - Write tests in
tests/directory - Add example usage in
tools/or update existing notebooks - Update documentation if needed
Examples
Example notebooks demonstrating library usage:
| Example | Notebook |
|---|---|
| Face Detection | 01_face_detection.ipynb |
| Face Alignment | 02_face_alignment.ipynb |
| Face Verification | 03_face_verification.ipynb |
| Face Search | 04_face_search.ipynb |
| Face Analyzer | 05_face_analyzer.ipynb |
| Face Parsing | 06_face_parsing.ipynb |
| Face Anonymization | 07_face_anonymization.ipynb |
| Gaze Estimation | 08_gaze_estimation.ipynb |
| Face Segmentation | 09_face_segmentation.ipynb |
| Face Vector Store | 10_face_vector_store.ipynb |
| Head Pose Estimation | 11_head_pose_estimation.ipynb |
Release Process
Releases are fully automated via GitHub Actions. Only maintainers with branch-protection bypass privileges on main can trigger a release.
Cutting a release
- Go to Actions → Release → Run workflow on GitHub.
- Enter the version following PEP 440:
- Stable:
0.7.0,1.0.0 - Pre-release:
0.7.0rc1,0.7.0b1,0.7.0a1,0.7.0.dev1
- Stable:
- Click Run workflow.
What happens automatically
The Release workflow:
- Validates the version string.
- Updates
pyproject.tomlanduniface/__init__.py. - Commits
chore: Release vX.Y.Ztomain. - Creates and pushes tag
vX.Y.Z.
Pushing the tag then triggers:
- Publish to PyPI — builds the package, runs tests on Python 3.10–3.14, uploads to PyPI, and creates a GitHub Release (flagged as pre-release for
a/b/rc/.devversions). - Deploy docs — fires only after a stable GitHub Release is published. Pre-releases do not update the live documentation site.
Verifying a release
- PyPI: https://pypi.org/project/uniface/
- GitHub Releases: https://github.com/yakhyo/uniface/releases
- Docs (stable only): https://yakhyo.github.io/uniface/
Installing a pre-release
End users can opt in to pre-releases with the --pre flag:
pip install uniface --pre # latest pre-release
pip install uniface==0.7.0rc1 # specific pre-release
Without --pre, pip install uniface always resolves to the latest stable version.
Questions?
Open an issue or start a discussion on GitHub.