mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
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:
committed by
GitHub
Unverified
parent
d3d0100822
commit
f48c4512d3
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user