Skip to content

extensible_storage

Generic Extensible Storage helper.

Extensible Storage lets a tool attach small bits of typed data to any Element and have Revit persist it as part of the model. Used directly, that means writing SchemaBuilder/Entity boilerplate -- a GUID, field types, access levels, generic Get[T]/Set[T] calls -- every time a tool needs to remember something about an element. This module wraps that boilerplate behind two things:

* BaseSchema -- a plain class a tool subclasses to *declare* its
  schema (GUID, name, vendor id, fields) instead of building one
  by hand.
* ElementDataStorage -- reads/writes/clears an element's Entity for
  a given schema as a plain dict, so callers never touch
  SchemaBuilder, Entity, or Get[T]/Set[T] directly.

Only "simple" fields (a single value per field -- str, int, float, bool, DB.ElementId, System.Guid, etc.) and "array" fields (an ordered list of one of those types -- e.g. every ElementId a tool generated) are covered directly. A schema that also needs SchemaBuilder.AddMapField can add it via the BaseSchema.extend_builder hook (see below).

Example:

from pyrevit import DB
from pyrevit.coreutils import extensible_storage

class MyToolLinkSchema(extensible_storage.BaseSchema):
    guid = "a1b2c3d4-e5f6-4a7b-8c9d-0123456789ab"
    schema_name = "MyToolElementLink"
    vendor_id = "mytool"
    fields = {"LinkedId": DB.ElementId}

storage = extensible_storage.ElementDataStorage(MyToolLinkSchema)

with revit.Transaction("Link elements"):
    storage.set_data(some_element, LinkedId=other_element.Id)

data = storage.get_data(some_element)
if data:
    linked_id = data["LinkedId"]

# A list-valued field works the same way, just declared under
# array_fields instead of fields:

class MyToolManagedElementsSchema(extensible_storage.BaseSchema):
    guid = "a1b2c3d4-e5f6-4a7b-8c9d-0123456789ab"
    schema_name = "MyToolManagedElements"
    vendor_id = "mytool"
    array_fields = {"ManagedIds": DB.ElementId}

managed = extensible_storage.ElementDataStorage(MyToolManagedElementsSchema)
managed.set_data(some_view, ManagedIds=[a.Id, b.Id, c.Id])
data = managed.get_data(some_view)
if data:
    ids = data["ManagedIds"]   # list of ElementId

Classes

BaseSchema

Bases: object

Subclass to declaratively define an Extensible Storage schema.

Class attributes

guid (str): fixed schema GUID as a string. Generate a fresh one per schema and never change it once the schema is in use) schema_name (str): schema name (letters/digits/underscore, must start with a letter) vendor_id (str): short vendor/application id required by SchemaBuilder.SetVendorId -- any short alphanumeric string is accepted, Revit does not validate it against a registry application_guid (str or None): optional -- only needed to further scope AccessLevel.Application/Vendor reads read_access_level / write_access_level (DB.ExtensibleStorage.AccessLevel): default Public (readable/writable by any application, not just the one that wrote it) fields (dict): {field_name: field_type}, e.g. {"LinkedId": DB.ElementId, "Note": str}. Only simple (single-value) fields. array_fields (dict): {field_name: element_type}, e.g. {"ManagedIds": DB.ElementId} -- each is stored/read as an ordered list of that type. A field name must not appear in both fields and array_fields. documentation (str or None): optional overall schema doc string

Attributes

guid = None class-attribute instance-attribute
schema_name = None class-attribute instance-attribute
vendor_id = 'pyrvt' class-attribute instance-attribute
application_guid = None class-attribute instance-attribute
read_access_level = DB.ExtensibleStorage.AccessLevel.Public class-attribute instance-attribute
write_access_level = DB.ExtensibleStorage.AccessLevel.Public class-attribute instance-attribute
fields = {} class-attribute instance-attribute
array_fields = {} class-attribute instance-attribute
documentation = None class-attribute instance-attribute

Methods:

extend_builder(builder) classmethod

Override to add anything SchemaBuilder supports beyond simple and array fields -- i.e. builder.AddMapField(...) -- before the schema is finished. Called once, right before Finish(), only when the schema is being newly registered. No-op by default.

Parameters:

Name Type Description Default
builder

DB.ExtensibleStorage.SchemaBuilder being built

required
Source code in pyrevitlib/pyrevit/coreutils/extensible_storage.py
@classmethod
def extend_builder(cls, builder):
    """Override to add anything SchemaBuilder supports beyond
    simple and array fields -- i.e. builder.AddMapField(...) --
    before the schema is finished. Called once, right before
    Finish(), only when the schema is being newly registered.
    No-op by default.

    Args:
        builder: DB.ExtensibleStorage.SchemaBuilder being built
    """
    pass

ElementDataStorage(schema_cls)

Bases: object

Reads/writes/clears one element's Entity for a given BaseSchema subclass, as a plain dict of field name -> value.

Parameters:

Name Type Description Default
schema_cls

a BaseSchema subclass describing the schema to read/write

