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.4+) |
auto_load_env | bool | Optional | Auto-search for .env files (default: True, v0.5.4+) |
cache_methods | bool | Optional | Enable differential caching (v0.5.4+) |
cache_ttl_hours | int | Optional | Cache TTL in hours (default: 24, v0.5.4+) |
cache_dir | Path | Optional | Cache directory (default: ~/.easy_acumatica_cache, v0.5.4+) |
force_rebuild | bool | Optional | Force rebuild ignoring cache (v0.5.4+) |
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.4, 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.4)
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.4 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
On initialization, the client fetches the OpenAPI schema and generates models and services based on your instance configuration.
# 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 methods: {service_info['methods']}")
# Get detailed information about a specific model
model_info = client.get_model_info('Customer')
print(f"\nCustomer model fields: {model_info['field_count']} fields")
# 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}")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.4 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}")