Skip to content

Commit 9529fbe

Browse files
committed
task: add --only-matching prefilter
Bump version to 0.6.7.
1 parent 1a26dc2 commit 9529fbe

5 files changed

Lines changed: 56 additions & 3 deletions

File tree

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,11 @@ Take tasks from a GitHub Project (requires `gh-task`):
7979
```bash
8080
codexapi task -p owner/projects/3 -n "Your Name" -s Ready task_a.yaml task_b.yaml
8181
```
82+
Filter project issues by title before taking them:
83+
84+
```bash
85+
codexapi task -p owner/projects/3 -n "Your Name" --only-matching "/n300/" task_a.yaml task_b.yaml
86+
```
8287
Reset owned tasks on a GitHub Project back to Ready:
8388

8489
```bash

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "codexapi"
7-
version = "0.6.6"
7+
version = "0.6.7"
88
description = "Minimal Python API for running the Codex CLI."
99
readme = "README.md"
1010
requires-python = ">=3.8"

src/codexapi/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,4 @@
2727
"task_result",
2828
"watch",
2929
]
30-
__version__ = "0.6.6"
30+
__version__ = "0.6.7"

src/codexapi/cli.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1106,6 +1106,13 @@ def main(argv=None):
11061106
"--name",
11071107
help="Owner label name for gh-task when using --project.",
11081108
)
1109+
task_parser.add_argument(
1110+
"--only-matching",
1111+
help=(
1112+
"When using --project, only take issues whose title matches this regex. "
1113+
"Useful for filtering tasks by hardware encoded in the issue title/path."
1114+
),
1115+
)
11091116
task_parser.add_argument(
11101117
"task_args",
11111118
nargs="*",
@@ -1419,6 +1426,11 @@ def main(argv=None):
14191426
raise SystemExit("--name is required with --project.")
14201427
if not args.task_args:
14211428
raise SystemExit("task --project requires one or more task files.")
1429+
if args.only_matching is not None:
1430+
try:
1431+
re.compile(args.only_matching)
1432+
except re.error as exc:
1433+
raise SystemExit(f"--only-matching regex is invalid: {exc}") from None
14221434
from .gh_integration import GhTaskRunner, project_url
14231435
from gh_task.errors import TakeError
14241436

@@ -1430,6 +1442,7 @@ def main(argv=None):
14301442
args.name,
14311443
args.task_args,
14321444
args.status,
1445+
args.only_matching,
14331446
args.cwd,
14341447
args.yolo,
14351448
args.flags,
@@ -1457,6 +1470,7 @@ def main(argv=None):
14571470
args.name,
14581471
args.task_args,
14591472
args.status,
1473+
args.only_matching,
14601474
args.cwd,
14611475
args.yolo,
14621476
args.flags,
@@ -1482,6 +1496,8 @@ def main(argv=None):
14821496
raise SystemExit(
14831497
"task -f --item requires {{item}} in the task file."
14841498
)
1499+
if args.only_matching is not None:
1500+
raise SystemExit("--only-matching is only supported with --project.")
14851501
if args.check is not None:
14861502
raise SystemExit("--check is not allowed with -f.")
14871503
if args.max_iterations is not None:
@@ -1553,6 +1569,8 @@ def main(argv=None):
15531569
raise SystemExit("--loop is only supported with -p.")
15541570
if args.item is not None:
15551571
raise SystemExit("--item is only supported with -f.")
1572+
if args.only_matching is not None:
1573+
raise SystemExit("--only-matching is only supported with --project.")
15561574
if args.max_iterations is None:
15571575
args.max_iterations = DEFAULT_MAX_ITERATIONS
15581576
if args.max_iterations < 0:

src/codexapi/gh_integration.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
from tqdm import tqdm
77

8+
from gh_task.errors import TakeError
89
from gh_task.project import Project, UPDATE_STATUS_MUTATION
910

1011
from .taskfile import TaskFile
@@ -138,6 +139,34 @@ def _match_task_file(issue, task_map):
138139
return matches[0][1]
139140

140141

142+
def _take_matching_issue(project, status, only_matching):
143+
"""Take the first available issue whose title matches only_matching.
144+
145+
only_matching is a regular expression. When unset/empty, this behaves like
146+
Project.take(status=...).
147+
"""
148+
if not only_matching:
149+
return project.take(status=status, return_issue=True)
150+
try:
151+
pattern = re.compile(only_matching)
152+
except re.error as exc:
153+
raise ValueError(f"Invalid only-matching regex {only_matching!r}: {exc}") from exc
154+
status_name = project._resolve_status_name(status)
155+
# Filter by title before fetching labels so we don't spam GitHub REST calls for
156+
# obviously unsupported issues.
157+
for issue in project._list_items():
158+
if (issue.status or "").lower() != status_name.lower():
159+
continue
160+
title = issue.title or ""
161+
if not pattern.search(title):
162+
continue
163+
if not project._issue_matches_label(issue):
164+
continue
165+
if project._try_take(issue, wait_seconds=1.0, strict=False):
166+
return issue
167+
raise TakeError(f"No available issues to take in status '{status_name}' matching {only_matching!r}")
168+
169+
141170
def _strip_progress_section(body):
142171
if not body:
143172
return ""
@@ -275,13 +304,14 @@ def __init__(
275304
name,
276305
task_files,
277306
status="Ready",
307+
only_matching=None,
278308
cwd=None,
279309
yolo=True,
280310
flags=None,
281311
):
282312
task_map = _task_file_map(task_files)
283313
self.project = Project(project, name, has_label=list(task_map))
284-
self.issue = self.project.take(status=status, return_issue=True)
314+
self.issue = _take_matching_issue(self.project, status, only_matching)
285315
self.issue = self.project.get_issue(self.issue)
286316
try:
287317
task_path = _match_task_file(self.issue, task_map)

0 commit comments

Comments
 (0)