Skip to content

Copying and Moving Containers

The Flywheel SDK provides dedicated methods for copying containers at every level of the hierarchy, plus a bulk operation for moving sessions. This page explains the difference between copy and move, covers each copy method and its input model, describes how to filter what gets included in a copy, and shows a workaround for copying a single file.

See Core Concepts: Containers and Core Concepts: Hierarchy for background on the container model.

Copy vs. move

Copy creates a linked reference of the source container in the destination. The original remains intact and continues to exist in its source location. A copy does not duplicate the underlying file data — it only creates a new reference, so the copy does not consume additional storage space.

Note

After a copy is created, the source and the copy are independent. Modifying a file on the copy will increment that file's version on the copy only — it will not affect the original. Metadata (info, tags, notes) is copied at the time of the operation but is not synced afterward. Changes to metadata on the original will not appear on the copy, and vice versa.

Move relocates the source container to a new parent. The original is removed from its source location. Use move when reorganizing data — for example, reassigning sessions to different subjects after a labeling correction.

The SDK exposes these as separate operations:

  • Copy: fw.session_copy(), fw.subject_copy(), fw.acquisition_copy(), fw.project_copy()
  • Move: fw.bulk_move_sessions() (sessions only; bulk operation)

Copy methods

Every copy method takes two arguments: the ID of the container to copy, and a *CopyInput model that specifies the destination and optional new label.

All copy methods except fw.project_copy() are synchronous and return the new container directly. fw.project_copy() is asynchronous and returns a ProjectCopyOutput with a task_id you use to track progress.

No container can be copied unless the parent project is copyable. Copying can be enabled via the UI under Projects -> Settings -> Copying or the SDK:

Enable copying
fw.modify_project(project.id, {"copyable": True})

Copy a session

Copy a session to a destination project
new_session = fw.session_copy(
    session_id,
    flywheel.SessionCopyInput(
        dst_project_id=destination_project_id,
        dst_session_label="ses-copy",
        filter=flywheel.CopyFilter(),
    ),
)
print(new_session.id)

SessionCopyInput fields:

Field Type Description
dst_project_id str Destination project. The copy lands under a new or matched subject in this project.
dst_subject_id str Destination subject. Use this instead of dst_project_id to place the session under a specific subject.
dst_session_label str Label for the copied session. If omitted, the original label is used.
filter CopyFilter Controls what is excluded from the copy. See CopyFilter below.

Copy a subject

Copy a subject to a destination project
new_subject = fw.subject_copy(
    subject_id,
    flywheel.SubjectCopyInput(
        dst_project_id=destination_project_id,
        dst_subject_label="sub-copy",
        filter=flywheel.CopyFilter(),
    ),
)
print(new_subject.id)

SubjectCopyInput fields:

Field Type Description
dst_project_id str Destination project.
dst_subject_label str Label for the copied subject.
filter CopyFilter Controls what is excluded from the copy.

Copy an acquisition

Acquisitions copy into a destination session rather than a project.

Copy an acquisition to a destination session
new_acquisition = fw.acquisition_copy(
    acquisition_id,
    flywheel.AcquisitionCopyInput(
        dst_session_id=destination_session_id,
        label="acq-copy",
        filter=flywheel.CopyFilter(),
    ),
)
print(new_acquisition.id)

AcquisitionCopyInput fields:

Field Type Description
dst_session_id str Destination session.
label str Label for the copied acquisition.
filter CopyFilter Controls what is excluded from the copy.

Copy a project (asynchronous)

Project copy is asynchronous. The call returns immediately with a ProjectCopyOutput that contains a task_id. You must poll separately to determine when the copy has finished.

Copy a project asynchronously
output = fw.project_copy(
    project_id,
    flywheel.ProjectCopyInput(
        group_id=destination_group_id,
        project_label="proj-copy",
        filter=flywheel.CopyFilter(),
    ),
)
print(output.task_id)  # Use this to track progress
print(output.project_id)  # ID of the new project (may be populated before copy completes)

ProjectCopyInput fields:

Field Type Description
group_id str Destination group.
project_label str Label for the copied project.
snapshot_id str Optional. Copy from a specific snapshot rather than the live project.
filter CopyFilter Controls what is excluded from the copy.

ProjectCopyOutput fields:

