Model Factory
Type-safe dataclasses generated dynamically from your Acumatica schema
Architecture Overview
The Model Factory creates Python dataclasses at runtime from your Acumatica schema definitions. This ensures your models always match your instance's exact structure, including custom fields and modifications.
Generation Process
- Schema Analysis: Parse OpenAPI components/schemas section
- Type Mapping: Convert JSON Schema types to Python type hints
- Dataclass Creation: Build dataclasses with proper inheritance
- Reference Resolution: Handle circular references and forward declarations
- Attachment to Client: Models become available via
client.models.*
Model Structure
Anatomy of a Generated Model
Every generated model is a Python dataclass that inherits from BaseDataClassModel
:
@dataclass
class Bill(Entity):
"""Represents the Bill entity.
Attributes:
Type (str): The type of bill
Vendor (str): Vendor reference
ReferenceNbr (str): Reference number
Status (str): Current status
Balance (decimal): Outstanding balance
DueDate (datetime): Payment due date
DetailTotal (decimal): Total of detail lines
ApprovalStatus (str): Approval workflow status
TaxTotal (decimal): Total tax amount
custom (dict): Custom field container
files (List[FileLink]): Attached files
"""
Type: Optional[str] = None
Vendor: Optional[str] = None
ReferenceNbr: Optional[str] = None
Status: Optional[str] = None
Balance: Optional[Union[str, int, float, Decimal]] = None
DueDate: Optional[datetime] = None
DetailTotal: Optional[Union[str, int, float, Decimal]] = None
ApprovalStatus: Optional[str] = None
TaxTotal: Optional[Union[str, int, float, Decimal]] = None
Models include all fields from your schema, with proper type annotations and optional markers.
Implementation Details
The Factory Process
class ModelFactory:
"""Dynamically builds Python dataclasses from OpenAPI schema."""
def __init__(self, schema: Dict[str, Any]):
self._schema = schema
self._models = {}
self._primitive_wrappers = {
"StringValue", "DecimalValue", "BooleanValue",
"DateTimeValue", "IntValue", "GuidValue"
}
def build_models(self) -> Dict[str, Type[BaseDataClassModel]]:
"""Build all models from schema definitions."""
definitions = self._schema.get("components", {}).get("schemas", {})
# First pass: Create placeholder classes
for name in definitions:
if name not in self._primitive_wrappers:
# Create empty class to handle forward references
self._models[name] = type(name, (BaseDataClassModel,), {})
# Second pass: Build complete models
for name, definition in definitions.items():
if name in self._primitive_wrappers:
continue
model_class = self._create_model(name, definition)
self._models[name] = model_class
# Third pass: Resolve forward references
self._resolve_forward_references()
return self._models
Handling Complex Types
The factory handles several complex scenarios:
Usage Patterns
Creating Entities
# Create new entities with models
new_contact = client.models.Contact(
DisplayName="John Doe",
Email="john@example.com",
Phone1="555-0123"
)
# Models validate and format data
created = client.contacts.put_entity(new_contact)
Updating Entities
# Partial updates with type safety
update = client.models.Contact(
id=contact_id,
Email="newemail@example.com"
# Only include fields to update
)
updated = client.contacts.put_entity(update)
Working with Nested Data
# Complex nested structures
invoice = client.models.Invoice(
Customer="CUST001",
Date=datetime.now(),
Details=[
client.models.InvoiceDetail(
InventoryID="ITEM001",
Quantity=5,
UnitPrice=10.00
),
client.models.InvoiceDetail(
InventoryID="ITEM002",
Quantity=3,
UnitPrice=25.00
)
]
)
# Everything is properly typed!
Type Stub Generation
For optimal IDE support, generate type stubs after connecting:
# Generate stubs for your instance
from easy_acumatica.generate_stubs import generate_stubs_from_client
# After connecting
client = AcumaticaClient(...)
# Generate .pyi files
generate_stubs_from_client(client)
# Now your IDE knows about all models!
Generated Stub Example
# Generated models.pyi
from typing import Optional, List, Union
from datetime import datetime
from decimal import Decimal
@dataclass
class Bill(Entity):
"""Represents the Bill entity."""
Type: Optional[str] = ...
Vendor: Optional[str] = ...
ReferenceNbr: Optional[str] = ...
Status: Optional[str] = ...
Balance: Optional[Union[str, int, float, Decimal]] = ...
# ... all fields from your schema
Best Practices
Trust the Generation
Models are built from your live schema - they're always correct
Generate Stubs
Run stub generation for the best IDE experience
Use Partial Updates
Only include fields you want to change when updating
Let Models Serialize
Use model.build() to get API-ready dictionaries
Type Safety First
Leverage type hints for error prevention
Embrace Custom Fields
Custom fields are discovered automatically