Skip to content

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.

FileSpec from a file path
spec = flywheel.FileSpec("/tmp/report.csv")

Upload string or bytes content — pass name (the destination filename), contents (a string or bytes object), and optionally content_type (MIME type).

FileSpec from string content
spec = flywheel.FileSpec("hello.txt", "Hello World!\n", "text/plain")

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.

FileSpec from a StringIO object
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 a file to a container
# 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 using client-level methods
# 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.

Upload string content to a project
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:

Upload binary content to an acquisition
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.

Upload to any container by ID
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.

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.

Download a file from a container
project.download_file("report.csv", 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 using client-level methods
# 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.

Download from any container by 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.

Read a file into memory and parse it
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:

Download to memory using client-level methods
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.

Get a signed download URL
# 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.

List members of a zip file
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.

Download a single zip member to disk
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.

Read a zip member into memory
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.

Skip upload if file already exists
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.