> ## Documentation Index
> Fetch the complete documentation index at: https://docs.speckle.systems/llms.txt
> Use this file to discover all available pages before exploring further.

# Working with Speckle Objects

> Learn how to create, structure, and work with Speckle Base objects

## What You'll Learn

By the end of this guide, you'll understand:

* ✅ How to create and structure `Base` objects
* ✅ The difference between direct attributes and the properties dictionary
* ✅ How to access and discover object properties
* ✅ How to work with lists and collections

## Prerequisites

Before starting this guide, you should:

* Have [specklepy installed](/developers/sdks/python/getting-started/installation)
* Understand basic Python (classes, lists, dictionaries)
* Be familiar with the [quickstart](/developers/sdks/python/getting-started/quickstart)

<Info>
  This guide focuses on **working with objects** - not sending/receiving them. For send/receive
  operations, see the [Quickstart](/developers/sdks/python/getting-started/quickstart).
</Info>

## How do I create a Speckle object?

You need to create custom data structures or add metadata to geometry for your Speckle project. Use the `Base` class - it's the foundation of all Speckle objects and supports dynamic properties:

```python lines icon="python" theme={null}
from specklepy.objects import Base

# Create a base object
obj = Base()

# Add direct attributes
obj.name = "My Wall"
obj.elementId = "wall-001"
obj.height = 3.5
obj.isLoadBearing = True

# Add nested structure
obj.elements = []  # Will hold child objects

print(f"Created: {obj.name}")
```

`Base` objects are Python classes that accept any property name dynamically (no predefined schema needed), serialize automatically for Speckle, support nesting (objects within objects), and work with Python's native attribute access.

When to create Base objects:

* Building custom data structures (surveys, analysis results, schedules)
* Grouping related objects together
* Adding application-specific metadata

<Warning>
  **Don't use reserved names:** Avoid property names starting with `_` or matching Base methods like `id`, `speckle_type`, `get_member_names()`. These have special meanings.

  ```python theme={null}
  # ❌ Bad - overwrites internal property
  obj.id = "custom-123"  # This sets the Speckle hash ID

  # ✅ Good - use your own naming
  obj.elementId = "custom-123"
  ```
</Warning>

## How do I add metadata: direct attributes vs. properties dictionary?

You need to attach metadata to objects, but you're unsure whether to use direct attributes (`obj.name`) or the properties dictionary (`obj.properties["name"]`).

Use direct attributes for structure and organization:

```python lines icon="python" theme={null}
from specklepy.objects import Base

# Direct attributes define YOUR data structure
building = Base()
building.name = "Building A"
building.address = "123 Main St"
building.levels = []  # Your hierarchy
building.elements = []  # Your organization

# Add a level
level = Base()
level.name = "Level 1"
level.elevation = 0.0
level.elements = []  # Objects on this level
building.levels.append(level)
```

Use the properties dictionary for queryable metadata:

```python lines icon="python" theme={null}
from specklepy.objects import Base

# Properties dict for metadata that other apps should find
wall = Base()
wall.name = "W-101"

# Standard BIM metadata
wall.properties = {
    "category": "Walls",
    "material": "Concrete",
    "thickness": 200,
    "fireRating": "2 hour",
    "isLoadBearing": True,
    "cost": 1500.00
}

# Access safely with defaults
material = wall.properties.get("material", "Unknown")
thickness = wall.properties.get("thickness", 0)
```

**Direct attributes** (`obj.name`) are part of your object's structure, provide Python-level property access, and are good for relationships and hierarchies like `obj.levels`, `obj.elements`, `obj.children`.

**Properties dictionary** (`obj.properties`) is a standardized metadata container, follows BIM connector conventions, makes data searchable across applications, and commonly uses keys like `category`, `family`, `type`, `material`.

<Info>
  **Which to use?** Both are valid Speckle patterns! The `properties` dictionary is a *convention*
  that makes metadata more discoverable. If Revit sends a wall, its category will be in
  `properties["category"]` - following this pattern makes your data easier to work with across
  platforms.
