Skip to content

Bulk Container Creation

The SDK creates containers one at a time with the add_* methods documented in Core Concepts: Containers. There is no batch-create endpoint — creating many containers means looping those single-container calls. This page covers the structure that makes such a loop safe to re-run and recover from: idempotent creation and per-item error handling.

There is no all-or-nothing creation

The SDK cannot create a group of containers as a single unit that either fully succeeds or fully fails. Each add_* call is committed on its own. If your script fails partway through, the containers created before the failure remain in place. Plan for partial success: make the operation idempotent so that re-running it finds the containers already created and only fills in the rest.

Idempotent creation

The single most useful pattern for bulk creation is get-or-create: look for the container first, and only create it if it is missing. This makes the whole script safe to re-run after a failure — already-created containers are found and skipped rather than duplicated.

Get-or-create a subject
def get_or_create_subject(project, label):
    existing = project.subjects.find_first(f"label={label}")
    if existing:
        return existing
    return project.add_subject({"label": label})

Drive it from your list of labels:

Create a set of subjects idempotently
created = []
for label in subject_labels:
    subject = get_or_create_subject(project, label)
    created.append(subject)

Running this script a second time creates nothing new — each find_first() matches the subject created on the first run. This is the SDK's substitute for the "transaction-like" guarantees the relational world provides: instead of all-or-nothing, you get safe-to-repeat.

Tip

For the underlying find_first / add_* mechanics and a get_or_create_project variant, see Core Concepts: Finders. This page focuses only on applying that pattern at scale.

Collecting errors instead of stopping

A simple loop aborts on the first failure, leaving the rest of the batch untouched and the exception unhandled. Bulk work usually calls for the opposite behavior: attempt every item, record what failed, and report at the end. Catch flywheel.ApiException per iteration and accumulate failures.

Continue past failures and report them
created = []
failures = []
for label in subject_labels:
    try:
        subject = project.add_subject({"label": label})
        created.append(subject)
    except flywheel.ApiException as exc:
        failures.append({"label": label, "status": exc.status, "reason": exc.reason})

print(f"Created {len(created)} subjects, {len(failures)} failed")
for failure in failures:
    print(f"  {failure['label']!r}: {failure['status']} {failure['reason']}")

flywheel.ApiException exposes .status (the HTTP status code) and .reason, which are enough to triage most bulk failures:

Status Typical cause in a creation loop
400 Invalid payload — missing or malformed required field (e.g. empty label)
403 No write permission on the parent container
404 Parent container ID does not exist
409 A conflicting container already exists

For the get-or-create pattern, a 409 conflict usually means the container already exists — which is exactly the case idempotent creation is designed to avoid hitting in the first place.