Skip to content

Commit 5a7b1cd

Browse files
pre-commit improvements
1 parent 8d7e01e commit 5a7b1cd

6 files changed

Lines changed: 165 additions & 53 deletions

File tree

python/.pre-commit-config.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ repos:
4747
entry: uv --directory ./python run poe pre-commit-check
4848
language: system
4949
files: ^python/
50-
pass_filenames: false
5150
- repo: https://github.com/astral-sh/uv-pre-commit
5251
# uv version.
5352
rev: 0.7.18

python/check_md_code_blocks.py

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,19 @@ def with_color(text: str, color: Colors) -> str:
3333
return f"{color.value}{text}{Colors.CEND.value}"
3434

3535

36-
def expand_file_patterns(patterns: list[str]) -> list[str]:
36+
def expand_file_patterns(patterns: list[str], skip_glob: bool = False) -> list[str]:
3737
"""Expand glob patterns to actual file paths."""
3838
all_files: list[str] = []
3939
for pattern in patterns:
40-
# Handle both relative and absolute paths
41-
matches = glob.glob(pattern, recursive=True)
42-
all_files.extend(matches)
40+
if skip_glob:
41+
# When skip_glob is True, treat patterns as literal file paths
42+
# Only include if it's a markdown file
43+
if pattern.endswith('.md'):
44+
all_files.append(pattern)
45+
else:
46+
# Handle both relative and absolute paths with glob expansion
47+
matches = glob.glob(pattern, recursive=True)
48+
all_files.extend(matches)
4349
return sorted(set(all_files)) # Remove duplicates and sort
4450

4551

