Python: Simplify Python Poe tasks and unify package selectors (#4722)

* updated automation tasks and commands, with alias for the time being

* Restore aggregate test exclusions

Preserve the legacy all-tests scope for test --all by excluding lab and devui from the default aggregate sweep, while still allowing explicit package selection. Also ignore hidden/generated test directories such as .mypy_cache during aggregate discovery.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* updated versions in pre-commit

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Eduard van Valkenburg
2026-03-18 19:39:11 +01:00
committed by GitHub
Unverified
parent d3d0100822
commit f48c4512d3
60 changed files with 1704 additions and 527 deletions
+8 -8
View File
@@ -46,14 +46,14 @@ These are the normal user-facing entrypoints:
uv run poe upgrade-dev-dependency-pins
uv run poe upgrade-dev-dependencies
uv run poe validate-dependency-bounds-test
uv run poe validate-dependency-bounds-test --project <workspace-package-name>
uv run poe validate-dependency-bounds-project --mode both --project <workspace-package-name> --dependency "<dependency-name>"
uv run poe validate-dependency-bounds-test --package core
uv run poe validate-dependency-bounds-project --mode both --package core --dependency "<dependency-name>"
```
- `upgrade-dev-dependency-pins` only refreshes exact dev pins in `pyproject.toml` files.
- `upgrade-dev-dependencies` refreshes dev pins (using task above), runs `uv lock --upgrade`, reinstalls from the frozen lockfile, then runs `check`, `typing`, and `test`.
- `validate-dependency-bounds-test` runs the repo-wide lower/upper smoke gate.
- `validate-dependency-bounds-project` is the single package-scoped task; use `--mode lower`, `--mode upper`, or `--mode both` for the target package/dependency pair. Its `--project` argument defaults to `*`, and `--dependency` is optional, so automation can also use it for repo-wide upper-bound runs.
- `validate-dependency-bounds-project` is the single package-scoped task; use `--mode lower`, `--mode upper`, or `--mode both` for the target package/dependency pair. Its `--package` argument defaults to `*`, and `--dependency` is optional, so automation can also use it for repo-wide upper-bound runs.
### GitHub Actions workflows
@@ -61,7 +61,7 @@ These workflows call the Poe tasks:
- `.github/workflows/python-dependency-range-validation.yml`
- Trigger: `workflow_dispatch`
- Runs `uv run poe validate-dependency-bounds-project --mode upper --project "*"`
- Runs `uv run poe validate-dependency-bounds-project --mode upper --package "*"`
- Uploads `python/scripts/dependencies/dependency-range-results.json`
- Creates issues for failing candidate versions and opens/updates a PR for passing range updates
@@ -76,10 +76,10 @@ These are useful for debugging or targeted manual runs:
```bash
python -m scripts.dependencies.upgrade_dev_dependencies --dry-run --version-source lock
python -m scripts.dependencies.validate_dependency_bounds --mode test --package packages/core --dry-run
python -m scripts.dependencies.validate_dependency_bounds --mode both --package packages/core --dependencies openai --dry-run
python -m scripts.dependencies._dependency_bounds_lower_impl --packages packages/core --dependencies openai --dry-run
python -m scripts.dependencies._dependency_bounds_upper_impl --packages packages/core --dependencies openai --dry-run
python -m scripts.dependencies.validate_dependency_bounds --mode test --package core --dry-run
python -m scripts.dependencies.validate_dependency_bounds --mode both --package core --dependencies openai --dry-run
python -m scripts.dependencies._dependency_bounds_lower_impl --packages core --dependencies openai --dry-run
python -m scripts.dependencies._dependency_bounds_upper_impl --packages core --dependencies openai --dry-run
```
Use the direct lower/upper implementation modules mainly for debugging or development of the optimizers themselves. For normal usage, prefer the Poe tasks or `validate_dependency_bounds.py`.
@@ -1,5 +1,5 @@
# Copyright (c) Microsoft. All rights reserved.
# ruff: noqa: INP001, S404, S603
# ruff: noqa: S404, S603
"""Lower dependency bounds, validate, and persist the oldest passing set."""
@@ -21,14 +21,15 @@ from urllib import error as urllib_error
from urllib import request as urllib_request
import tomli
from packaging.requirements import InvalidRequirement, Requirement
from packaging.version import InvalidVersion, Version
from rich import print
from scripts.dependencies._dependency_bounds_runtime import (
extend_command_with_runtime_tools,
extend_command_with_task,
)
from packaging.requirements import InvalidRequirement, Requirement
from packaging.version import InvalidVersion, Version
from rich import print
from scripts.task_runner import discover_projects, extract_poe_tasks
from scripts.task_runner import discover_projects, extract_poe_tasks, project_filter_matches
CHECK_TASK_PRIORITY = ("check", "typing", "pyright", "mypy", "lint")
REQ_PATTERN = r"^\s*([A-Za-z0-9_.-]+(?:\[[^\]]+\])?)\s*(.*?)\s*$"
@@ -937,7 +938,7 @@ def main() -> None:
"--packages",
nargs="*",
default=None,
help="Optional package filters by workspace path (e.g., packages/core) or package name.",
help="Optional package filters by short name (for example core), workspace path, or package name.",
)
parser.add_argument(
"--dependencies",
@@ -1001,7 +1002,11 @@ def main() -> None:
project_section = package_config.get("project", {})
optional_dependencies = project_section.get("optional-dependencies", {}) or {}
dependency_groups = package_config.get("dependency-groups", {}) or {}
if package_filters and str(project_path) not in package_filters and package_name not in package_filters:
# Reuse the shared selector matcher so direct optimizer runs accept the
# same short-name package filters as the contributor-facing Poe tasks.
if package_filters and not any(
project_filter_matches(project_path, package_filter, [package_name]) for package_filter in package_filters
):
continue
plans.append(
PackagePlan(
@@ -1,5 +1,5 @@
# Copyright (c) Microsoft. All rights reserved.
# ruff: noqa: INP001, S404, S603
# ruff: noqa: S404, S603
"""Raise dependency upper bounds, validate, and persist the latest passing set."""
@@ -22,15 +22,16 @@ from urllib import error as urllib_error
from urllib import request as urllib_request
import tomli
from packaging.requirements import InvalidRequirement, Requirement
from packaging.version import InvalidVersion, Version
from rich import print
from scripts.dependencies._dependency_bounds_runtime import (
extend_command_with_runtime_tools,
extend_command_with_task,
next_zero_major_minor_boundary,
)
from packaging.requirements import InvalidRequirement, Requirement
from packaging.version import InvalidVersion, Version
from rich import print
from scripts.task_runner import discover_projects, extract_poe_tasks
from scripts.task_runner import discover_projects, extract_poe_tasks, project_filter_matches
CHECK_TASK_PRIORITY = ("check", "typing", "pyright", "mypy", "lint")
REQ_PATTERN = r"^\s*([A-Za-z0-9_.-]+(?:\[[^\]]+\])?)\s*(.*?)\s*$"
@@ -1088,7 +1089,7 @@ def main() -> None:
"--packages",
nargs="*",
default=None,
help="Optional package filters by workspace path (e.g., packages/core) or package name.",
help="Optional package filters by short name (for example core), workspace path, or package name.",
)
parser.add_argument(
"--dependencies",
@@ -1153,7 +1154,11 @@ def main() -> None:
project_section = package_config.get("project", {})
optional_dependencies = project_section.get("optional-dependencies", {}) or {}
dependency_groups = package_config.get("dependency-groups", {}) or {}
if package_filters and str(project_path) not in package_filters and package_name not in package_filters:
# Reuse the shared selector matcher so direct optimizer runs accept the
# same short-name package filters as the contributor-facing Poe tasks.
if package_filters and not any(
project_filter_matches(project_path, package_filter, [package_name]) for package_filter in package_filters
):
continue
plans.append(
PackagePlan(
@@ -0,0 +1,118 @@
# Copyright (c) Microsoft. All rights reserved.
# ruff: noqa: S603
"""Add a dependency to one workspace package selected by short name or path.
``uv add --package`` expects the published workspace distribution name, while
the root Poe surface intentionally speaks in short repo package names such as
``core``. This wrapper keeps the user-facing selector stable and translates it
just before delegating to uv.
"""
from __future__ import annotations
import argparse
import subprocess
from dataclasses import dataclass
from pathlib import Path
import tomli
from rich import print
from scripts.task_runner import discover_projects, project_filter_matches
@dataclass(frozen=True)
class WorkspacePackage:
"""Workspace package metadata needed for `uv add --package`."""
short_name: str
project_path: Path
distribution_name: str
def _load_distribution_name(pyproject_file: Path) -> str:
with pyproject_file.open("rb") as f:
data = tomli.load(f)
return str(data.get("project", {}).get("name", "")).strip()
def _discover_workspace_packages(workspace_root: Path) -> list[WorkspacePackage]:
workspace_pyproject = workspace_root / "pyproject.toml"
packages: list[WorkspacePackage] = []
for project_path in sorted(discover_projects(workspace_pyproject), key=str):
pyproject_file = workspace_root / project_path / "pyproject.toml"
if not pyproject_file.exists():
continue
distribution_name = _load_distribution_name(pyproject_file)
if not distribution_name:
continue
packages.append(
WorkspacePackage(
short_name=project_path.name,
project_path=project_path,
distribution_name=distribution_name,
)
)
return packages
def _resolve_workspace_package(workspace_root: Path, project_filter: str) -> WorkspacePackage:
"""Resolve one workspace package from a user-facing selector.
The wrapper accepts the same short-name/path/distribution-name vocabulary as
the other root tasks, but errors on ambiguous matches so dependency edits
never hit the wrong package.
"""
matches = [
package
for package in _discover_workspace_packages(workspace_root)
if project_filter_matches(package.project_path, project_filter, [package.short_name, package.distribution_name])
]
if not matches:
raise SystemExit(f"No workspace package matched selector '{project_filter}'.")
if len(matches) > 1:
names = ", ".join(sorted(package.short_name for package in matches))
raise SystemExit(
f"Package selector '{project_filter}' matched multiple workspace packages: {names}. "
"Use a more specific short name or path."
)
return matches[0]
def main() -> None:
"""Resolve a workspace project selector, then delegate to `uv add`."""
parser = argparse.ArgumentParser(
description="Add a dependency to a single workspace package selected by short name, path, or package name."
)
parser.add_argument(
"-P",
"--package",
dest="project",
metavar="PACKAGE",
required=True,
help="Workspace package selector, such as `core`.",
)
# Keep the old long flag as a silent alias while downstream automation
# finishes moving to the user-facing ``--package`` spelling.
parser.add_argument("--project", dest="project", help=argparse.SUPPRESS)
parser.add_argument("-D", "--dependency", required=True, help="Dependency specifier to add.")
args = parser.parse_args()
workspace_root = Path(__file__).resolve().parents[2]
package = _resolve_workspace_package(workspace_root, args.project)
print(
f"[cyan]Adding {args.dependency} to {package.short_name} "
f"({package.distribution_name})[/cyan]"
)
result = subprocess.run(
["uv", "add", "--package", package.distribution_name, args.dependency],
cwd=workspace_root,
check=False,
)
if result.returncode:
raise SystemExit(result.returncode)
if __name__ == "__main__":
main()
@@ -1,5 +1,5 @@
# Copyright (c) Microsoft. All rights reserved.
# ruff: noqa: INP001, S404, S603
# ruff: noqa: S404, S603
"""Unified dependency-bound validation entrypoint.
@@ -8,6 +8,10 @@ Modes:
- lower: run lower-bound expansion for one package.
- upper: run upper-bound expansion for one package.
- both: run lower then upper expansion for one package.
Package filters intentionally reuse the root task selector semantics so the
same short package names (for example ``core``) work in both contributor
commands and direct debugging entrypoints.
"""
from __future__ import annotations
@@ -23,6 +27,7 @@ from pathlib import Path
import tomli
from rich import print
from scripts.dependencies._dependency_bounds_runtime import (
extend_command_with_runtime_tools,
extend_command_with_task,
@@ -33,7 +38,7 @@ from scripts.dependencies._dependency_bounds_upper_impl import (
_load_package_name,
_resolve_internal_editables,
)
from scripts.task_runner import discover_projects, extract_poe_tasks
from scripts.task_runner import discover_projects, extract_poe_tasks, project_filter_matches
_LOWER_IMPL_MODULE = "scripts.dependencies._dependency_bounds_lower_impl"
_UPPER_IMPL_MODULE = "scripts.dependencies._dependency_bounds_upper_impl"
@@ -76,10 +81,10 @@ def _coerce_subprocess_output(output: str | bytes | None) -> str:
def _build_test_plans(workspace_root: Path, package_filter: str | None) -> list[PackageTestPlan]:
"""Build per-package test plans for the requested workspace selector."""
workspace_pyproject = workspace_root / "pyproject.toml"
package_map = _build_workspace_package_map(workspace_root)
internal_graph = _build_internal_graph(workspace_root, package_map)
normalized_filter = None if package_filter in {None, "", "*"} else package_filter
plans: list[PackageTestPlan] = []
missing_tasks: list[str] = []
@@ -89,7 +94,14 @@ def _build_test_plans(workspace_root: Path, package_filter: str | None) -> list[
continue
package_name = _load_package_name(pyproject_file)
if normalized_filter and str(project_path) != normalized_filter and package_name != normalized_filter:
# Reuse the shared matcher so dependency-bound test mode accepts the
# same short names and legacy path-style selectors as the root Poe
# commands.
if (
package_filter
and package_filter != "*"
and not project_filter_matches(project_path, package_filter, [package_name])
):
continue
available_tasks = extract_poe_tasks(pyproject_file)
@@ -366,7 +378,10 @@ def main() -> None:
parser.add_argument(
"--package",
default=None,
help="Optional workspace package path/name filter for all modes. Use '*' or omit it for the whole workspace.",
help=(
"Optional workspace package selector for all modes, such as `core`. "
"Use '*' or omit it for the whole workspace."
),
)
parser.add_argument(
"--dependencies",