</Info>

Combining both approaches:

```python lines icon="python" theme={null}
from specklepy.objects import Base
from specklepy.objects.geometry import Point, Mesh

# Create a beam with both structure and metadata
beam = Base()

# Direct attributes - your structure
beam.name = "B-205"
beam.startPoint = Point(x=0, y=0, z=3.5)
beam.endPoint = Point(x=5, y=0, z=3.5)
beam.displayValue = mesh  # Geometry reference

# Properties dict - queryable metadata
beam.properties = {
    "category": "Structural Framing",
    "family": "Steel Beam",
    "type": "W12x26",
    "material": "Steel",
    "length": 5.0,
    "weight": 130.0
}
```

## How do I access object properties?

You have a Speckle object and need to read its properties, but you don't know what properties it has or how to access them safely.

For direct attributes:

```python lines icon="python" theme={null}
# Direct access (if you know the property exists)
name = obj.name
height = obj.height

# Safe access with hasattr()
if hasattr(obj, "name"):
    print(f"Name: {obj.name}")
else:
    print("No name property")

# Safe access with getattr() and default
name = getattr(obj, "name", "Unnamed")
height = getattr(obj, "height", 0.0)
```

For properties dictionary:

```python lines icon="python" theme={null}
# Direct access (might raise KeyError)
category = obj.properties["category"]  # KeyError if missing!

# ✅ Safe access with .get() and default
category = obj.properties.get("category", "Unknown")
material = obj.properties.get("material", "Not specified")

# Check if key exists
if "category" in obj.properties:
    print(f"Category: {obj.properties['category']}")
```

**Discovering all properties** - use `get_member_names()` to discover what properties an object has:

```python lines icon="python" theme={null}
from specklepy.objects import Base

obj = Base()
obj.name = "Widget"
obj.value = 42
obj.tags = ["important", "new"]

# Get all property names
property_names = obj.get_member_names()
print(property_names)
# ['name', 'value', 'tags', 'id', 'speckle_type', ...]

# Loop through all properties
for prop_name in obj.get_member_names():
    value = getattr(obj, prop_name, None)
    print(f"{prop_name}: {value}")
```

<Accordion title="What does get_member_names() return?">
  `get_member_names()` returns all property names on the object, **automatically filtering out**:

  * Private members (starting with `_`)
  * Methods and functions
  * Class attributes

  This means you get a clean list of just the data properties. No need to check `if not name.startswith("_")` - it's already filtered!

  ```python theme={null}
  # get_member_names() already excludes private members
  for name in obj.get_member_names():
      value = getattr(obj, name, None)  # Clean, no filtering needed
  ```
</Accordion>

Safe property access pattern:

```python lines icon="python" expandable theme={null}
def safely_read_object(obj):
    """Read all properties from an object safely."""

    # Read known direct attributes with defaults
    name = getattr(obj, "name", "Unnamed")

    # Read properties dictionary safely
    properties = {}
    if hasattr(obj, "properties") and obj.properties:
        category = obj.properties.get("category", "Unknown")
        material = obj.properties.get("material", "Not specified")
        properties = {
            "category": category,
            "material": material
        }

    return {
        "name": name,
        "properties": properties
    }

# Use it
info = safely_read_object(wall)
print(f"Wall: {info['name']}, Category: {info['properties']['category']}")
```

<Warning>
  **Don't assume properties exist!** Speckle objects can come from different sources with different schemas. Always use safe access patterns:

  ```python theme={null}
  # ❌ Bad - will crash if property doesn't exist
  category = obj.properties["category"]
  name = obj.name

  # ✅ Good - handles missing properties gracefully
  category = obj.properties.get("category", "Unknown")
  name = getattr(obj, "name", "Unnamed")
  ```
</Warning>

## How do I work with lists and collections?

You need to work with multiple objects - filtering them, counting them, or processing them in bulk.

Looping through lists:

