Frequently Asked Questions
General
What Python versions are supported?
RevitPy requires Python 3.11 or later. The pyproject.toml specifies requires-python = ">=3.11" and classifiers list Python 3.11 and 3.12.
What license is RevitPy released under?
RevitPy is released under the MIT license.
Do I need Revit installed to develop with RevitPy?
No. RevitPy includes a MockRevit environment that simulates the Revit application, documents, and elements. You can develop and test your code entirely without a Revit installation. See the Testing guide for details.
API and Querying
What is the difference between the API QueryBuilder and the ORM QueryBuilder?
RevitPy has two query builders that serve different purposes:
API QueryBuilder (revitpy.api.query.QueryBuilder):
- Filters by property name and
FilterOperatorenum (e.g.,equals("Name", "Wall-1")). - Sorts by property name and
SortDirection. - Has
skip,take,distinctwith property name arguments. - Terminal operations:
execute()returns anElementSet, pluscount(),first(),any(),to_list().
ORM QueryBuilder (revitpy.orm.query_builder.QueryBuilder):
- Filters with predicate functions (e.g.,
where(lambda e: e.name == "Wall-1")). - Projects with selector functions (e.g.,
select(lambda e: e.name)). - Sorts with key selector functions.
- Has lazy evaluation with query plan optimization.
- Has both sync and async terminal operations (e.g.,
to_list(),to_list_async()). - Supports streaming queries for large datasets via
as_streaming().
Use the API QueryBuilder for straightforward property-based queries. Use the ORM QueryBuilder when you need predicate-based filtering, projection, lazy evaluation, async execution, or integration with change tracking.
How do I get all elements from a document?
# Using the API
all_elements = api.elements.execute()
# Using the ORM
all_of_type = context.all(WallElement)
How do I paginate results?
Use skip and take:
page_size = 25
page_number = 2 # 0-indexed
results = (
api.elements
.order_by_ascending("Name")
.skip(page_number * page_size)
.take(page_size)
.execute()
)
Transactions
What happens if an exception occurs inside a transaction?
When using Transaction as a context manager, an exception causes the transaction to roll back automatically. The auto_commit option (default True) controls whether the transaction commits on a clean exit:
with api.transaction("My Work") as txn:
# If this raises, the transaction rolls back
do_work()
# If no exception, the transaction commits (when auto_commit=True)
Can I nest transactions?
RevitPy does not support nested transactions in the API layer. Use TransactionGroup to coordinate multiple transactions:
with api.transaction_group("Batch") as group:
txn1 = group.add_transaction()
txn2 = group.add_transaction()
# All start, commit, or rollback together
How does retry work with transactions?
Use retry_transaction from revitpy.api.transaction:
from revitpy.api.transaction import retry_transaction
result = retry_transaction(
provider=api.active_document,
operation=lambda: do_work(),
max_retries=3,
delay=1.0,
name="Retry Example",
)
This retries the operation up to max_retries times with the specified delay between attempts.
ORM and Change Tracking
How does change tracking work?
When auto_track_changes is enabled in ContextConfiguration (the default), entities retrieved through RevitContext are automatically attached and tracked. The ChangeTracker records entity states:
- When you retrieve an entity, it is attached with state
UNCHANGED. - When you modify an attached entity, it transitions to
MODIFIED. context.add(entity)marks it asADDED.context.remove(entity)marks it asDELETED.context.save_changes()persists all pending changes and accepts them.context.reject_changes()reverts to the last accepted state.
You can check the state of any entity with context.get_entity_state(entity).
What are the CachePolicy options?
| Policy | Behavior |
|---|---|
NONE |
No caching at all |
MEMORY |
Cache in memory with LRU eviction (default) |
PERSISTENT |
Persistent cache with invalidation support |
AGGRESSIVE |
Cache everything, maximize hit rate |
How do I clear the ORM cache?
# Clear all cache
context.clear_cache()
# Invalidate by type
context.invalidate_cache(entity_type=WallElement)
# Invalidate a specific entity
context.invalidate_cache(entity_type=WallElement, entity_id=some_id)
Events
How do I register event handlers at module level?
Use the @event_handler decorator at module level. The handler metadata is stored on the function. To activate it, either let the EventManager auto-discover it, or register it manually:
from revitpy.events.decorators import event_handler
from revitpy.events.types import EventType, EventResult
@event_handler([EventType.ELEMENT_CREATED])
def on_created(event_data):
return EventResult.CONTINUE
# Manual registration
from revitpy.events.manager import get_event_manager
manager = get_event_manager()
manager.register_handler(on_created._event_handler, on_created._event_types)
What is the maximum error count for handlers?
By default, event handlers are disabled after 10 errors (max_errors=10 in the @event_handler decorator). You can change this per handler.
Async
Can I use async operations without an event loop?
The @async_revit_operation and @background_task decorators detect the execution context. If there is no running event loop, they fall back to synchronous execution.
How do I cancel a long-running async operation?
Use a CancellationToken:
from revitpy.async_support.cancellation import CancellationToken
token = CancellationToken()
# Start the operation
task = asyncio.create_task(
async_revit.get_elements_async(cancellation_token=token)
)
# Cancel when needed
token.cancel()
Extensions
How are extension dependencies resolved?
When dependency_resolution is enabled in ExtensionManagerConfig (the default), the ExtensionManager loads dependencies before loading the dependent extension. Dependencies are listed by name in ExtensionMetadata.dependencies.
Can I use dependency injection outside of extensions?
Yes. The DIContainer can be used standalone:
from revitpy.extensions.dependency_injection import DIContainer
container = DIContainer()
container.register_singleton(MyService, instance=my_service)
service = container.get_service(MyService)
Testing
How do I run tests without Revit?
Use MockRevit to simulate the Revit environment:
from revitpy import RevitAPI, MockRevit
mock = MockRevit()
doc = mock.create_document("Test.rvt")
mock.create_elements(count=5, element_type="Wall")
api = RevitAPI()
api.connect(mock.application)
# Run tests against the API as normal
See the Testing guide for pytest integration examples.
Can I serialize mock state for reproducible tests?
Yes. MockRevit supports save_state(path) and load_state(path) to persist the entire mock environment (documents, elements, fixtures) as JSON.