Easy-Acumatica
Documentation

Model Factory

Type-safe dataclasses generated dynamically from your Acumatica schema

Dynamic Models
Type Safety
Custom Fields

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

  1. Schema Analysis: Parse OpenAPI components/schemas section
  2. Type Mapping: Convert JSON Schema types to Python type hints
  3. Dataclass Creation: Build dataclasses with proper inheritance
  4. Reference Resolution: Handle circular references and forward declarations
  5. 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:

python
@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

python
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

python
# 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

python
# 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

python
# 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:

python
# 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

python
# 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