```python lines icon="python" theme={null}
from specklepy.objects import Base

# Create a collection of objects
building = Base()
building.elements = []

for i in range(5):
    element = Base()
    element.name = f"Element-{i}"
    element.properties = {"category": "Walls" if i % 2 == 0 else "Floors"}
    building.elements.append(element)

# Loop through elements
for element in building.elements:
    name = getattr(element, "name", "unnamed")
    category = element.properties.get("category", "Unknown")
    print(f"{name}: {category}")
```

Filtering lists:

```python lines icon="python" theme={null}
# Filter with list comprehension
walls = [
    obj for obj in building.elements
    if obj.properties.get("category") == "Walls"
]

print(f"Found {len(walls)} walls")

# Filter with multiple criteria
load_bearing_walls = [
    obj for obj in building.elements
    if obj.properties.get("category") == "Walls"
    and obj.properties.get("isLoadBearing", False)
]
```

Counting and aggregating:

```python lines icon="python" theme={null}
# Count by category
from collections import Counter

categories = Counter(
    obj.properties.get("category", "Unknown")
    for obj in building.elements
)

print(categories)
# Counter({'Walls': 3, 'Floors': 2})

# Sum numeric properties
total_area = sum(
    obj.properties.get("area", 0.0)
    for obj in building.elements
)

print(f"Total area: {total_area}")
```

**Working with nested collections** - many Speckle objects use `elements` arrays for hierarchical structures:

<Note>
  **Terminology Note:** Examples here use terms like "building", "level", "room" to illustrate
  hierarchical concepts. Real BIM data from connectors (Revit, Rhino, etc.) uses **proxy
  structures** rather than direct nesting - for example, Revit levels are `LevelProxy` objects that
  reference elements by ID. See [Proxification](/developers/sdks/python/concepts/proxification) and
  [BIM Data Patterns](/developers/sdks/python/guides/bim-data-patterns) for actual BIM structures.
</Note>

```python lines icon="python" expandable theme={null}
from specklepy.objects import Base

# Create hierarchical structure (illustrative example)
building = Base()
building.name = "Building A"
building.elements = []

# Add levels
for level_num in range(3):
    level = Base()
    level.name = f"Level {level_num}"
    level.properties = {"category": "Levels", "elevation": level_num * 3.5}
    level.elements = []  # Objects on this level

    # Add rooms to this level
    for room_num in range(4):
        room = Base()
        room.name = f"Room {level_num}{room_num}"
        room.properties = {"category": "Rooms", "area": 25.0}
        level.elements.append(room)

    building.elements.append(level)

# Process hierarchy
for level in building.elements:
    level_name = getattr(level, "name", "unnamed")
    room_count = len(level.elements) if hasattr(level, "elements") else 0
    print(f"{level_name}: {room_count} rooms")
```

Common collection patterns:

```python lines icon="python" theme={null}
# Check if object has elements
if hasattr(obj, "elements") and obj.elements:
    print(f"Object has {len(obj.elements)} elements")
    for element in obj.elements:
        process(element)

# Safe iteration (handles missing elements)
elements = getattr(obj, "elements", [])
for element in elements:
    process(element)

# Find first match
def find_first(objects, category):
    """Find first object with matching category."""
    for obj in objects:
        if obj.properties.get("category") == category:
            return obj
    return None

wall = find_first(building.elements, "Walls")
```

<Warning>
  Check if elements exists before looping:

  ```python theme={null}
  # ❌ Bad - crashes if no elements property
  for element in obj.elements:
      process(element)

  # ✅ Good - safe check first

  if hasattr(obj, "elements"):
  for element in obj.elements:
  process(element)

  # ✅ Also good - use getattr with default

  for element in getattr(obj, "elements", []):
  process(element)

  ```
</Warning>

## Practical Examples

### Example 1: Create a Survey Point Collection