Field Type Description
task_id str ID of the background task running the copy. Poll this to track progress.
project_id str ID of the newly created project.
snapshot_id str ID of the snapshot used for the copy, if applicable.

CopyFilter

Every *CopyInput model accepts an optional filter field of type CopyFilter. When omitted, the copy includes everything. Use CopyFilter to exclude specific content categories or to restrict which files are included using file name patterns.

Copy a session with a CopyFilter
copy_filter = flywheel.CopyFilter(
    exclude_analysis=True,
    exclude_tags=True,
    exclude_notes=False,
    exclude_empty_containers=True,  # default: True
)

new_session = fw.session_copy(
    session_id,
    flywheel.SessionCopyInput(
        dst_project_id=destination_project_id,
        dst_session_label="ses-copy-no-analyses",
        filter=copy_filter,
    ),
)

CopyFilter fields:

Field Type Default Description
exclude_analysis bool False When True, analyses are not copied.
exclude_notes bool False When True, notes are not copied.
exclude_tags bool False When True, tags are not copied.
exclude_empty_containers bool True When True, containers with no files are omitted from the copy.
include_rules list[str] None If set, only files whose names match one of these patterns are included.
exclude_rules list[str] None If set, files whose names match any of these patterns are excluded.

include_rules and exclude_rules accept file name glob patterns. When both are set, include_rules takes precedence — the SDK first applies include_rules to build the candidate set, then applies exclude_rules within that set.

Bulk move sessions

fw.bulk_move_sessions() moves multiple sessions in a single call. Pass a BulkMoveInput that identifies the source sessions and the destination subjects.

Bulk move sessions to a target subject
result = fw.bulk_move_sessions(
    flywheel.BulkMoveInput(
        destination_container_type="subjects",
        sources=[session_1.id, session_2.id],
        destinations=[destination_subject.id],
        conflict_mode="skip",
        remove_source=False,
    )
)

BulkMoveInput fields:

Field Type Description
sources list[str] Session IDs to move.
destinations list[str] Subject IDs to move sessions into.
destination_container_type str Must be "subjects" for session moves.
conflict_mode str How to handle label conflicts. See below.
remove_source bool True removes the source session after moving (default behavior). False leaves the source session in place.

conflict_mode values:

Value Behavior
"dry" Performs a dry run. Reports what would happen without making changes.
"skip" Skips sessions that would conflict with an existing session at the destination.
"merge" Merges the source session into the matching destination session.

When destinations contains a single subject ID, all source sessions are moved into that subject. When destinations contains multiple subject IDs, the number of destinations must match the number of sources — each session is paired with its corresponding destination.

Copying a single file

The SDK does not provide a direct file copy method. To copy one file from one acquisition to another:

  1. Copy the source acquisition with an include_rules filter that matches only the target file.
  2. The copy lands as a new temporary acquisition containing just that one file.
  3. Move the file from the temporary acquisition to the target acquisition using fw.move_file().
  4. Delete the temporary acquisition.
Copy a single file between acquisitions
# Step 1: copy the source acquisition, including only the target file
temp_acq = fw.acquisition_copy(
    source_acquisition_id,
    flywheel.AcquisitionCopyInput(
        dst_session_id=destination_session_id,
        label="__temp_file_copy__",
        filter=flywheel.CopyFilter(
            include_rules=[f"file.name={test_file}"],
            exclude_analysis=True,
        ),
    ),
)

# Step 2: wait for copy to complete; temp_acq.id may populate before the file is ready
for _ in range(30):
    temp_acq = fw.get_acquisition(temp_acq.id)
    if temp_acq.copy_status == "completed":
        break
    time.sleep(1)
else:
    raise TimeoutError("Acquisition copy did not complete.")

temp_acq = fw.get_acquisition(temp_acq.id).reload()

# Step 3: move the file from the temp acquisition to the target acquisition
file_id = temp_acq.files[0].file_id
fw.move_file(
    file_id=file_id,
    body={
        "container_reference": {
            "id": destination_acquisition_id,
            "type": "acquisition",
        },
        "name": None,
        "run_gear_rules": False,
    },
)

# Step 4: delete the temporary acquisition
fw.delete_acquisition(temp_acq.id)

This approach avoids a full round-trip download and re-upload for the file. For large collections of files, consider whether a full acquisition copy is more practical.