Finders
The Flywheel SDK exposes collections of containers (projects, subjects, sessions, acquisitions, analyses, jobs, files) as Finder objects. A Finder wraps the underlying paginated API and provides a consistent interface for filtering, sorting, and iterating over results — regardless of which collection you are querying.
You do not instantiate Finder directly.
# Client-level finders span the entire instance
fw.groups
fw.projects
fw.subjects
fw.sessions
fw.acquisitions
fw.analyses
fw.files
fw.jobs
fw.gears
# Container-level finders scope to that container's children
group.projects
project.subjects
project.sessions
subject.sessions
session.acquisitions
Note
container.files and container.analyses are not Finders — they are plain lists populated when the container was fetched. Use fw.files or fw.analyses (client-level) for filtered, paginated queries across the instance, or reload the container to refresh its .files and .analyses lists.
See Getting Started for client initialization and Core Concepts: Containers for the full container type reference.
Method Reference
The Finder interface exposes five methods. Choose based on how many results you expect and whether you want them all in memory at once.
| Method | Return type | Zero results | Multiple results | Notes |
|---|---|---|---|---|
find() | list | Empty list | All matches | Loads all matching results into memory |
find_first() | object or None | Returns None | Returns first match | Fetches at most 1 result from the API |
find_one() | object | Raises ValueError | Raises ValueError | Fetches at most 2 results to detect ambiguity |
iter() | iterator | Empty iterator | All results (lazy) | Pages through all items with no filter |
iter_find() | iterator | Empty iterator | All matches (lazy) | Pages through filtered results lazily |
find()
Returns a list of all matching objects. When no limit is specified, find() internally delegates to iter_find() and collects all results into a list. When limit is specified, it fetches that page and returns it directly.
# All projects (no limit — collects everything)
projects = fw.projects.find()
# All sessions created after a date
sessions = project.sessions.find("created>2023-01-01")
# First 100 sessions (explicit limit returns a single page)
sessions = project.sessions.find(limit=100)
find_first()
Returns the first matching object, or None if nothing matched. Always sends limit=1 to the API, making it the most efficient choice when you only need one result and absence is acceptable.
session = subject.sessions.find_first("label=baseline")
if session is None:
print("No baseline session found")
find_one()
Returns the single matching object. Raises ValueError if zero or more than one result matches. Internally requests limit=2 so it can detect the ambiguous case without fetching the entire result set.
Use find_one() when exactly one result is a correctness requirement — for instance, looking up a subject by a unique identifier.
try:
subject = project.subjects.find_one("label=sub-001")
except ValueError as e:
print(f"Lookup failed: {e}")
iter()
Iterates over every item in the collection without a filter. Pages through results lazily using cursor-based pagination (after_id), so it does not load everything into memory at once. Default page size is 250.
iter_find()
Iterates over all items matching a filter. When no sort is provided, it uses cursor-based pagination (after_id). When sort is provided, it falls back to page-number pagination. Default page size is 250.
for job in fw.jobs.iter_find("state=failed"):
print(f"Failed job: {job.id}")
Filter Syntax
Filter expressions are strings passed as the first positional argument to any finder method. Multiple conditions separated by commas are combined with AND logic.
Field references
Use dot notation to filter on nested fields.
# Top-level field
fw.projects.find("label=My Project")
# Nested field
fw.sessions.find("subject.label=sub-01")
# Deeply nested
fw.files.find("parents.project=abc123,parent_ref.type=acquisition")
Value types
The API automatically converts filter values to the appropriate type:
| Format | Type |
|---|---|
YYYY-MM-DD | Date |
YYYY-MM-DDTHH:mm:ss | Timestamp |
Numeric (e.g., 42, 15.7) | Number |
null | Null / missing |
Quoted string (e.g., "My Label") | String literal |
Wrap values in double quotes when they contain spaces or when you need to force string interpretation.
# String with spaces — quotes required
project = fw.projects.find_first('label="Anxiety Study"')
# Force string interpretation of a numeric-looking label
subject = project.subjects.find_first('label="123456"')
Operator reference
| Operator | Meaning | Example |
|---|---|---|
= | Equals | label=baseline |
!= | Not equals | label!=excluded |
< | Less than | age<65 |
<= | Less than or equal | age<=65 |
> | Greater than | created>2023-01-01 |
>= | Greater than or equal | created>=2023-01-01 |
=~ | Regex match | label=~.*_v2$ |
=| | In list | gear_info.name=|[dcm2niix,file-metadata-importer] |
!=| | Not in list | state!=|[failed,cancelled] |
Regex notes: The =~ operator does not support quoted values. Use unquoted patterns.
# Match acquisitions whose labels end in _v2
fw.acquisitions.find("label=~.*_v2$")
# Match DICOM files by extension
list(fw.acquisitions.iter_find("files.name=~.*\\.dcm"))
List operator syntax: Provide the values as a bracket-delimited, comma-separated list with no spaces.
# Jobs running either of two gears
list(fw.jobs.iter_find("gear_info.name=|[dcm2niix,file-metadata-importer]"))
Multiple conditions
Comma-separated conditions are ANDed together. There is no OR operator at the filter string level — use =| (in-list) for OR on a single field, or issue multiple queries.
# Sessions in one project that are labeled baseline and belong to male subjects
sessions = fw.sessions.find("project.label=MyProject,label=baseline,subject.sex=M")
# Subjects who are human and have completed a session after a date
subjects = project.subjects.find("type=human,modified>2023-06-01")
Sorting
Pass sort as a keyword argument. The format is fieldname:direction where direction is asc or desc. Separate multiple sort fields with commas.
# Most recently created sessions first
sessions = project.sessions.find("label=baseline", sort="created:desc")
# Subjects sorted by label ascending
subjects = project.subjects.find(sort="label:asc")
# Multiple sort fields
sessions = project.sessions.find(sort="subject.label:asc,created:desc")
Constraint: sort cannot be combined with after_id cursor-based pagination. If you pass both sort and after_id, the request will fail or produce undefined results. When iter_find() detects a sort argument, it automatically falls back to page-number pagination to avoid this conflict.
Pagination
Parameters
| Parameter | Type | Meaning |
|---|---|---|
limit | int | Maximum number of results per page (or total, when using find() with an explicit limit) |
page | int | 1-based page number for offset pagination |
skip | int | Number of results to skip before returning |
after_id | str | ID of the last item from the previous page (cursor pagination) |
Cursor pagination vs. offset pagination
Cursor pagination (after_id) is the default for iter() and iter_find() (when no sort is given). The client reads the last item's ID from each page and passes it as after_id to the next request. This approach is stable — if items are added or removed between pages, earlier pages are not shifted. The trade-off is that you cannot jump to an arbitrary page, and you cannot combine it with sort.
Offset pagination (page / skip) lets you jump to any page and is compatible with sort. The trade-off is that inserts and deletes during iteration can cause duplicates or skipped items.
limit = 200
page = 1
results = []
while True:
batch = project.sessions.find(limit=limit, page=page)
results.extend(batch)
if len(batch) < limit:
break
page += 1
iter_find() handles this loop automatically. Use manual pagination only when you need direct page access or when iter_find() does not fit your control flow.
Page size and throughput
The default page size for iter() and iter_find() is 250. For bulk export or large data migrations, increasing limit to 400–800 typically gives better throughput by reducing the number of round trips. Values above 1 000 are likely to cause server-side timeouts or oversized responses.
for session in project.sessions.iter_find("label!=test", limit=500):
process(session)
Memory vs. Lazy Iteration
find() with no limit collects all matching results into a Python list before returning. For small collections this is convenient. For large collections (tens of thousands of items), it consumes significant memory.
iter_find() yields one item at a time and fetches the next page only when the current page is exhausted. Use it whenever the result set might be large or when you want to process items as they arrive.
# All sessions in memory — fine for small projects
sessions = project.sessions.find("label=baseline")
print(f"Found {len(sessions)} sessions")
# Lazy iteration — works at any scale
for session in project.sessions.iter_find("label=baseline"):
process(session)
Practical Patterns
Get or create a container
def get_or_create_project(fw, group, label):
existing = fw.projects.find_first(f"label={label},group={group}")
if existing:
return existing
new_id = fw.add_project({"label": label, "group": group})
return fw.get_project(new_id)
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})
Filter on info fields
Custom metadata stored in container .info is queryable using dot notation.
# Sessions where a custom info field is set to a specific value
sessions = project.sessions.find("info.protocol_version=2")
Null checks
Use null to find containers with missing or unset fields.
# Subjects with no sex recorded
subjects = project.subjects.find("sex=null")
# Sessions where analysis parent is absent
files = fw.files.find("parents.analysis=null,parent_ref.type=analysis")
Combining date ranges
Multiple conditions on the same field are legal and combine with AND.
# Sessions created in a specific window
sessions = project.sessions.find("created>=2023-01-01,created<2024-01-01")