@@ -126,8 +132,9 @@ def check_code_blocks(markdown_file_paths: list[str], exclude_patterns: list[str
126132
# Argument is a list of markdown files containing glob patterns
127133
parser.add_argument("markdown_files", nargs="+", help="Markdown files to check (supports glob patterns).")
128134
parser.add_argument("--exclude", action="append", help="Exclude files containing this pattern.")
135+
parser.add_argument("--no-glob", action="store_true", help="Treat file arguments as literal paths (no glob expansion).")
129136
args = parser.parse_args()
130-
131-
# Expand glob patterns to actual file paths
132-
expanded_files = expand_file_patterns(args.markdown_files)
137+
138+
# Expand glob patterns to actual file paths (or skip if --no-glob)
139+
expanded_files = expand_file_patterns(args.markdown_files, skip_glob=args.no_glob)
133140
check_code_blocks(expanded_files, args.exclude)

python/packages/anthropic/tests/test_anthropic_client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ def test_anthropic_settings_init_with_explicit_values() -> None:
9292
@pytest.mark.parametrize("exclude_list", [["ANTHROPIC_API_KEY"]], indirect=True)
9393
def test_anthropic_settings_missing_api_key(anthropic_unit_test_env: dict[str, str]) -> None:
9494
"""Test AnthropicSettings when API key is missing."""
95-
settings = AnthropicSettings()
95+
settings = AnthropicSettings(env_file_path="test.env")
9696
assert settings.api_key is None
9797
assert settings.chat_model_id == anthropic_unit_test_env["ANTHROPIC_CHAT_MODEL_ID"]
9898

python/packages/chatkit/pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ omit = [
5656
]
5757

5858
[tool.pyright]
59-
extend = "../../pyproject.toml"
59+
extends = "../../pyproject.toml"
6060
exclude = ['tests', 'chatkit-python', 'openai-chatkit-advanced-samples']
6161

6262
[tool.mypy]
@@ -86,4 +86,4 @@ test = "pytest --cov=agent_framework_chatkit --cov-report=term-missing:skip-cove
8686

8787
[build-system]
8888
requires = ["flit-core >= 3.11,<4.0"]
89-
build-backend = "flit_core.buildapi"
89+
build-backend = "flit_core.buildapi"

python/pyproject.toml

Lines changed: 20 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -215,11 +215,6 @@ executor.type = "uv"
215215

216216
[tool.poe.tasks]
217217
markdown-code-lint = "uv run python check_md_code_blocks.py 'README.md' './packages/**/README.md' './samples/**/*.md' --exclude cookiecutter-agent-framework-lab --exclude tau2 --exclude 'packages/devui/frontend'"
218-
docs-install = "uv sync --all-packages --all-extras --dev -U --prerelease=if-necessary-or-explicit --group=docs"
219-
docs-clean = "rm -rf docs/build"
220-
docs-build = "uv run python ./docs/generate_docs.py"
221-
docs-debug = "uv run python -m debugpy --listen 5678 ./docs/generate_docs.py"
222-
docs-rename = "mv docs/build/agent-framework-core docs/build/agent-framework"
223218
pre-commit-install = "uv run pre-commit install --install-hooks --overwrite"
224219
install = "uv sync --all-packages --all-extras --dev -U --prerelease=if-necessary-or-explicit --no-group=docs"
225220
test = "python run_tasks_in_packages_if_exists.py test"
@@ -239,21 +234,23 @@ build = ["build-packages", "build-meta"]
239234
publish = "uv publish"
240235
# combined checks
241236
check = ["fmt", "lint", "pyright", "mypy", "test", "markdown-code-lint"]
242-
pre-commit-check = ["fmt", "lint", "pyright", "markdown-code-lint"]
243237

244238
[tool.poe.tasks.all-tests-cov]
245239
cmd = """
246240
pytest --import-mode=importlib
247241
--cov=agent_framework
242+
--cov=agent_framework_core
248243
--cov=agent_framework_a2a
249244
--cov=agent_framework_ag_ui
245+
--cov=agent_framework_anthropic
250246
--cov=agent_framework_azure_ai
251247
--cov=agent_framework_azurefunctions
252248
--cov=agent_framework_chatkit
253249
--cov=agent_framework_copilotstudio
254250
--cov=agent_framework_mem0
255-
--cov=agent_framework_redis
256251
--cov=agent_framework_purview
252+
--cov=agent_framework_redis
253+
--cov-config=pyproject.toml
257254
--cov-report=term-missing:skip-covered
258255
--ignore-glob=packages/lab/**
259256
--ignore-glob=packages/devui/**
@@ -282,50 +279,31 @@ packages/azure-ai/tests
282279
[tool.poe.tasks.venv]
283280
cmd = "uv venv --clear --python $python"
284281
args = [{ name = "python", default = "3.13", options = ['-p', '--python'] }]
282+
285283
[tool.poe.tasks.setup]
286284
sequence = [
287285
{ ref = "venv --python $python"},
288286
{ ref = "install" },
289287
{ ref = "pre-commit-install" }
290288
]
291289
args = [{ name = "python", default = "3.13", options = ['-p', '--python'] }]
292-
[tool.poe.tasks.docs-full]
293-
sequence = [
294-
{ ref = "clean-dist" },
295-
{ ref = 'build' },
296-
{ ref = "docs-clean" },
297-
{ ref = "docs-build" },
298-
{ ref = "docs-rename" }
299-
]
300-
[tool.poe.tasks.docs-full-setup-install]
301-
sequence = [
302-
{ ref = "setup --python 3.11" },
303-
{ ref = "docs-install" },
304-
{ ref = 'build' },
305-
{ ref = "docs-clean" },
306-
{ ref = "docs-build" },
307-
{ ref = "docs-rename" }
308-
]
309-
[tool.poe.tasks.docs-full-install]
310-
sequence = [
311-
{ ref = "docs-install" },
312-
{ ref = 'build' },
313-
{ ref = "docs-clean" },
314-
{ ref = "docs-build" },
315-
{ ref = "docs-rename" }
316-
]
317-
[tool.poe.tasks.docs-rebuild]
318-
sequence = [
319-
{ ref = "docs-clean" },
320-
{ ref = "docs-build" },
321-
{ ref = "docs-rename" }
322-
]
323-
[tool.poe.tasks.docs-rebuild-debug]
290+
291+
[tool.poe.tasks.pre-commit-markdown-code-lint]
292+
cmd = "uv run python check_md_code_blocks.py ${files} --no-glob --exclude cookiecutter-agent-framework-lab --exclude tau2 --exclude 'packages/devui/frontend'"
293+
args = [{ name = "files", default = ".", positional = true, multiple = true }]
294+
295+
[tool.poe.tasks.pre-commit-pyright]
296+
cmd = "uv run python run_tasks_in_changed_packages.py pyright ${files}"
297+
args = [{ name = "files", default = ".", positional = true, multiple = true }]
298+
299+
[tool.poe.tasks.pre-commit-check]
324300
sequence = [
325-
{ ref = "docs-clean" },
326-
{ ref = "docs-debug" },
327-
{ ref = "docs-rename" }
301+
{ ref = "fmt" },
302+
{ ref = "lint" },
303+
{ ref = "pre-commit-pyright ${files}" },
304+
{ ref = "pre-commit-markdown-code-lint ${files}" }
328305
]
306+
args = [{ name = "files", default = ".", positional = true, multiple = true }]
329307

330308
[tool.setuptools.packages.find]
331309
where = ["packages"]
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
# Copyright (c) Microsoft. All rights reserved.
2+
3+
"""Run a task only in packages that have changed files."""
4+
5+
import argparse
6+
import glob
7+
import sys
8+
from pathlib import Path
9+
10+
import tomli
11+
from poethepoet.app import PoeThePoet
12+
from rich import print
13+
14+
15+
def discover_projects(workspace_pyproject_file: Path) -> list[Path]:
16+
with workspace_pyproject_file.open("rb") as f:
17+
data = tomli.load(f)
18+
19+
projects = data["tool"]["uv"]["workspace"]["members"]
20+
exclude = data["tool"]["uv"]["workspace"].get("exclude", [])
21+
22+
all_projects: list[Path] = []
23+
for project in projects:
24+
if "*" in project:
25+
globbed = glob.glob(str(project), root_dir=workspace_pyproject_file.parent)
26+
globbed_paths = [Path(p) for p in globbed]
27+
all_projects.extend(globbed_paths)
28+
else:
29+
all_projects.append(Path(project))
30+
31+
for project in exclude:
32+
if "*" in project:
33+
globbed = glob.glob(str(project), root_dir=workspace_pyproject_file.parent)
34+
globbed_paths = [Path(p) for p in globbed]
35+
all_projects = [p for p in all_projects if p not in globbed_paths]
36+
else:
37+
all_projects = [p for p in all_projects if p != Path(project)]
38+
39+
return all_projects
40+
41+
42+
def extract_poe_tasks(file: Path) -> set[str]:
43+
with file.open("rb") as f:
44+
data = tomli.load(f)
45+
46+
tasks = set(data.get("tool", {}).get("poe", {}).get("tasks", {}).keys())
47+
48+
# Check if there is an include too
49+
include: str | None = data.get("tool", {}).get("poe", {}).get("include", None)
50+
if include:
51+
include_file = file.parent / include
52+
if include_file.exists():
53+
tasks = tasks.union(extract_poe_tasks(include_file))
54+
55+
return tasks
56+
57+
58+
def get_changed_packages(projects: list[Path], changed_files: list[str], workspace_root: Path) -> set[Path]:
59+
"""Determine which packages have changed files."""
60+
changed_packages: set[Path] = set()
61+
core_package_changed = False
62+
63+
for file_path in changed_files:
64+
# Convert to absolute path if relative
65+
abs_path = Path(file_path)
66+
if not abs_path.is_absolute():
67+
abs_path = workspace_root / file_path
68+
69+
# Check which package this file belongs to
70+
for project in projects:
71+
project_abs = workspace_root / project
72+
try:
73+
# Check if the file is within this project directory
74+
abs_path.relative_to(project_abs)
75+
changed_packages.add(project)
76+
# Check if the core package was changed
77+
if project == Path("packages/core"):
78+
core_package_changed = True
79+
break
80+
except ValueError:
81+
# File is not in this project
82+
continue
83+
84+
# If core package changed, check all packages
85+
if core_package_changed:
86+
print("[yellow]Core package changed - checking all packages[/yellow]")
87+
return set(projects)
88+
89+
return changed_packages
90+
91+
92+
def main() -> None:
93+
parser = argparse.ArgumentParser(description="Run a task only in packages with changed files.")
94+
parser.add_argument("task", help="The task name to run")
95+
parser.add_argument("files", nargs="*", help="Changed files to determine which packages to run")
96+
args = parser.parse_args()
97+
98+
pyproject_file = Path(__file__).parent / "pyproject.toml"
99+
workspace_root = pyproject_file.parent
100+
projects = discover_projects(pyproject_file)
101+
102+
# If no files specified, run in all packages (default behavior)
103+
if not args.files or args.files == ["."]:
104+
print(f"[yellow]No specific files provided, running {args.task} in all packages[/yellow]")
105+
changed_packages = set(projects)
106+
else:
107+
changed_packages = get_changed_packages(projects, args.files, workspace_root)
108+
if changed_packages:
109+
print(f"[cyan]Detected changes in packages: {', '.join(str(p) for p in sorted(changed_packages))}[/cyan]")
110+
else:
111+
print(f"[yellow]No changes detected in any package, skipping {args.task}[/yellow]")
112+
return
113+
114+
# Run the task in changed packages
115+
for project in sorted(changed_packages):
116+
tasks = extract_poe_tasks(project / "pyproject.toml")
117+
if args.task in tasks:
118+
print(f"Running task {args.task} in {project}")
119+
app = PoeThePoet(cwd=project)
120+
result = app(cli_args=[args.task])
121+
if result:
122+
sys.exit(result)
123+
else:
124+
print(f"Task {args.task} not found in {project}")
125+
126+
127+
if __name__ == "__main__":
128+
main()

0 commit comments

Comments
 (0)