Core API Reference
Manage entities, register relationships, and resolve DTO trees with ErManager, Resolver, DefineSubset, and Loader.
ErManager
Create an entity relationship manager to discover entities, register relationships, and create Resolvers.
from nexusx import ErManager
er = ErManager(
base=SQLModel, # SQLModel base class (mutually exclusive with entities)
entities=None, # Explicit entity list (mutually exclusive with base)
session_factory=async_session, # Async session factory
)
Warning
The base and entities parameters are mutually exclusive — you cannot pass both. Choose one approach: either provide a base class for auto-discovery or explicitly list your entities.
Methods
| Method | Description |
|---|---|
create_resolver() |
Returns a Resolver class bound to the entity graph |
get_diagram() |
Returns an ErDiagram instance |
add_virtual_entities(entities) |
Registers plain BaseModel subclasses as virtual entities (see below) |
add_virtual_entities
Register plain pydantic.BaseModel subclasses as virtual entities — non-SQLModel roots that participate in resolution, custom relationships, and ER visualization alongside SQLModel entities. Typical use cases: CurrentUser assembled from OIDC claims, page wrappers aggregating multiple services, third-party SDK DTOs.
from pydantic import BaseModel
from nexusx import ErManager, Relationship
class CurrentUserRoot(BaseModel):
oid: str
name: str
agents: list[AgentDTO] = []
__relationships__ = [
Relationship(fk="oid", target=list[AgentDTO],
name="agents", loader=load_agents_by_oid),
]
er = ErManager(entities=[Agent], session_factory=async_session)
er.add_virtual_entities([CurrentUserRoot]) # ← must be before create_resolver()
Resolver = er.create_resolver()
Must be called before the first create_resolver() — the registry freezes at that point.
| Input | Result |
|---|---|
[A, B] (plain BaseModel, not yet registered) |
Both registered |
[] (empty list) |
No-op |
[42] or [int] (non-class) |
TypeError |
[SomeRandomClass] (not a BaseModel) |
TypeError |
[User] where User(SQLModel, table=True) |
TypeError — SQLModel goes in __init__'s entities= / base= |
[A, A] (duplicate within or across calls) |
ValueError |
Called after create_resolver() |
RuntimeError ("registry is frozen") |
See the Virtual Entities guide for usage patterns, ER visualization rules, and migration from the _subset_registry hack.
Resolver
Resolve DTO trees by traversing relationships and executing resolver methods.
Constructor Parameters
| Parameter | Type | Description |
|---|---|---|
context |
dict |
Global context, accessible via ancestor_context |
loader_params |
dict |
DataLoader extra parameters |
debug |
bool |
Enable debug logging |
resolve Method
Execute the resolution process on a DTO or list of DTOs.
The dtos parameter can be a single DTO instance or a list of DTOs. Returns the same objects after in-place modification.
Execution Order
The Resolver executes methods in a specific order:
- Execute all
resolve_*methods (load relationship data) - Traverse existing object fields
- Execute all
post_*methods (compute derived fields) - Collect SendTo values to ancestor Collectors
DefineSubset
Create DTO base classes that generate Pydantic models from SQLModel entities.
Tip
Think of DefineSubset as a lens that focuses on specific fields of an entity. You declare which fields to include, and the framework generates a clean Pydantic model with those fields — automatically hiding FK fields while keeping them accessible for relationship loading.
subset Syntax
Accepts either a tuple (Entity, ('field1', 'field2')) or a SubsetConfig object.
Rules
- FK fields are automatically hidden from serialization output (
exclude=True), but remain internally accessible forresolve_*methods - Relationship fields are declared in the class body (not in
__subset__), and must use DTO types - Direct use of SQLModel entities as field types is prohibited
Warning
You cannot use SQLModel entities as field types in your DTOs. Always use DTO types for relationships — declaring author: User | None will raise a TypeError. Instead, use author: UserDTO | None.
Tip
The __subset__ source can be any BaseModel subclass, not just SQLModel. This lets you subset external schemas (OAuth claims, third-party SDK classes) without an ORM table behind them. When the source is a plain BaseModel, no _orm_to_dto conversion runs — the user constructs DTO instances directly. See the Virtual Entities guide for details.
SubsetConfig
Configure DTOs declaratively as an alternative to the __subset__ tuple syntax.
from nexusx import SubsetConfig
class UserDTO(DefineSubset):
__subset__ = SubsetConfig(entity=User, fields=("id", "name"))
Loader
Declare DataLoader dependencies in resolve_* methods to load relationship data.
from nexusx import Loader
# DataLoader class
def resolve_tags(self, loader=Loader(TagLoader)):
return loader.load(self.id)
# Async batch function
async def load_users(user_ids):
...
def resolve_owner(self, loader=Loader(load_users)):
return loader.load(self.owner_id)
Warning
Your Loader dependency names must match relationship names in ErManager. For example, Loader('author') requires that ErManager has a relationship named author registered.
build_dto_select
Build a SELECT statement for querying DTO fields from the SQL database.
from nexusx import build_dto_select
stmt = build_dto_select(SprintSummary)
stmt = build_dto_select(SprintSummary, where=Sprint.id == sprint_id)
Tip
When ORM relationships use lazy="noload" (the recommended pattern with ErManager + Resolver), this function provides minimal benefit since the only pruning is on scalar columns. You can achieve the same result with select(Entity) and DTO.model_validate(entity). Use this function when the DTO selects a small subset of scalar columns from a wide table.