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:
Copy a session
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
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.
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.
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_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.
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:
- Copy the source acquisition with an
include_rulesfilter that matches only the target file. - The copy lands as a new temporary acquisition containing just that one file.
- Move the file from the temporary acquisition to the target acquisition using
fw.move_file(). - Delete the temporary acquisition.
# 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.