Using the returned Collection objects from `operations.receive`

Hi Specklers,

Please bear with a rusty python coder :slight_smile:

I am trying to retrieve model data from the latest version of a project using the new Project, Model, Version API. I have successfully found the model version I want but when trying to grab the BIM data using operations.receive I am getting stuck with what to do with the returned Collections object. I can see that the ‘raw’ collection has 2161 children but then I am not sure what to do with this info, i.e. how can I iterate through all these children?

Here is the function I am using:

def fetch_latest_version_data(client, model_id, project_id, transport):
    # Fetch version for the stream
    version = client.version.get_versions(model_id, project_id)
    if not version:
        raise ValueError("No versions found for the version ID")
   
    print(f"Attemptin to fetch data from version: {version.items[0].message}")

    try:
        obj_data = operations.receive(version.items[0].referencedObject, transport)
    
    except Exception as e:
        print(f"Failed to fetch data from version: {version.items[0].id} with error: {e}")
   
    return obj_data

TL;DR: The Speckle Collection class is a structured container for BIM objects. You can:

  • Traverse it recursively to inspect structure.
  • Extract everything at once (flatten it).
  • Use a generator (preferred) to iterate efficiently.

When you use operations.receive(), you often return a Collection, a special Speckle class that acts like a container for multiple objects. Think of it as a structured list of elements, each of which can be another Collection or a specific BIM object (like walls, doors, etc.).

Since a Collection holds nested data, you can handle it in two main ways:

Traversing the Collection (Recursive Approach)

If you want to walk through the structure and inspect each object, including any nested Collections:

def traverse_collection(collection):
    """Recursively traverses a Collection and prints object types"""
    print(f"Traversing collection with {len(collection.["elements"])} children")

    for element in collection["elements"]:
        print(f"Child object type: {type(element)}")
        
        if isinstance(child, Collection):  # If nested, keep going
            traverse_collection(element)
        else:
            print(f"Object properties: {element.__dict__}")  # Inspect properties

Flattening the Collection (Extract All Objects at Once)

If you’re mainly interested in getting all objects in one list, regardless of hierarchy:

def flatten_collection(collection):
    """Returns a flat list of all objects within a Collection"""
    objects = []

    def collect_items(coll):
        for child in coll["elements"]:
            if isinstance(element, Collection):
                collect_items(element)  # Dive deeper
            else:
                objects.append(element)  # Store object

    collect_items(collection)
    return objects

Then use this as so:

all_objects = flatten_collection(obj_data)
print(f"Total extracted objects: {len(all_objects)}")

As you are a rusty Pythonista, that should suffice, but my preferred pattern for the flatten function is to work with a Generator

Instead of collecting all objects in a list, we can yield each object on demand:

def yield_collection_items(collection):
    """Generator that lazily iterates through all objects in a Collection"""
    for element in collection["elements"]:
        if isinstance(element, Collection):
            yield from yield_collection_items(element)  # Recursively yield deeper items
        else:
            yield element # Yield the object itself

and then

for obj in yield_collection_items(obj_data):
    print(f"Found object: {type(obj)}, Properties: {obj.__dict__}")

Like C#'s LINQ, generators delay execution until needed, but they’re one-time-use. Convert to a list if you need to reuse the results: list(generator)… Similar to LINQ, this allows you not to store thousands of objects in a list, and if you are looking for something specific, you can exit the process when you find it.

We introduced Collections a while back as a change to how Revit data was stored in Speckle: BREAKING CHANGE: Introducing Collections Class for Predictable Data Hierarchies: Testing the Revit Connector .

A typical Speckle use case is querying Revit data. Since Revit models are stored as Collections, these patterns make filtering elements like walls, doors, and windows incredibly efficient.

If you need to extract all walls from a Revit model,

def yield_revit_elements(collection, category=None):
    """Generator to yield elements of a specific Revit category."""
    for element in collection["elements"]:
        if isinstance(element, Collection):
            yield from yield_revit_elements(element, category)  # Dive into nested collections
        elif category is None or getattr(element, "speckle_type", "").endswith(category):
            yield element # Yield only objects of the specified category

# Example: Extracting all "Wall" objects
walls = yield_revit_elements(obj_data, category="Revit.Wall")

for wall in walls:
    print(f"Wall ID: {wall.id}, Level: {wall.level}")

This stupid-simple example obviously can work with Walls, Doors, Windows, etc. but the pattern stops once you find what you need.

When to Use What?

Use Case Best Approach
Querying specific elements dynamically Generator (yield_revit_elements)
Processing all elements at once Flat list (flatten_revit_elements)
Exploring nested structure Recursive traversal
1 Like

Thanks for this info - I tried the flatten_collection method but receive an attribute error:

Exception has occurred: AttributeError
'Collection' object has no attribute 'children'

Am i missing something obvious here? :slight_smile:

Dammit sorry @NigeDemo elements - i was writing direct on the forum from memory.

No worries @jonathon - really appreciate your comprehensive answer. One question though - the Collection type needs to be imported I presume as I am getting a not defined exception…?

yup - in the same way that Base is needed as an import to treat objects as that (Base is the uber parent Speckle object)

I also need to add a caveat that the Revit example is a v2 example. Some changes coming in Next Gen that will require something different

1 Like

Just found it when you posted this - thanks @jonathon although I am using this - seems to be working :grinning:

from specklepy.objects.other import Collection
1 Like