AcumaticaClient
Main client class for Acumatica REST API integration
Introduction
The AcumaticaClient handles authentication, session management, and provides dynamically-generated services and models based on your Acumatica instance's schema.
from easy_acumatica import AcumaticaClient
# Initialize client (loads from .env automatically)
client = AcumaticaClient()
# Access dynamically generated services
customer = client.customers.get_by_id("ABCCOMP")
print(f"Customer: {customer.CustomerName}")
# Use dynamically generated models
new_customer = client.models.Customer(
CustomerID="NEWCUST",
CustomerName="New Customer"
)
created = client.customers.put_entity(new_customer)Initialization
The client can be initialized with explicit credentials, environment variables, or automatic .env file loading.
from easy_acumatica import AcumaticaClient
# Method 1: Automatic .env loading (recommended)
# Client searches for .env file in current directory and parent directories
client = AcumaticaClient()
# Method 2: Specify .env file location
client = AcumaticaClient(env_file="config/.env")
# Method 3: Explicit credentials
client = AcumaticaClient(
base_url="https://your-instance.acumatica.com",
username="api_user",
password="secure_password",
tenant="Company"
)
# Method 4: Environment variables (without .env file)
# Set: ACUMATICA_URL, ACUMATICA_USERNAME, ACUMATICA_PASSWORD, ACUMATICA_TENANT
import os
os.environ['ACUMATICA_URL'] = 'https://your-instance.acumatica.com'
os.environ['ACUMATICA_USERNAME'] = 'api_user'
# ...
client = AcumaticaClient()
# Method 5: Using a config object
from easy_acumatica import AcumaticaConfig
config = AcumaticaConfig(
base_url="https://your-instance.acumatica.com",
username="api_user",
password="secure_password",
tenant="Company"
)
client = AcumaticaClient(config=config)| Parameter | Type | Required | Description |
|---|---|---|---|
base_url | str | Optional | Root URL of your Acumatica instance |
username | str | Optional | API username |
password | str | Optional | API password |
tenant | str | Optional | Tenant/Company name |
branch | str | Optional | Branch code |
locale | str | Optional | Locale (e.g., "en-US") |
env_file | str | Path | Optional | Path to .env file (v0.5.10+) |
auto_load_env | bool | Optional | Auto-search for .env files (default: True, v0.5.10+) |
cache_methods | bool | Optional | Enable differential caching (v0.5.10+) |
cache_ttl_hours | int | Optional | Cache TTL in hours (default: 24, v0.5.10+) |
cache_dir | Path | Optional | Cache directory (default: ~/.easy_acumatica_cache, v0.5.10+) |
force_rebuild | bool | Optional | Force rebuild ignoring cache (v0.5.10+) |
verify_ssl | bool | Optional | Verify SSL certificates (default: True) |
persistent_login | bool | Optional | Maintain session (default: True) |
retry_on_idle_logout | bool | Optional | Auto-retry on timeout (default: True) |
endpoint_name | str | Optional | API endpoint name (default: "Default") |
endpoint_version | str | Optional | Specific API version |
config | AcumaticaConfig | Optional | Config object (overrides other params) |
rate_limit_calls_per_second | float | Optional | API rate limit (default: 10.0) |
timeout | int | Optional | Request timeout in seconds (default: 60) |
.env File Loading
As of v0.5.10, the client automatically searches for and loads credentials from .env files. This simplifies configuration management across different environments.
# .env file in your project root
ACUMATICA_URL=https://your-instance.acumatica.com
ACUMATICA_USERNAME=your-username
ACUMATICA_PASSWORD=your-password
ACUMATICA_TENANT=your-tenant
ACUMATICA_BRANCH=MAIN
ACUMATICA_CACHE_METHODS=true
ACUMATICA_CACHE_TTL_HOURS=24
# Your Python script
from easy_acumatica import AcumaticaClient
# Automatically loads from .env - no credentials needed!
client = AcumaticaClient()
# Or specify a custom .env location
client = AcumaticaClient(env_file="config/.env")
# Disable auto-loading if needed
client = AcumaticaClient(
base_url="...",
username="...",
password="...",
tenant="...",
auto_load_env=False # Don't search for .env files
)env_file parameter. Configuration Options
Optional parameters control caching, rate limiting, session behavior, and performance tuning.
client = AcumaticaClient(
base_url="https://your-instance.acumatica.com",
username="api_user",
password="secure_password",
tenant="Company",
# Caching (new in v0.5.10)
cache_methods=True, # Enable differential caching
cache_ttl_hours=24, # Cache lifetime in hours
cache_dir=Path("~/.cache"), # Cache directory location
force_rebuild=False, # Force rebuild ignoring cache
# Session behavior
persistent_login=True, # Maintain session (default)
retry_on_idle_logout=True, # Auto-retry on session timeout
# Performance
rate_limit_calls_per_second=10.0, # Rate limiting
timeout=60, # Request timeout (seconds)
# SSL verification
verify_ssl=True,
# API endpoint
endpoint_name="Default",
endpoint_version=None, # Auto-detect latest
# Localization
branch="MAIN",
locale="en-US"
)Differential Caching
Version 0.5.10 introduces differential caching, which dramatically reduces initialization time by caching generated models and services. The cache intelligently updates only changed components.
from easy_acumatica import AcumaticaClient
from pathlib import Path
# Enable caching for faster subsequent initializations
client = AcumaticaClient(
cache_methods=True, # Enable differential caching
cache_ttl_hours=24, # Cache valid for 24 hours
cache_dir=Path("~/.cache/acumatica") # Custom cache location
)
# First run: Full schema discovery and model/service generation (~5-10 seconds)
# Subsequent runs: Differential cache loading (~0.5-1 second)
# Check cache statistics
stats = client.get_cache_stats()
print(f"Cache hits: {stats['hits']}")
print(f"Cache misses: {stats['misses']}")
print(f"Cache age: {stats['age_hours']:.1f} hours")
# Force rebuild (ignores cache)
client = AcumaticaClient(force_rebuild=True)
# Clear cache manually
client.clear_cache()
# The cache intelligently tracks changes:
# - Only rebuilds models/services that changed
# - Removes models/services that were deleted
# - Preserves unchanged components from cache
# - Automatically invalidates on schema changesSchema Discovery & Introspection
On initialization, the client fetches the OpenAPI schema and generates models and services based on your instance configuration. The client provides powerful introspection methods to explore available services, models, and their schemas.
# The client discovers everything on initialization
client = AcumaticaClient()
# Use built-in methods to see what was discovered
print("\nAvailable Services:")
services = client.list_services()
print(f"Found {len(services)} services")
for service in services[:10]: # Show first 10
print(f" - {service}")
print("\nAvailable Models:")
models = client.list_models()
print(f"Found {len(models)} models")
for model in models[:10]: # Show first 10
print(f" - {model}")
# Get detailed information about a specific service
service_info = client.get_service_info('Customer')
print(f"\nCustomer service: {service_info['entity_name']}")
print(f"Available methods: {service_info['methods']}")
print(f"Available actions: {service_info.get('actions', [])}")
# Get detailed information about a specific model
model_info = client.get_model_info('Customer')
print(f"\nCustomer model fields: {model_info['field_count']} fields")
print(f"Field names: {list(model_info['fields'].keys())[:10]}")
# Check for custom fields
customer_fields = model_info['fields']
custom_fields = [name for name in customer_fields if name.startswith('Usr')]
print(f"Custom fields on Customer: {custom_fields}")
# Get complete schema information
schema_info = client.get_schema_info()
print(f"\nSchema version: {schema_info.get('version')}")
print(f"Total endpoints: {schema_info.get('endpoint_count')}")Dynamic Models
Models are Python dataclasses generated from the OpenAPI schema. They include standard fields and any custom fields defined in your instance.
# All models follow Python dataclass patterns
# Create a new customer
customer = client.models.Customer(
CustomerID="PYTHN001",
CustomerName="Python Customer",
CustomerClass="DEFAULT",
# Your custom fields are here too!
UsrCustomField="Custom Value" if hasattr(client.models.Customer, 'UsrCustomField') else None
)
# Models support all standard operations
print(customer.CustomerID) # PYTHN001
print(customer.__dict__) # See all fields
# Nested models work seamlessly
order = client.models.SalesOrder(
OrderType="SO",
CustomerID="PYTHN001",
Details=[
client.models.SalesOrderDetail(
InventoryID="WIDGET",
Quantity=5
)
]
)
# Type hints work perfectly in your IDE
# Your editor knows all fields and their types!Dynamic Services
Service instances are created for each endpoint defined in your schema. They provide methods for CRUD operations, actions, and file attachments.
# Services provide intuitive CRUD operations
# GET - Retrieve by keys
customer = client.customers.get_by_id("PYTHN001")
# GET - With field selection
opts = QueryOptions(filter=F.CustomerID == "PYTHN001", select=["CustomerID","CustomerName","Balance"])
customer = client.customers.get_list(options=opts)
# LIST - Get multiple records
all_customers = client.customers.get_list()
# LIST - With OData parameters
from easy_acumatica.odata import QueryOptions, F
# CREATE - Add new record
new_customer = client.models.Customer(
CustomerID="PYTHN002",
CustomerName="Another Python Customer"
)
created = client.customers.put_entity(new_customer)
# UPDATE - Modify existing
customer.CustomerName = "Updated Name"
updated = client.customers.put_entity(customer)
# DELETE - Remove record
client.customers.delete_by_id("PYTHN002")Service Method Patterns
| Method Pattern | Description |
|---|---|
get_by_id(entity_id, select=None, expand=None) | Retrieve a record by ID |
get_list(filter=None, options=None) | List records with OData filtering |
put_entity(entity) | Create or update a record |
delete_by_id(entity_id) | Delete a record by ID |
put_file(entity_id, filename, data, comment=None) | Attach a file to a record |
get_files(entity_id) | List files attached to a record |
invoke_action_<action_name>(entity, parameters=None) | Execute an action (dynamically generated) |
OData Query Features
The client supports OData filtering and query options through the F filter factory and QueryOptions class.
from easy_acumatica.odata import F
# Simple equality filter
active_filter = F.Status == "Active"
# Comparison operators
high_value = F.Balance > 10000
recent = F.CreatedDate >= "2024-01-01"
# Logical operators
combined = (F.Status == "Active") & (F.Balance > 5000)
either_or = (F.Type == "Business") | (F.Type == "Corporate")
# String functions (lowercase for OData compatibility)
name_starts = F.CustomerName.tolower().startswith("acme")
contains_text = F.Description.contains("important")
name_ends = F.CustomerName.endswith("LLC")
# Date functions
this_year = F.CreatedDate.year() == 2024
this_month = F.OrderDate.month() == 3
# Navigation properties
has_email = F.MainContact.Email != None
contact_city = F.MainContact.Address.City == "New York"
# Complex nested conditions
complex_filter = (
((F.Status == "Active") | (F.Status == "OnHold")) &
(F.Balance > 1000) &
F.CustomerName.tolower().startswith("tech") &
(F.Terms == "30D" | F.Terms == "60D")
)
# Use with list operations
results = client.customers.get_list(filter=complex_filter)Session Management
Sessions can be persistent (default) or non-persistent. The client includes automatic retry logic for idle session timeouts.
# Default behavior - one login, multiple operations
client = AcumaticaClient(
base_url="https://your-instance.acumatica.com",
username="api_user",
password="secure_password",
tenant="Company"
# persistent_login=True is the default
)
# Session established once during __init__
# All operations use the same session
customers = client.customers.get_list()
orders = client.sales_orders.get_list()
invoices = client.invoices.get_list()
# Session automatically cleaned up on exitTask Scheduler
Version 0.5.10 adds a built-in task scheduler for running periodic jobs. Access it via client.scheduler property. See the Task Scheduler documentation for detailed information.
from easy_acumatica import AcumaticaClient
import time
client = AcumaticaClient()
# Define a task function
def sync_customers():
customers = client.customers.get_list()
# Process customers...
print(f"Synced {len(customers)} customers")
# Schedule task to run every 5 minutes
task_id = client.scheduler.schedule_task(
func=sync_customers,
interval_seconds=300, # 5 minutes
task_name="customer_sync"
)
# Schedule with a cron-like expression
task_id = client.scheduler.schedule_task(
func=sync_customers,
cron_expression="0 */6 * * *", # Every 6 hours
task_name="hourly_sync"
)
# List scheduled tasks
tasks = client.scheduler.list_tasks()
for task in tasks:
print(f"{task['name']}: {task['status']}")
# Stop a specific task
client.scheduler.stop_task(task_id)
# Stop all tasks
client.scheduler.stop_all()
# Scheduler automatically stops when client is closed
client.close()Exception Handling
The library defines specific exception types for authentication, network, timeout, and API errors.
from easy_acumatica import (
AcumaticaError,
AcumaticaAuthError,
AcumaticaNotFoundError,
AcumaticaValidationError,
AcumaticaBusinessRuleError,
AcumaticaConcurrencyError,
AcumaticaServerError,
AcumaticaConnectionError,
AcumaticaTimeoutError,
AcumaticaRateLimitError
)
try:
customer = client.customers.get_by_id("UNKNOWN")
except AcumaticaNotFoundError as e:
# 404 - Resource not found
print(f"Not found: {e}")
print(f"Suggestions: {e.suggestions}")
except AcumaticaAuthError as e:
# 401/403 - Authentication or authorization failed
print(f"Auth error: {e.message}")
print(f"Status: {e.status_code}")
except AcumaticaValidationError as e:
# 400/422 - Data validation failed
print(f"Validation error: {e}")
if e.field_errors:
for field, errors in e.field_errors.items():
print(f" {field}: {errors}")
except AcumaticaBusinessRuleError as e:
# 422 - Business rule violation
print(f"Business rule error: {e}")
except AcumaticaConcurrencyError as e:
# 412 - Concurrent modification
print(f"Concurrency conflict: {e}")
except AcumaticaTimeoutError as e:
# Request timeout
print(f"Timeout: {e.timeout_seconds}s")
except AcumaticaRateLimitError as e:
# 429 - Rate limit exceeded
print(f"Rate limited. Retry after: {e.retry_after}s")
except AcumaticaServerError as e:
# 5xx - Server error
print(f"Server error: {e.status_code}")
except AcumaticaConnectionError as e:
# Network/connection error
print(f"Connection error: {e}")
except AcumaticaError as e:
# Catch-all for any Acumatica error
print(f"Error: {e}")
print(f"Detailed: {e.get_detailed_message()}")
print(f"Context: {e.context}")