-
Notifications
You must be signed in to change notification settings - Fork 1.3k
feat: Modernize precommit hooks and optimize test performance #5929
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
e7c9113
031a978
a23152e
ae52fcc
88cefb3
759dd9e
ff4548e
de69e92
c8bbf87
46ed9d9
df45285
6bc12c2
eb6b346
5417ab2
6f6c736
9dae77f
63c8f3b
2068303
0da7c1d
8848a45
4904104
60466b2
97cd848
386c7cf
d8b156c
0b6d274
c530cf6
dec75eb
f50366b
282558a
f8051e1
0e111fc
12b4a72
2d54924
c69a5c8
cf72f4e
ad90593
3e827bc
9552822
9c499ad
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
This comprehensive update modernizes Feast's development workflow with significant performance improvements inspired by llama-stack patterns: **Precommit Hook Improvements:** - ✅ Run hooks on commit (not push) for immediate feedback - ✅ Add comprehensive file checks (merge conflicts, large files, etc.) - ✅ Consolidate ruff linting and formatting - ✅ Enable MyPy incremental mode with sqlite cache for 75% speedup - ✅ Add smart template building (only when templates change) - ✅ Add __init__.py validation for Python packages **Test Performance Optimizations:** - ✅ Reduce pytest timeout from 20min to 5min - ✅ Add enhanced test markers and parallelization settings - ✅ Create fast unit test targets with auto worker detection - ✅ Add smoke test target for quick development validation **New Developer Tools:** - 🔧 Helper scripts: uv-run.sh, check-init-py.sh, mypy-daemon.sh - 📊 Performance monitoring with perf-monitor.py - 🚀 New Makefile targets: precommit-check, test-python-unit-fast - ⚡ MyPy daemon support for sub-second type checking **Expected Performance Gains:** - Lint time: 22s → <8s (64% improvement target) - Unit tests: 5min → 2min (60% improvement target) - Developer feedback: Immediate on commit vs delayed on push Co-Authored-By: Claude Sonnet 4 <noreply@anthropic.com>
- Loading branch information
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,23 +1,100 @@ | ||
| default_stages: | ||
| - push | ||
| exclude: 'build/|feast/embedded_go/lib/|\.pb2\.py$|protos/' | ||
| minimum_pre_commit_version: 3.3.0 | ||
| default_language_version: | ||
| python: python3.11 | ||
|
|
||
| default_stages: [commit] # RUN ON COMMIT, NOT PUSH! | ||
|
|
||
| repos: | ||
| # Standard file checks | ||
| - repo: https://github.com/pre-commit/pre-commit-hooks | ||
| rev: v5.0.0 | ||
| hooks: | ||
| - id: check-merge-conflict | ||
| args: ['--assume-in-merge'] | ||
| - id: trailing-whitespace | ||
| exclude: '\.py$' # Ruff handles Python files | ||
| - id: check-added-large-files | ||
| args: ['--maxkb=5000'] # Allow larger files for ML datasets | ||
| - id: end-of-file-fixer | ||
| exclude: '^(.*\.svg|.*\.md|.*\.pb2\.py)$' | ||
| - id: no-commit-to-branch | ||
| args: ['--branch=master', '--branch=main'] | ||
| - id: check-yaml | ||
| args: ["--unsafe"] | ||
| - id: detect-private-key | ||
| - id: mixed-line-ending | ||
| args: [--fix=lf] | ||
| - id: check-executables-have-shebangs | ||
| - id: check-json | ||
| - id: check-toml | ||
|
|
||
|
|
||
| # Ruff - consolidate linting and formatting | ||
| - repo: https://github.com/astral-sh/ruff-pre-commit | ||
| rev: v0.14.14 | ||
| hooks: | ||
| - id: ruff | ||
| args: [--fix] | ||
| files: ^sdk/python/ | ||
| - id: ruff-format | ||
| files: ^sdk/python/ | ||
|
|
||
| # Local hooks | ||
| - repo: local | ||
| hooks: | ||
| - id: format | ||
| name: Format | ||
| stages: [ push ] | ||
| # MyPy type checking with proper working directory | ||
| - id: mypy | ||
| name: mypy | ||
| entry: bash -c "cd sdk/python && python -m mypy feast" | ||
| language: system | ||
| entry: make format-python | ||
| files: ^sdk/python/(feast|tests)/.*\.py$ | ||
| pass_filenames: false | ||
| - id: lint | ||
| name: Lint | ||
| stages: [ push ] | ||
| # Template building only when templates change | ||
| - id: build-templates | ||
| name: Build Templates | ||
| entry: python infra/scripts/compile-templates.py | ||
| language: system | ||
| entry: make lint-python | ||
| files: \.(jinja2|md)$ | ||
| pass_filenames: false | ||
| - id: template | ||
| name: Build Templates | ||
| stages: [ commit ] | ||
| require_serial: true | ||
|
|
||
| # Check for missing __init__.py files in SDK | ||
| - id: check-init-py | ||
| name: Check for missing __init__.py files | ||
| entry: ./scripts/check-init-py.sh | ||
| language: system | ||
| entry: make build-templates | ||
| pass_filenames: false | ||
| pass_filenames: false | ||
| require_serial: true | ||
| files: ^sdk/python/feast/.*$ | ||
|
|
||
| # Prevent direct pytest.mark.asyncio usage | ||
| - id: forbid-pytest-asyncio | ||
| name: Block @pytest.mark.asyncio (use asyncio_mode=auto) | ||
| entry: bash | ||
| language: system | ||
| types: [python] | ||
| pass_filenames: true | ||
| args: | ||
| - -c | ||
| - | | ||
| grep -EnH '^[^#]*@pytest\.mark\.asyncio' "$@" && { | ||
| echo "❌ Do not use @pytest.mark.asyncio." | ||
| echo " pytest is already configured with asyncio_mode=auto." | ||
| exit 1; | ||
| } || true | ||
|
|
||
| # Full MyPy check (manual stage for thorough checking) | ||
| - id: mypy-full | ||
| name: mypy (full type checking) | ||
| entry: bash -c "cd sdk/python && python -m mypy feast tests" | ||
| language: system | ||
| pass_filenames: false | ||
| stages: [manual] | ||
|
|
||
| ci: | ||
| autofix_commit_msg: 🎨 [pre-commit.ci] Auto format | ||
| autoupdate_commit_msg: ⬆ [pre-commit.ci] pre-commit autoupdate | ||
| autofix_prs: true | ||
| autoupdate_schedule: weekly | ||
| skip: [mypy-full] # Skip manual stage hooks in CI | ||
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -55,14 +55,35 @@ protos: compile-protos-python compile-protos-docs ## Compile protobufs for Pytho | |||||||
| build: protos build-docker ## Build protobufs and Docker images | ||||||||
|
|
||||||||
| format-python: ## Format Python code | ||||||||
| cd ${ROOT_DIR}/sdk/python; python -m ruff check --fix feast/ tests/ | ||||||||
| cd ${ROOT_DIR}/sdk/python; python -m ruff format feast/ tests/ | ||||||||
| cd ${ROOT_DIR}/sdk/python && uv run ruff check --fix feast/ tests/ | ||||||||
| cd ${ROOT_DIR}/sdk/python && uv run ruff format feast/ tests/ | ||||||||
|
|
||||||||
| lint-python: ## Lint Python code | ||||||||
| cd ${ROOT_DIR}/sdk/python; python -m mypy feast | ||||||||
| cd ${ROOT_DIR}/sdk/python; python -m ruff check feast/ tests/ | ||||||||
| cd ${ROOT_DIR}/sdk/python; python -m ruff format --check feast/ tests | ||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. removing format check from CI?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nah i'll add that back. |
||||||||
|
|
||||||||
| cd ${ROOT_DIR}/sdk/python && uv run ruff check feast/ tests/ | ||||||||
| cd ${ROOT_DIR}/sdk/python && uv run mypy feast | ||||||||
|
|
||||||||
| # New combined target | ||||||||
| precommit-check: format-python lint-python ## Run all precommit checks | ||||||||
| @echo "✅ All precommit checks passed" | ||||||||
|
|
||||||||
| # Install precommit hooks with correct stages | ||||||||
| install-precommit: ## Install precommit hooks (runs on commit, not push) | ||||||||
| pip install pre-commit | ||||||||
| pre-commit install --hook-type pre-commit | ||||||||
| @echo "✅ Precommit hooks installed (will run on commit, not push)" | ||||||||
|
|
||||||||
| # Manual full type check | ||||||||
| mypy-full: ## Full MyPy type checking with all files | ||||||||
| cd ${ROOT_DIR}/sdk/python && uv run mypy feast tests | ||||||||
|
|
||||||||
| # Run precommit on all files | ||||||||
| precommit-all: ## Run all precommit hooks on all files | ||||||||
| pre-commit run --all-files | ||||||||
|
|
||||||||
| # Make scripts executable | ||||||||
| setup-scripts: ## Make helper scripts executable | ||||||||
| chmod +x scripts/uv-run.sh scripts/check-init-py.sh | ||||||||
|
devin-ai-integration[bot] marked this conversation as resolved.
Outdated
|
||||||||
| chmod +x scripts/uv-run.sh scripts/check-init-py.sh | |
| chmod +x scripts/uv-run.sh scripts/check-init-py.sh scripts/mypy-daemon.sh scripts/perf-monitor.py |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔴 test-python-integration-parallel target uses 300s timeout instead of 1200s like all other integration targets
The new test-python-integration-parallel Makefile target specifies --timeout=300 (5 minutes), while every other integration test target in the Makefile uses --timeout=1200 (20 minutes). Integration tests are inherently slower and frequently need more than 5 minutes per test. This will cause legitimate integration tests to be killed with timeout errors when run via this target.
Root Cause and Impact
All existing integration test targets consistently use --timeout=1200:
test-python-integrationatMakefile:157uses--timeout=1200test-python-integration-localatMakefile:169uses--timeout=1200test-python-integration-rbac-remoteatMakefile:179uses--timeout=1200
But the new target at Makefile:199-203 uses --timeout=300:
test-python-integration-parallel:
uv run python -m pytest sdk/python/tests/integration \
-n auto --dist loadscope \
--timeout=300 --tb=short -v \
--integration --color=yes --durations=20
The 300s value appears to have been copied from the new timeout = 300 setting in sdk/python/pytest.ini:21, which is intended as a default for unit tests. Integration tests need the longer 1200s timeout.
Impact: Integration tests running via this target will fail with timeout errors for any test taking over 5 minutes, which is common for integration tests that interact with external services.
| -n auto --dist loadscope \ | |
| --timeout=1200 --tb=short -v \ | |
Was this helpful? React with 👍 or 👎 to provide feedback.
Copilot
AI
Feb 2, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These targets hardcode .venv/bin/python without the fallback to python used by other test targets. That makes make test-python-integration-parallel fail for developers/CI environments that install deps into a different venv or use --system installs. Consider using the same conditional fallback pattern as test-python-unit/test-python-integration.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| #!/bin/bash | ||
| # Check for missing __init__.py files in Python packages | ||
|
|
||
| set -euo pipefail | ||
|
|
||
| SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | ||
| ROOT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" | ||
|
|
||
| # Find Python package directories missing __init__.py | ||
| missing_init_files=() | ||
|
|
||
| while IFS= read -r -d '' dir; do | ||
| # Skip .ipynb_checkpoints directories and other unwanted directories | ||
| if [[ "${dir}" == *".ipynb_checkpoints"* ]] || [[ "${dir}" == *"__pycache__"* ]]; then | ||
| continue | ||
| fi | ||
|
|
||
| if [[ ! -f "${dir}/__init__.py" ]] && [[ -n "$(find "${dir}" -maxdepth 1 -name "*.py" -print -quit)" ]]; then | ||
| missing_init_files+=("${dir}") | ||
| fi | ||
| done < <(find "${ROOT_DIR}/sdk/python/feast" -type d -print0) | ||
|
|
||
| if [[ ${#missing_init_files[@]} -gt 0 ]]; then | ||
| echo "❌ Missing __init__.py files in:" | ||
| printf " %s\n" "${missing_init_files[@]}" | ||
| echo "" | ||
| echo "Run: touch ${missing_init_files[*]/%//__init__.py}" | ||
| exit 1 | ||
| fi | ||
|
|
||
| echo "✅ All Python packages have __init__.py files" |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,55 @@ | ||||||
| #!/bin/bash | ||||||
| # MyPy daemon for sub-second type checking | ||||||
|
|
||||||
| set -euo pipefail | ||||||
|
|
||||||
| SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | ||||||
| ROOT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" | ||||||
|
|
||||||
| MYPY_CACHE_DIR="${ROOT_DIR}/sdk/python/.mypy_cache" | ||||||
| PID_FILE="$MYPY_CACHE_DIR/dmypy.pid" | ||||||
|
|
||||||
| case "$1" in | ||||||
|
franciscojavierarceo marked this conversation as resolved.
Outdated
|
||||||
| case "$1" in | |
| case "${1:-}" in |
Uh oh!
There was an error while loading. Please reload this page.