Skip to content

[PLT-4090] Add batch_ids filter to project.get_overview() - #2060

Merged
maciejtatol merged 2 commits into
developfrom
mtatol/PLT-4090-batch-overview
Jul 9, 2026
Merged

[PLT-4090] Add batch_ids filter to project.get_overview()#2060
maciejtatol merged 2 commits into
developfrom
mtatol/PLT-4090-batch-overview

Conversation

@maciejtatol

@maciejtatolmaciejtatol commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Ticket: LINK

Description

Problem

Customers such as Pinterest need to poll batch completion status programmatically (To Label / In Review / In Rework / Done counts per batch). Today the only option is running multiple project exports with batch_ids + workflow_status filters - each export takes minutes. For hourly polling across many batches, this is expensive and slow.

Solution

Extend Project.get_overview() with an optional batch_ids parameter that returns the same workflow-state counts as the project Overview tab, scoped to one or more batches - without exporting label data.

overview=project.get_overview(batch_ids=[batch.uid])
print(overview.to_label, overview.in_review, overview.in_rework, overview.done)
print(overview.total_data_rows)
# Multiple batches → combined counts (not per-batch breakdown)overview=project.get_overview(batch_ids=[batch_a.uid, batch_b.uid])
# Per-queue breakdown, also batch-scopedoverview=project.get_overview(batch_ids=[batch.uid], details=True)

Query: ProjectGetOverviewPyApi (still uses experimental=True / /_gql for issues on project-wide calls)

Validation:

batch_ids must be a non-empty list (empty list raises ValueError - backend treats [] as no filter, which would silently return project-wide counts)
Max 1000 batch IDs per call
Return type change: ProjectOverview.issues and ProjectOverviewDetailed.issues are now Optional[int] = None (always populated for project-wide calls; None when batch-scoped)

Test on Local

Project:

  • 2 batches:
  • 1 (the one that is requested): 3 total, 1 in review, 1 in rework, 1 in done
  • 2: 4 total, all in To Label

UI view:

image

Test script:

#!/usr/bin/env python3"""Manual smoke test for Project.get_overview(batch_ids=...) against local lb-api.Prerequisites: - intelligence API running on http://localhost:8080 (pm2 start api) - Docker infra up (yarn dev:up) for MySQL + Elasticsearch - LABELBOX_API_KEY exported (create one in local app: http://localhost:3333)Usage: export LABELBOX_API_KEY="your-local-key" python scripts/test_batch_overview_local.pyOptional: python scripts/test_batch_overview_local.py --project-id <id> --batch-id <id>"""from __future__ importannotationsimportargparseimportosimportsysimporttimeimportuuidimportlabelboxaslbfromlabelbox.schema.media_typeimportMediaTypeLOCAL_GRAPHQL="http://localhost:8080/graphql"LOCAL_REST="http://localhost:8080/api/v1"TEST_IMAGE= (
"https://storage.googleapis.com/lb-artifacts-testing-public/""sdk_integration_test/potato.jpeg"
)
POLL_TIMEOUT_SECONDS=120POLL_INTERVAL_SECONDS=3def_client() ->lb.Client:
api_key=os.environ.get("LABELBOX_API_KEY")
ifnotapi_key:
print(
"ERROR: Set LABELBOX_API_KEY (create one in http://localhost:3333)",
file=sys.stderr,
)
sys.exit(1)
returnlb.Client(
api_key=api_key,
endpoint=LOCAL_GRAPHQL,
rest_endpoint=LOCAL_REST,
)
def_wait_for_batch_counts(project, batch_a_id: str, batch_b_id: str) ->None:
deadline=time.time() +POLL_TIMEOUT_SECONDSwhiletime.time() <deadline:
overview_a=project.get_overview(batch_ids=[batch_a_id])
overview_b=project.get_overview(batch_ids=[batch_b_id])
if (
overview_a.total_data_rows==1andoverview_b.total_data_rows==1
):
returnprint(
f" waiting for ES indexing... "f"batch_a={overview_a.total_data_rows}, "f"batch_b={overview_b.total_data_rows}"
)
time.sleep(POLL_INTERVAL_SECONDS)
raiseTimeoutError(
"Timed out waiting for batch-scoped overview counts "f"(>{POLL_TIMEOUT_SECONDS}s). Is catalog ES indexing healthy?"
)
def_run_existing(project_id: str, batch_id: str) ->None:
client=_client()
project=client.get_project(project_id)
print(f"Project: {project.uid} ({project.name})")
project_overview=project.get_overview()
batch_overview=project.get_overview(batch_ids=[batch_id])
print("\nProject-wide overview:")
print(f" total_data_rows={project_overview.total_data_rows}")
print(f" to_label={project_overview.to_label}")
print(f" in_review={project_overview.in_review}")
print(f" in_rework={project_overview.in_rework}")
print(f" done={project_overview.done}")
print(f" issues={project_overview.issues}")
print("\nBatch-scoped overview:")
print(f" total_data_rows={batch_overview.total_data_rows}")
print(f" to_label={batch_overview.to_label}")
print(f" in_review={batch_overview.in_review}")
print(f" in_rework={batch_overview.in_rework}")
print(f" done={batch_overview.done}")
print(f" issues={batch_overview.issues} (expected None)")
ifbatch_overview.issuesisnotNone:
raiseAssertionError("Expected issues=None for batch-scoped overview")
def_run_full_smoke() ->None:
client=_client()
suffix=uuid.uuid4().hex[:8]
project=client.create_project(
name=f"batch-overview-local-{suffix}",
media_type=MediaType.Image,
)
dataset=client.create_dataset(name=f"batch-overview-dataset-{suffix}")
print(f"Created project {project.uid}")
print(f"Created dataset {dataset.uid}")
try:
task=dataset.create_data_rows(
[
{"row_data": TEST_IMAGE, "external_id": f"dr-a-{suffix}"},
{"row_data": TEST_IMAGE, "external_id": f"dr-b-{suffix}"},
]
)
task.wait_till_done()
iftask.errors:
raiseRuntimeError(f"Data row creation errors: {task.errors}")
export_task=dataset.export()
export_task.wait_till_done()
data_row_ids= [
row.json["data_row"]["id"]
forrowinexport_task.get_buffered_stream()
]
iflen(data_row_ids) <2:
raiseRuntimeError(
f"Expected 2 data rows, got {len(data_row_ids)}"
)
batch_a=project.create_batch(f"batch-a-{suffix}", [data_row_ids[0]])
batch_b=project.create_batch(f"batch-b-{suffix}", [data_row_ids[1]])
print(f"Created batches: {batch_a.uid}, {batch_b.uid}")
_wait_for_batch_counts(project, batch_a.uid, batch_b.uid)
overview_a=project.get_overview(batch_ids=[batch_a.uid])
overview_b=project.get_overview(batch_ids=[batch_b.uid])
combined=project.get_overview(batch_ids=[batch_a.uid, batch_b.uid])
project_overview=project.get_overview()
print("\nBatch A:")
print(f" total_data_rows={overview_a.total_data_rows}, issues={overview_a.issues}")
print("Batch B:")
print(f" total_data_rows={overview_b.total_data_rows}, issues={overview_b.issues}")
print("Combined:")
print(f" total_data_rows={combined.total_data_rows}, issues={combined.issues}")
print("Project-wide:")
print(f" total_data_rows={project_overview.total_data_rows}, issues={project_overview.issues}")
assertoverview_a.total_data_rows==1assertoverview_b.total_data_rows==1assertcombined.total_data_rows==2assertoverview_a.issuesisNoneassertoverview_b.issuesisNoneassertcombined.issuesisNoneassertproject_overview.issuesisnotNoneprint("\nSUCCESS: batch-scoped get_overview() works against local lb-api.")
finally:
print("\nCleaning up...")
project.delete()
dataset.delete()
defmain() ->None:
parser=argparse.ArgumentParser(
description="Smoke test Project.get_overview(batch_ids=...) locally"
)
parser.add_argument("--project-id", help="Existing project ID (skip setup)")
parser.add_argument("--batch-id", help="Existing batch ID (requires --project-id)")
args=parser.parse_args()
ifargs.project_idorargs.batch_id:
ifnotargs.project_idornotargs.batch_id:
parser.error("--project-id and --batch-id must be used together")
_run_existing(args.project_id, args.batch_id)
print("\nSUCCESS")
return_run_full_smoke()
if__name__=="__main__":
main()