```python lines icon="python" expandable theme={null}
from specklepy.objects import Base
from specklepy.objects.geometry import Point

# Create survey collection
survey = Base()
survey.name = "Site Survey 2024-01"
survey.date = "2024-01-15"
survey.surveyor = "Jane Smith"
survey.points = []

# Add survey points
coordinates = [
    (0, 0, 100.5),
    (10, 0, 100.8),
    (10, 10, 101.2),
    (0, 10, 100.9)
]

for i, (x, y, z) in enumerate(coordinates):
    point = Point(x=x, y=y, z=z)
    point.units = "m"

    # Add metadata as properties
    point.properties = {
        "pointId": f"SP-{i+1:03d}",
        "accuracy": 0.01,
        "method": "GPS"
    }

    survey.points.append(point)

print(f"Survey '{survey.name}' has {len(survey.points)} points")
```

## Example 2: Filter and Summarize Objects

```python lines icon="python" expandable theme={null}
from collections import Counter

def filter_by_property(objects, key, value):
    """Filter objects by property key-value pair."""
    return [
        obj for obj in objects
        if obj.properties.get(key) == value
    ]

def summarize_objects(objects):
    """Create summary statistics for objects."""

    if not objects:
        return "No objects to summarize"

    # Count by category
    categories = Counter(
        obj.properties.get("category", "Unknown")
        for obj in objects
    )

    # Calculate total area (if present)
    total_area = sum(
        obj.properties.get("area", 0.0)
        for obj in objects
    )

    return {
        "count": len(objects),
        "categories": dict(categories),
        "total_area": total_area
    }

# Use the functions
elements = building.elements
walls = filter_by_property(elements, "category", "Walls")
summary = summarize_objects(walls)

print(f"Found {summary['count']} walls")
print(f"Categories: {summary['categories']}")
print(f"Total area: {summary['total_area']} m²")
```

### Example 3: Build a Hierarchy

```python lines icon="python" expandable theme={null}
from specklepy.objects import Base

def create_project_hierarchy():
    """Create a multi-level project hierarchy."""

    # Project root
    project = Base()
    project.name = "Office Building"
    project.elements = []

    # Building
    building = Base()
    building.name = "Building A"
    building.properties = {"type": "Office", "floors": 3}
    building.elements = []

    # Add levels
    for floor in range(3):
        level = Base()
        level.name = f"Level {floor + 1}"
        level.properties = {
            "category": "Levels",
            "elevation": floor * 3.5,
            "area": 1000.0
        }
        level.elements = []

        # Add spaces
        for space in range(5):
            room = Base()
            room.name = f"Room {floor + 1}.{space + 1}"
            room.properties = {
                "category": "Rooms",
                "area": 25.0,
                "occupancy": 4
            }
            level.elements.append(room)

        building.elements.append(level)

    project.elements.append(building)
    return project

# Create and inspect
project = create_project_hierarchy()
building = project.elements[0]

for level in building.elements:
    level_name = level.name
    room_count = len(level.elements)
    print(f"{level_name}: {room_count} rooms")
```

## Learn More

Core Concepts:

* [Objects & Base Class](/developers/sdks/python/concepts/objects) - Deep dive into Base and object system
* [Data Types](/developers/sdks/python/concepts/data-types) - Understanding Speckle's type system

Guides:

* [Simple Data Patterns](/developers/sdks/python/guides/simple-data-patterns) - More examples of custom data structures
* [Intermediate: Finding and Extracting Data](/developers/sdks/python/guides/how-to-find-and-extract-data) - Learn to traverse and filter nested structures

API Reference:

* [Base class methods](/developers/sdks/python/api-reference/operations) - Complete Base API documentation

## Next Steps

Now that you understand how to work with Speckle objects, you're ready to:

1. **Learn traversal** - Navigate nested object structures recursively
2. **Extract data** - Filter and find specific objects in complex hierarchies
3. **Work with geometry** - Extract and manipulate displayValue meshes from BIM objects

Continue to [Intermediate: Finding and Extracting Data](/developers/sdks/python/guides/how-to-find-and-extract-data) →