required
Source code in pyrevitlib/pyrevit/coreutils/extensible_storage.py
def __init__(self, schema_cls):
    """
    Args:
        schema_cls: a BaseSchema subclass describing the schema to
            read/write
    """
    if not schema_cls.guid or not schema_cls.schema_name:
        raise ValueError(
            "{0} must define both 'guid' and 'schema_name'".format(
                schema_cls.__name__
            )
        )
    if not schema_cls.fields and not schema_cls.array_fields:
        raise ValueError(
            "{0} must define at least one field".format(schema_cls.__name__)
        )
    overlap = set(schema_cls.fields) & set(schema_cls.array_fields)
    if overlap:
        raise ValueError(
            "{0} declares {1} in both 'fields' and 'array_fields'".format(
                schema_cls.__name__, sorted(overlap)
            )
        )
    self._schema_cls = schema_cls
    self._schema = None

Attributes

schema property

DB.ExtensibleStorage.Schema: the registered schema, built and registered on first access if not already present in this document/session.

Methods:

has_data(element)

bool: whether element currently has an Entity for this schema.

Source code in pyrevitlib/pyrevit/coreutils/extensible_storage.py
def has_data(self, element):
    """bool: whether `element` currently has an Entity for this
    schema."""
    schema = self._lookup_schema()
    if schema is None:
        return False
    entity = element.GetEntity(schema)
    return entity is not None and entity.IsValid()
get_data(element)

Read this schema's data off element.

Parameters:

Name Type Description Default
element

DB.Element to read from

required

Returns:

Name Type Description
dict

{field_name: value} for every field declared on the schema (default-valued for any field never explicitly set), or None if element has no Entity for this schema -- including if the schema has never been written anywhere in this document at all

Source code in pyrevitlib/pyrevit/coreutils/extensible_storage.py
def get_data(self, element):
    """Read this schema's data off `element`.

    Args:
        element: DB.Element to read from

    Returns:
        dict: {field_name: value} for every field declared on the
            schema (default-valued for any field never explicitly
            set), or None if `element` has no Entity for this
            schema -- including if the schema has never been
            written anywhere in this document at all
    """
    schema = self._lookup_schema()
    if schema is None:
        return None

    entity = element.GetEntity(schema)
    if not entity.IsValid():
        return None

    data = {}
    for field_name, field_type in self._schema_cls.fields.items():
        data[field_name] = entity.Get[field_type](field_name)
    for field_name, field_type in self._schema_cls.array_fields.items():
        values = entity.Get[IList[field_type]](field_name)
        data[field_name] = list(values) if values is not None else []
    return data
set_data(element, **field_values)

Write (or overwrite) this schema's Entity on element.

This replaces the whole Entity, not just the fields passed -- any declared field you don't pass reverts to its CLR default (0 / empty string / InvalidElementId, depending on type) rather than keeping whatever was there before. Pass every field you care about each time.

Parameters:

Name Type Description Default
element

DB.Element to write to

required
**field_values

value for each field name declared in the schema's fields (a single value) or array_fields (an iterable); passing a name that isn't a declared field raises KeyError

{}

Must be called inside an open transaction.

Source code in pyrevitlib/pyrevit/coreutils/extensible_storage.py
def set_data(self, element, **field_values):
    """Write (or overwrite) this schema's Entity on `element`.

    This replaces the whole Entity, not just the fields passed --
    any declared field you don't pass reverts to its CLR default
    (0 / empty string / InvalidElementId, depending on type) rather
    than keeping whatever was there before. Pass every field you
    care about each time.

    Args:
        element: DB.Element to write to
        **field_values: value for each field name declared in the
            schema's `fields` (a single value) or `array_fields`
            (an iterable); passing a name that isn't a declared
            field raises KeyError

    Must be called inside an open transaction.
    """
    known_fields = set(self._schema_cls.fields) | set(
        self._schema_cls.array_fields
    )
    for field_name in field_values:
        if field_name not in known_fields:
            raise KeyError(
                "'{0}' is not a field on schema '{1}'".format(
                    field_name, self._schema_cls.schema_name
                )
            )

    entity = DB.ExtensibleStorage.Entity(self.schema)
    for field_name, field_type in self._schema_cls.fields.items():
        if field_name in field_values:
            entity.Set[field_type](field_name, field_values[field_name])
    for field_name, field_type in self._schema_cls.array_fields.items():
        if field_name in field_values:
            entity.Set[IList[field_type]](
                field_name, to_clr_list(field_type, field_values[field_name])
            )
    element.SetEntity(entity)
clear_data(element)

Remove this schema's Entity from element, if present.

Parameters:

Name Type Description Default
element

DB.Element to clear

required

Must be called inside an open transaction.

Source code in pyrevitlib/pyrevit/coreutils/extensible_storage.py
def clear_data(self, element):
    """Remove this schema's Entity from `element`, if present.

    Args:
        element: DB.Element to clear

    Must be called inside an open transaction.
    """
    schema = self._lookup_schema()
    if schema is None:
        return
    element.DeleteEntity(schema)

Functions: