devx: add a repo-managed pre-push CI gate
Add a versioned pre-push runner that mirrors the basic required CI suites we can execute locally, including version/changelog checks, Rust fmt/clippy/core checks, Python lint/typecheck/tests, docs, and the WASM gate. Wire the runner into pre-commit at the pre-push stage, add make prepush and make hooks for manual use and hook installation, and document the workflow in the contribution guides. Also add pre-commit to the development dependency set and refresh uv.lock so a synced dev environment can install and run the hook reproducibly.
This commit is contained in:
+11
-1
@@ -1,5 +1,5 @@
|
||||
# Pre-commit hooks for ferro-ta
|
||||
# Install: pre-commit install
|
||||
# Install: pre-commit install --hook-type pre-commit --hook-type pre-push
|
||||
# Run: pre-commit run --all-files
|
||||
default_language_version:
|
||||
python: python3
|
||||
@@ -31,3 +31,13 @@ repos:
|
||||
# entry: mypy python/ferro_ta --ignore-missing-imports
|
||||
# language: system
|
||||
# pass_filenames: false
|
||||
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: ci-basic-pre-push
|
||||
name: ci basic pre-push
|
||||
entry: scripts/pre_push_checks.sh
|
||||
language: system
|
||||
pass_filenames: false
|
||||
always_run: true
|
||||
stages: [pre-push]
|
||||
|
||||
@@ -36,6 +36,34 @@ uv run ruff check python/ tests/
|
||||
uv run mypy python/ferro_ta --ignore-missing-imports
|
||||
```
|
||||
|
||||
## Git hooks and pre-push checks
|
||||
|
||||
Install the repo-managed git hooks after syncing your environment:
|
||||
|
||||
```bash
|
||||
make hooks
|
||||
```
|
||||
|
||||
That installs both the existing `pre-commit` hook and a `pre-push` hook that
|
||||
runs the local CI gate before anything is pushed.
|
||||
|
||||
To run the same gate manually:
|
||||
|
||||
```bash
|
||||
make prepush
|
||||
```
|
||||
|
||||
To run only part of it while iterating:
|
||||
|
||||
```bash
|
||||
make prepush CHECKS="version changelog python_lint"
|
||||
```
|
||||
|
||||
The pre-push runner covers the basic required CI categories we can execute
|
||||
locally: version/changelog checks, Rust fmt/clippy/core checks, Python
|
||||
lint/typecheck/tests, docs, and WASM. It intentionally skips the multi-version
|
||||
matrix, audit, and benchmark-regression jobs.
|
||||
|
||||
## Alternative: set up with plain pip
|
||||
|
||||
```bash
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# ferro-ta development Makefile
|
||||
# Usage: make <target>
|
||||
|
||||
.PHONY: help dev build test lint typecheck fmt docs clean bench version
|
||||
.PHONY: help dev build test lint typecheck fmt docs clean bench version audit prepush hooks
|
||||
|
||||
# Default target
|
||||
help:
|
||||
@@ -17,12 +17,14 @@ help:
|
||||
@echo " make bench Run Rust criterion benchmarks (ferro_ta_core)"
|
||||
@echo " make version Bump tracked version strings (set VERSION=X.Y.Z)"
|
||||
@echo " make audit Run cargo-audit + pip-audit"
|
||||
@echo " make prepush Run the local pre-push CI gate (set CHECKS='version rust_fmt' to scope it)"
|
||||
@echo " make hooks Install pre-commit and pre-push git hooks"
|
||||
@echo " make clean Remove build artefacts"
|
||||
|
||||
dev:
|
||||
pip install uv
|
||||
uv pip install --system maturin numpy pytest pytest-cov pandas polars hypothesis pyyaml \
|
||||
sphinx sphinx-rtd-theme ruff mypy pyright
|
||||
sphinx sphinx-rtd-theme ruff mypy pyright pre-commit
|
||||
|
||||
build:
|
||||
maturin develop --release
|
||||
@@ -57,6 +59,12 @@ audit:
|
||||
cargo audit
|
||||
uv run --with pip-audit pip-audit
|
||||
|
||||
prepush:
|
||||
bash scripts/pre_push_checks.sh $(CHECKS)
|
||||
|
||||
hooks:
|
||||
uv run --with pre-commit pre-commit install --hook-type pre-commit --hook-type pre-push
|
||||
|
||||
clean:
|
||||
cargo clean
|
||||
rm -rf dist/ docs/_build/ coverage.xml .coverage *.egg-info
|
||||
|
||||
@@ -26,6 +26,28 @@ Prerequisites: Rust stable toolchain, Python 3.10+, and ``maturin``.
|
||||
pytest tests/
|
||||
|
||||
|
||||
Git hooks and pre-push checks
|
||||
-----------------------------
|
||||
|
||||
Install the repository-managed hooks after setting up the environment:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
make hooks
|
||||
|
||||
Run the same push gate manually with:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
make prepush
|
||||
|
||||
You can scope it to selected checks while iterating:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
make prepush CHECKS="version changelog python_lint"
|
||||
|
||||
|
||||
Adding a new indicator
|
||||
-----------------------
|
||||
|
||||
|
||||
@@ -53,6 +53,7 @@ dev = [
|
||||
"hypothesis>=6.0",
|
||||
"pandas>=1.0",
|
||||
"polars>=0.19",
|
||||
"pre-commit>=3.0",
|
||||
"ruff>=0.3",
|
||||
"mypy>=1.0",
|
||||
"pyright>=1.1",
|
||||
@@ -130,6 +131,7 @@ dev = [
|
||||
"hypothesis>=6.0",
|
||||
"pandas>=1.0",
|
||||
"polars>=0.19",
|
||||
"pre-commit>=3.0",
|
||||
"ruff>=0.3",
|
||||
"mypy>=1.0",
|
||||
"pyright>=1.1",
|
||||
|
||||
Executable
+193
@@ -0,0 +1,193 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
cd "$ROOT_DIR"
|
||||
|
||||
AVAILABLE_CHECKS=(
|
||||
version
|
||||
changelog
|
||||
rust_fmt
|
||||
rust_clippy
|
||||
rust_core
|
||||
rust_bench
|
||||
python_lint
|
||||
python_typecheck
|
||||
python_test
|
||||
docs
|
||||
wasm
|
||||
manifest
|
||||
)
|
||||
|
||||
DEFAULT_CHECKS=("${AVAILABLE_CHECKS[@]}")
|
||||
|
||||
python_env_ready=0
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage:
|
||||
scripts/pre_push_checks.sh
|
||||
scripts/pre_push_checks.sh <check> [<check> ...]
|
||||
scripts/pre_push_checks.sh --list
|
||||
|
||||
Runs the repo's basic local CI gate before push. By default it covers:
|
||||
version changelog rust_fmt rust_clippy rust_core rust_bench
|
||||
python_lint python_typecheck python_test docs wasm manifest
|
||||
|
||||
Notes:
|
||||
- This mirrors the required CI categories we can run locally.
|
||||
- It intentionally skips the multi-Python test matrix, audit jobs, perf smoke,
|
||||
and benchmark-regression jobs.
|
||||
EOF
|
||||
}
|
||||
|
||||
list_checks() {
|
||||
printf '%s\n' "${AVAILABLE_CHECKS[@]}"
|
||||
}
|
||||
|
||||
need_cmd() {
|
||||
local command_name="$1"
|
||||
if ! command -v "$command_name" >/dev/null 2>&1; then
|
||||
echo "Missing required command: $command_name" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
run_cmd() {
|
||||
printf ' +'
|
||||
printf ' %q' "$@"
|
||||
printf '\n'
|
||||
"$@"
|
||||
}
|
||||
|
||||
ensure_python_env() {
|
||||
if [[ "$python_env_ready" -eq 1 ]]; then
|
||||
return
|
||||
fi
|
||||
|
||||
need_cmd uv
|
||||
run_cmd uv sync --extra dev --extra docs
|
||||
run_cmd uv run --extra dev --extra docs maturin develop --release
|
||||
python_env_ready=1
|
||||
}
|
||||
|
||||
run_version() {
|
||||
need_cmd python3
|
||||
run_cmd python3 scripts/bump_version.py --check
|
||||
}
|
||||
|
||||
run_changelog() {
|
||||
need_cmd python3
|
||||
run_cmd python3 scripts/check_changelog.py
|
||||
}
|
||||
|
||||
run_rust_fmt() {
|
||||
need_cmd cargo
|
||||
run_cmd cargo fmt --all -- --check
|
||||
}
|
||||
|
||||
run_rust_clippy() {
|
||||
need_cmd cargo
|
||||
run_cmd cargo clippy --release -- -D warnings
|
||||
}
|
||||
|
||||
run_rust_core() {
|
||||
need_cmd cargo
|
||||
run_cmd cargo build -p ferro_ta_core
|
||||
run_cmd cargo test -p ferro_ta_core
|
||||
}
|
||||
|
||||
run_rust_bench() {
|
||||
need_cmd cargo
|
||||
run_cmd cargo bench -p ferro_ta_core --no-run
|
||||
}
|
||||
|
||||
run_python_lint() {
|
||||
need_cmd uv
|
||||
run_cmd uv run --with ruff ruff check python/ tests/
|
||||
run_cmd uv run --with ruff ruff format --check python/ tests/
|
||||
}
|
||||
|
||||
run_python_typecheck() {
|
||||
need_cmd uv
|
||||
run_cmd uv run --with mypy --with numpy mypy python/ferro_ta --ignore-missing-imports --no-error-summary
|
||||
run_cmd uv run --with pyright pyright python/ferro_ta
|
||||
}
|
||||
|
||||
run_python_test() {
|
||||
ensure_python_env
|
||||
run_cmd uv run --extra dev --with pytest-cov pytest tests/unit/ tests/integration/ -v --cov=ferro_ta --cov-report=term-missing --cov-fail-under=65
|
||||
}
|
||||
|
||||
run_docs() {
|
||||
ensure_python_env
|
||||
run_cmd uv run --extra docs python -m sphinx -b html docs docs/_build -W --keep-going
|
||||
}
|
||||
|
||||
run_wasm() {
|
||||
need_cmd node
|
||||
need_cmd wasm-pack
|
||||
local benchmark_json="../.wasm_benchmark.prepush.json"
|
||||
(
|
||||
cd wasm
|
||||
trap 'rm -f "$benchmark_json"' EXIT
|
||||
run_cmd wasm-pack test --node
|
||||
run_cmd wasm-pack build --target nodejs --out-dir pkg
|
||||
run_cmd node bench.js --json "$benchmark_json"
|
||||
)
|
||||
}
|
||||
|
||||
run_manifest() {
|
||||
need_cmd python3
|
||||
run_cmd python3 scripts/check_api_manifest.py
|
||||
}
|
||||
|
||||
run_check() {
|
||||
local check_name="$1"
|
||||
case "$check_name" in
|
||||
version) run_version ;;
|
||||
changelog) run_changelog ;;
|
||||
rust_fmt) run_rust_fmt ;;
|
||||
rust_clippy) run_rust_clippy ;;
|
||||
rust_core) run_rust_core ;;
|
||||
rust_bench) run_rust_bench ;;
|
||||
python_lint) run_python_lint ;;
|
||||
python_typecheck) run_python_typecheck ;;
|
||||
python_test) run_python_test ;;
|
||||
docs) run_docs ;;
|
||||
wasm) run_wasm ;;
|
||||
manifest) run_manifest ;;
|
||||
*)
|
||||
echo "Unknown check: $check_name" >&2
|
||||
echo "Use --list to see supported checks." >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
if [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]]; then
|
||||
usage
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ "${1:-}" == "--list" ]]; then
|
||||
list_checks
|
||||
exit 0
|
||||
fi
|
||||
|
||||
selected_checks=()
|
||||
if [[ "$#" -gt 0 ]]; then
|
||||
selected_checks=("$@")
|
||||
else
|
||||
selected_checks=("${DEFAULT_CHECKS[@]}")
|
||||
fi
|
||||
|
||||
total_checks="${#selected_checks[@]}"
|
||||
index=0
|
||||
for check_name in "${selected_checks[@]}"; do
|
||||
index=$((index + 1))
|
||||
printf '\n[%d/%d] %s\n' "$index" "$total_checks" "$check_name"
|
||||
run_check "$check_name"
|
||||
done
|
||||
|
||||
printf '\nAll selected pre-push checks passed.\n'
|
||||
@@ -180,6 +180,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfgv"
|
||||
version = "3.5.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/4e/b5/721b8799b04bf9afe054a3899c6cf4e880fcf8563cc71c15610242490a0c/cfgv-3.5.0.tar.gz", hash = "sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132", size = 7334, upload-time = "2025-11-19T20:55:51.612Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0", size = 7445, upload-time = "2025-11-19T20:55:50.744Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "charset-normalizer"
|
||||
version = "3.4.5"
|
||||
@@ -595,6 +604,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321, upload-time = "2023-10-07T05:32:16.783Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "distlib"
|
||||
version = "0.4.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605, upload-time = "2025-07-17T16:52:00.465Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "docutils"
|
||||
version = "0.21.2"
|
||||
@@ -657,7 +675,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "ferro-ta"
|
||||
version = "1.0.3"
|
||||
version = "1.0.4"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "numpy" },
|
||||
@@ -693,6 +711,7 @@ dev = [
|
||||
{ name = "pandas", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or python_full_version >= '3.14'" },
|
||||
{ name = "pandas", version = "3.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' and python_full_version < '3.14'" },
|
||||
{ name = "polars" },
|
||||
{ name = "pre-commit" },
|
||||
{ name = "pyright" },
|
||||
{ name = "pytest" },
|
||||
{ name = "pyyaml" },
|
||||
@@ -731,6 +750,7 @@ dev = [
|
||||
{ name = "pandas", version = "3.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' and python_full_version < '3.14'" },
|
||||
{ name = "pandas-ta", marker = "python_full_version >= '3.12'" },
|
||||
{ name = "polars" },
|
||||
{ name = "pre-commit" },
|
||||
{ name = "pyright" },
|
||||
{ name = "pytest" },
|
||||
{ name = "pyyaml" },
|
||||
@@ -757,6 +777,7 @@ requires-dist = [
|
||||
{ name = "polars", marker = "extra == 'all'", specifier = ">=0.19" },
|
||||
{ name = "polars", marker = "extra == 'dev'", specifier = ">=0.19" },
|
||||
{ name = "polars", marker = "extra == 'polars'", specifier = ">=0.19" },
|
||||
{ name = "pre-commit", marker = "extra == 'dev'", specifier = ">=3.0" },
|
||||
{ name = "pyright", marker = "extra == 'dev'", specifier = ">=1.1" },
|
||||
{ name = "pytest", marker = "extra == 'all'", specifier = ">=7.0" },
|
||||
{ name = "pytest", marker = "extra == 'benchmark'", specifier = ">=7.0" },
|
||||
@@ -783,6 +804,7 @@ dev = [
|
||||
{ name = "pandas", specifier = ">=1.0" },
|
||||
{ name = "pandas-ta", marker = "python_full_version >= '3.12'", specifier = ">=0.3" },
|
||||
{ name = "polars", specifier = ">=0.19" },
|
||||
{ name = "pre-commit", specifier = ">=3.0" },
|
||||
{ name = "pyright", specifier = ">=1.1" },
|
||||
{ name = "pytest", specifier = ">=7.0" },
|
||||
{ name = "pyyaml", specifier = ">=6.0" },
|
||||
@@ -924,6 +946,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/f7/5cc291d701094754a1d327b44d80a44971e13962881d9a400235726171da/hypothesis-6.151.9-py3-none-any.whl", hash = "sha256:7b7220585c67759b1b1ef839b1e6e9e3d82ed468cfc1ece43c67184848d7edd9", size = 529307, upload-time = "2026-02-16T22:59:20.443Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "identify"
|
||||
version = "2.6.18"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/46/c4/7fb4db12296cdb11893d61c92048fe617ee853f8523b9b296ac03b43757e/identify-2.6.18.tar.gz", hash = "sha256:873ac56a5e3fd63e7438a7ecbc4d91aca692eb3fefa4534db2b7913f3fc352fd", size = 99580, upload-time = "2026-03-15T18:39:50.319Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/46/33/92ef41c6fad0233e41d3d84ba8e8ad18d1780f1e5d99b3c683e6d7f98b63/identify-2.6.18-py2.py3-none-any.whl", hash = "sha256:8db9d3c8ea9079db92cafb0ebf97abdc09d52e97f4dcf773a2e694048b7cd737", size = 99394, upload-time = "2026-03-15T18:39:48.915Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.11"
|
||||
@@ -2046,6 +2077,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f2/26/c56ce33ca856e358d27fda9676c055395abddb82c35ac0f593877ed4562e/pillow-12.1.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:cb9bb857b2d057c6dfc72ac5f3b44836924ba15721882ef103cecb40d002d80e", size = 7029880, upload-time = "2026-02-11T04:23:04.783Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "platformdirs"
|
||||
version = "4.9.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/19/56/8d4c30c8a1d07013911a8fdbd8f89440ef9f08d07a1b50ab8ca8be5a20f9/platformdirs-4.9.4.tar.gz", hash = "sha256:1ec356301b7dc906d83f371c8f487070e99d3ccf9e501686456394622a01a934", size = 28737, upload-time = "2026-03-05T18:34:13.271Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/63/d7/97f7e3a6abb67d8080dd406fd4df842c2be0efaf712d1c899c32a075027c/platformdirs-4.9.4-py3-none-any.whl", hash = "sha256:68a9a4619a666ea6439f2ff250c12a853cd1cbd5158d258bd824a7df6be2f868", size = 21216, upload-time = "2026-03-05T18:34:12.172Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pluggy"
|
||||
version = "1.6.0"
|
||||
@@ -2083,6 +2123,22 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/da/76/2d48927e0aa2abbdde08cbf4a2536883b73277d47fbeca95e952de86df34/polars_runtime_32-1.39.3-cp310-abi3-win_arm64.whl", hash = "sha256:f49f51461de63f13e5dd4eb080421c8f23f856945f3f8bd5b2b1f59da52c2860", size = 41857648, upload-time = "2026-03-20T11:15:01.142Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pre-commit"
|
||||
version = "4.5.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "cfgv" },
|
||||
{ name = "identify" },
|
||||
{ name = "nodeenv" },
|
||||
{ name = "pyyaml" },
|
||||
{ name = "virtualenv" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/40/f1/6d86a29246dfd2e9b6237f0b5823717f60cad94d47ddc26afa916d21f525/pre_commit-4.5.1.tar.gz", hash = "sha256:eb545fcff725875197837263e977ea257a402056661f09dae08e4b149b030a61", size = 198232, upload-time = "2025-12-16T21:14:33.552Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl", hash = "sha256:3b3afd891e97337708c1674210f8eba659b52a38ea5f822ff142d10786221f77", size = 226437, upload-time = "2025-12-16T21:14:32.409Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "py-cpuinfo"
|
||||
version = "9.0.0"
|
||||
@@ -2345,6 +2401,19 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-discovery"
|
||||
version = "1.2.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "filelock" },
|
||||
{ name = "platformdirs" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/9c/90/bcce6b46823c9bec1757c964dc37ed332579be512e17a30e9698095dcae4/python_discovery-1.2.0.tar.gz", hash = "sha256:7d33e350704818b09e3da2bd419d37e21e7c30db6e0977bb438916e06b41b5b1", size = 58055, upload-time = "2026-03-19T01:43:08.248Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/3c/2005227cb951df502412de2fa781f800663cccbef8d90ec6f1b371ac2c0d/python_discovery-1.2.0-py3-none-any.whl", hash = "sha256:1e108f1bbe2ed0ef089823d28805d5ad32be8e734b86a5f212bf89b71c266e4a", size = 31524, upload-time = "2026-03-19T01:43:07.045Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-dotenv"
|
||||
version = "1.2.2"
|
||||
@@ -3162,6 +3231,22 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/0a/89/f8827ccff89c1586027a105e5630ff6139a64da2515e24dafe860bd9ae4d/uvicorn-0.42.0-py3-none-any.whl", hash = "sha256:96c30f5c7abe6f74ae8900a70e92b85ad6613b745d4879eb9b16ccad15645359", size = 68830, upload-time = "2026-03-16T06:19:48.325Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "virtualenv"
|
||||
version = "21.2.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "distlib" },
|
||||
{ name = "filelock" },
|
||||
{ name = "platformdirs" },
|
||||
{ name = "python-discovery" },
|
||||
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/aa/92/58199fe10049f9703c2666e809c4f686c54ef0a68b0f6afccf518c0b1eb9/virtualenv-21.2.0.tar.gz", hash = "sha256:1720dc3a62ef5b443092e3f499228599045d7fea4c79199770499df8becf9098", size = 5840618, upload-time = "2026-03-09T17:24:38.013Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c6/59/7d02447a55b2e55755011a647479041bc92a82e143f96a8195cb33bd0a1c/virtualenv-21.2.0-py3-none-any.whl", hash = "sha256:1bd755b504931164a5a496d217c014d098426cddc79363ad66ac78125f9d908f", size = 5825084, upload-time = "2026-03-09T17:24:35.378Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zipp"
|
||||
version = "3.23.0"
|
||||
|
||||
Reference in New Issue
Block a user