File Upload and Download
The Flywheel SDK provides methods for uploading files to containers and downloading files from them. This document covers single-file transfers using the SDK. For bulk transfers — entire sessions, projects, or batch exports — use the Flywheel CLI (fw import, fw export, fw download), which is significantly faster for large volumes of data. For more information on bulk data transfer, see the inbound data transfer documentation and outbound data transfer documentation.
See also: Core Concepts: Containers, Core Concepts: Hierarchy, and Core Concepts: Metadata
FileSpec — the upload wrapper
flywheel.FileSpec wraps the content you want to upload. The SDK accepts three forms:
Upload from disk — pass the file path as name. The SDK opens and reads the file, and derives the filename from the path.
Upload string or bytes content — pass name (the destination filename), contents (a string or bytes object), and optionally content_type (MIME type).
Upload a file-like object — pass a StringIO or BytesIO as contents, and always provide size in bytes. Without size the upload will fail because the SDK cannot determine the size of a stream.
import io
content = "subject_id,score\nsub-01,42\n"
spec = flywheel.FileSpec("scores.csv", io.StringIO(content), size=len(content))
When content_type is not specified, the SDK infers it from the filename extension. When it cannot be inferred, it falls back to application/octet-stream.
Uploading files
Container-level upload
Every container (project, subject, session, acquisition) exposes upload_file(). Pass a local file path or a FileSpec object.
# Upload from a local path
project.upload_file("/tmp/hello.txt")
# Upload with explicit FileSpec (name, content, mimetype)
project.upload_file(flywheel.FileSpec("hello.txt", "Hello World!\n", "text/plain"))
This is the most concise form and works identically across all container types. The method delegates to the appropriate container-specific API call internally.
Client-level upload methods
The fw client exposes container-specific upload methods. These require the container ID and a FileSpec.
# Upload to a project
fw.upload_file_to_project(project.id, flywheel.FileSpec("report.csv", "col1,col2\n1,2\n"))
# Upload to an acquisition
fw.upload_file_to_acquisition(acquisition.id, flywheel.FileSpec("scan.nii.gz", b"\x00\x01"))
# Upload to a session or subject
fw.upload_file_to_session(session.id, flywheel.FileSpec("notes.txt", "session notes"))
fw.upload_file_to_subject(subject.id, flywheel.FileSpec("consent.pdf", "consent text"))
The full set of container-specific upload methods:
| Method | Container |
|---|---|
fw.upload_file_to_project(id, file) | Project |
fw.upload_file_to_subject(id, file) | Subject |
fw.upload_file_to_session(id, file) | Session |
fw.upload_file_to_acquisition(id, file) | Acquisition |
Uploading in-memory content
Use StringIO or BytesIO with FileSpec when you have content in memory rather than on disk. Always pass size.
import io
content = "subject_id,score\nsub-01,42\n"
fw.upload_file_to_container(
project.id,
flywheel.FileSpec("scores.csv", io.StringIO(content), size=len(content)),
)
For binary content, use BytesIO and compute size from the bytes length:
import io
data = b"\x00\x01\x02\x03"
fw.upload_file_to_container(
acquisition.id,
flywheel.FileSpec("data.bin", io.BytesIO(data), size=len(data)),
)
Generic upload method
fw.upload_file_to_container() works with any container ID, regardless of container type. It is useful when you have a container ID from a reference and do not know the type at call time.
fw.upload_file_to_container(container.id, flywheel.FileSpec("results.json"))
Signed URL uploads
All upload methods accept a signed parameter (default True). When signed=True, the SDK attempts to upload via a signed URL, which allows direct transfer to object storage without routing through the Flywheel API server. This is faster for large files and reduces server load. Pass signed=False to force a direct API upload.
fw.upload_file_to_acquisition(
acquisition.id, flywheel.FileSpec("scan.nii.gz"), signed=False
)
In most cases the default (signed=True) is what you want.
Downloading files to disk
Container-level download
Use container.download_file(filename, dest_path) to download a named file to a local path.
This works on any container type (project, subject, session, acquisition).
Client-level download methods
The fw client provides container-specific download methods that take the container ID, the filename on Flywheel, and the local destination path.
# Download from an acquisition
fw.download_file_from_acquisition(acquisition.id, "scan.nii.gz", acq_download_path)
# Download from a project
fw.download_file_from_project(project.id, "report.csv", project_download_path)
# Download from a session or subject
fw.download_file_from_session(session.id, "notes.txt", session_download_path)
fw.download_file_from_subject(subject.id, "consent.pdf", subject_download_path)
The full set of container-specific download methods:
| Method | Container |
|---|---|
fw.download_file_from_project(id, filename, dest) | Project |
fw.download_file_from_subject(id, filename, dest) | Subject |
fw.download_file_from_session(id, filename, dest) | Session |
fw.download_file_from_acquisition(id, filename, dest) | Acquisition |
Generic download method
fw.download_file_from_container() works with any container ID.
fw.download_file_from_container(container.id, "results.json", "/tmp/results.json")
Downloading to memory
Use container.read_file(filename) to download file contents directly to memory as bytes. This avoids writing a temporary file to disk, which is useful for small files you need to process immediately.
import csv, io
data = project.read_file("report.csv")
reader = csv.reader(io.StringIO(data.decode("utf-8")))
for row in reader:
print(row)
The client-level *_as_data() variants do the same thing:
data = fw.download_file_from_project_as_data(project.id, "report.csv")
data = fw.download_file_from_acquisition_as_data(acquisition.id, "scan.nii.gz")
data = fw.download_file_from_session_as_data(session.id, "notes.txt")
data = fw.download_file_from_subject_as_data(subject.id, "consent.pdf")
All of these return bytes. Do not use these for large files (multi-gigabyte imaging data, for example) — download to disk instead.
Signed download URLs
To get a time-limited signed URL for a file — for example, to hand to another service or open in a browser — use the container-level get_file_download_url() or the client-level get_*_download_url() methods.
# Container-level
url = project.get_file_download_url("report.csv")
# Client-level, container-specific
url = fw.get_project_download_url(project.id, "report.csv")
url = fw.get_acquisition_download_url(acquisition.id, "scan.nii.gz")
url = fw.get_session_download_url(session.id, "notes.txt")
url = fw.get_subject_download_url(subject.id, "consent.pdf")
# Generic
url = fw.get_container_download_url(project.id, "report.csv")
The returned URL includes a ticket query parameter and is valid for a limited time. It can be passed to any HTTP client.
Zip file members
The SDK provides methods to inspect and extract individual members from zip files stored in Flywheel, without downloading the entire archive.
List zip contents — returns a zip info object whose members list contains each entry's path and metadata.
zip_info = acquisition.get_file_zip_info("archive.zip")
for member in zip_info.members:
print(member.path)
Download a single member to disk — specify the zip filename, the member path within the archive, and a local destination.
import os
zip_info = acquisition.get_file_zip_info("archive.zip")
entry_name = zip_info.members[0].path
out_path = os.path.join(tmpdir, entry_name)
acquisition.download_file_zip_member("archive.zip", entry_name, out_path)
Read a single member to memory — returns the member contents as bytes.
readme = acquisition.read_file_zip_member("archive.zip", "readme.txt")
print(readme.decode("utf-8"))
These methods are available on any container that has the file (acquisition, session, project, etc.).
Comparing files with hashes
File entries in Flywheel expose a hash attribute. You can compare hashes to determine whether a file already exists on a destination container before uploading, avoiding redundant transfers.
source_file = source_acquisition.get_file("scan.nii.gz")
dest_file = dest_acquisition.get_file("scan.nii.gz")
if dest_file and dest_file.hash == source_file.hash:
print("Files are identical — skipping upload")
else:
dest_acquisition.upload_file(flywheel.FileSpec("scan.nii.gz", b"\x00\x01\x02\x03"))
Note that get_file() returns None if no file with that name exists on the container, so guard the comparison accordingly.