Execute script:

image

Type of change

Please delete options that are not relevant.

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Document change (fix typo or modifying any markdown files, code comments or anything in the examples folder only)

All Submissions

  • Have you followed the guidelines in our Contributing document?
  • Have you provided a description?
  • Are your changes properly formatted?

New Feature Submissions

  • Does your submission pass tests?
  • Have you added thorough tests for your new feature?
  • Have you commented your code, particularly in hard-to-understand areas?
  • Have you added a Docstring?

Changes to Core Features

  • Have you written new tests for your core changes, as applicable?
  • Have you successfully run tests with your changes locally?
  • Have you updated any code comments, as applicable?

Note

Cursor Bugbot is generating a summary for commit 77cc539. Configure here.

Maciej Tatoland others added 2 commits July 9, 2026 16:03
Extend Project.get_overview() with an optional batch_ids parameter that
scopes workflow state counts to one or more batches via the existing
workstreamStateCounts(batchIds) GraphQL field. Batch-scoped calls omit
issues (not batch-filterable) and pass a batch search query into
taskQueues.dataRowCount for details=True queue breakdowns.
https: //labelbox.atlassian.net/browse/PLT-4090
Co-authored-by: Cursor <cursoragent@cursor.com>
pass


_MAX_BATCH_IDS = 1000

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a similar server-side enforcement?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll check it, good call-out

@maciejtatol
maciejtatol merged commit 9dad695 into developJul 9, 2026
15 of 45 checks passed
@maciejtatol
maciejtatol deleted the mtatol/PLT-4090-batch-overview branch July 9, 2026 20:35
@cursorcursorBot mentioned this pull request Jul 9, 2026
4 tasks
mrobers1982 added a commit that referenced this pull request Jul 9, 2026
project.py, test_batch.py, and test_project.py (last touched by #2060) were
not formatted per the repo's ruff config, failing `rye fmt --check` on every
PR. Formatted with ruff 0.8.2 (the version bundled by CI's rye 0.43.0).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@maciejtatol@mrobers1982