# py-autotask
A comprehensive Python SDK for the Autotask REST API providing **100% complete API coverage** with 193 entity implementations.
[](https://badge.fury.io/py/py-autotask)
[](https://pypi.org/project/py-autotask/)
[](https://opensource.org/licenses/MIT)
[](https://codecov.io/gh/asachs01/py-autotask)
[](docs/API_COVERAGE.md)
[](docs/API_COVERAGE.md)
## Features
- **๐ฏ 100% API Coverage** - Complete implementation of all 193 Autotask REST API entities
- **๐ Easy to Use** - Intuitive API that follows Python best practices
- **๐ Automatic Authentication** - Handles zone detection and authentication seamlessly
- **๐ Full CRUD Operations** - Create, Read, Update, Delete for all Autotask entities
- **๐ Advanced Query Builder** - Fluent API for complex filtering and relationship queries
- **๐๏ธ Parent-Child Relationships** - Built-in support for entity relationships and hierarchies
- **โก Batch Operations** - Efficient bulk operations for create, update, delete, and retrieve
- **๐ Enhanced Pagination** - Automatic pagination with safety limits and cursor support
- **โก Performance Optimized** - Intelligent retry logic and connection pooling
- **๐ก๏ธ Type Safe** - Full type hints for better IDE support and code reliability
- **๐งช Well Tested** - Comprehensive test suite with 82+ test methods
- **๐ฑ CLI Interface** - Command-line tool for quick operations
- **๐ Comprehensive Documentation** - Detailed examples and complete API reference
- **๐ผ Enterprise Ready** - Production-grade with extensive error handling and logging
## Quick Start
### Installation
```bash
pip install py-autotask
```
### Basic Usage
```python
from py_autotask import AutotaskClient
# Create client with credentials
client = AutotaskClient.create(
username="user@example.com",
integration_code="YOUR_INTEGRATION_CODE",
secret="YOUR_SECRET"
)
# Get a ticket
ticket = client.tickets.get(12345)
print(f"Ticket: {ticket['title']}")
# Query companies
companies = client.companies.query({
"filter": [{"op": "eq", "field": "isActive", "value": "true"}]
})
# Create a new contact
new_contact = client.contacts.create({
"firstName": "John",
"lastName": "Doe",
"emailAddress": "john.doe@example.com",
"companyID": 12345
})
```
### Environment Variables
You can also configure authentication using environment variables:
```bash
export AUTOTASK_USERNAME="user@example.com"
export AUTOTASK_INTEGRATION_CODE="YOUR_INTEGRATION_CODE"
export AUTOTASK_SECRET="YOUR_SECRET"
```
```python
from py_autotask import AutotaskClient
# Client will automatically use environment variables
client = AutotaskClient.from_env()
```
## CLI Usage
The library includes a powerful CLI for common operations:
```bash
# Test authentication
py-autotask auth
# Get a ticket
py-autotask get ticket 12345
# Query active companies
py-autotask query companies --filter '{"op": "eq", "field": "isActive", "value": "true"}'
# Get field information
py-autotask info Tickets
```
## Supported Entities
py-autotask supports all major Autotask entities:
- **Tickets** - Service desk tickets and related operations
- **Companies** - Customer and vendor company records
- **Contacts** - Individual contact records
- **Projects** - Project management and tracking
- **Resources** - User and technician records
- **Contracts** - Service contracts and agreements
- **Time Entries** - Time tracking and billing
- **Expenses** - Expense tracking and management
- **Products** - Product catalog and inventory
- **Services** - Service catalog management
## Advanced Features
### Advanced Query Builder
```python
from py_autotask.entities import FilterOperator
# Fluent query building with method chaining
tickets = (client.tickets.query_builder()
.where("status", FilterOperator.EQUAL, "1")
.where("priority", FilterOperator.GREATER_THAN_OR_EQUAL, 3)
.where("title", FilterOperator.CONTAINS, "urgent")
.select(["id", "title", "status", "priority"])
.limit(100)
.order_by("createDateTime", "desc")
.execute_all())
# Date range queries
recent_tickets = (client.tickets.query_builder()
.where_date_range("createDateTime", "2023-01-01T00:00:00Z", "2023-12-31T23:59:59Z")
.where_in("status", ["1", "2", "3"])
.execute())
# Relationship-based queries
tickets_for_acme = (client.tickets.query_builder()
.where_related("Companies", "companyName", "contains", "Acme")
.execute_all())
```
### Parent-Child Relationships
```python
# Get all tickets for a company
company_tickets = client.companies.get_children(12345, "Tickets")
# Get all active tickets for a project
active_tickets = client.projects.get_children(
67890,
"Tickets",
filters={"field": "status", "op": "eq", "value": "1"}
)
# Get parent company for a ticket
company = client.tickets.get_parent(12345, "Companies")
# Link entities
client.tickets.link_to_parent(ticket_id=1001, parent_id=123, parent_entity="Companies")
# Batch link multiple tickets to a company
client.companies.batch_link_children(12345, [1001, 1002, 1003], "Tickets")
```
### Batch Operations
```python
# Batch create multiple tickets
tickets_data = [
{"title": "Issue 1", "accountID": 123, "status": 1},
{"title": "Issue 2", "accountID": 123, "status": 1},
{"title": "Issue 3", "accountID": 456, "status": 1},
]
results = client.tickets.batch_create(tickets_data)
# Batch update
updates = [
{"id": 1001, "priority": 4},
{"id": 1002, "priority": 3},
{"id": 1003, "status": 2},
]
updated = client.tickets.batch_update(updates)
# Batch retrieve
tickets = client.tickets.batch_get([1001, 1002, 1003])
# Batch delete with results
result = client.tickets.batch_delete([1001, 1002, 1003])
print(f"Deleted: {result['success_count']}, Failed: {result['failure_count']}")
```
### Enhanced Pagination
```python
# Automatic pagination with safety limits
all_companies = client.companies.query_all(
filters={"field": "isActive", "op": "eq", "value": "true"},
max_total_records=10000, # Safety limit
page_size=500 # Records per page
)
# Check if records exist without retrieving them
has_urgent_tickets = (client.tickets.query_builder()
.where("priority", "gte", 4)
.exists())
# Get just the first matching record
first_urgent = (client.tickets.query_builder()
.where("priority", "gte", 4)
.order_by("createDateTime", "desc")
.first())
```
### Time Entry Management
Track time against tickets, projects, and tasks with comprehensive time management features:
```python
# Create time entries
time_entry = client.time_entries.create_time_entry(
resource_id=123,
ticket_id=12345,
start_date_time="2023-08-01T09:00:00",
end_date_time="2023-08-01T17:00:00",
hours_worked=8.0,
hours_to_bill=8.0,
description="Development work on feature X",
billable_to_account=True
)
# Get time entries for a resource
time_entries = client.time_entries.get_time_entries_by_resource(
resource_id=123,
start_date="2023-08-01",
end_date="2023-08-31"
)
# Get billable time for invoicing
billable_time = client.time_entries.get_billable_time_entries(
account_id=456,
start_date="2023-08-01",
end_date="2023-08-31"
)
# Time analytics and reporting
time_summary = client.time_entries.get_time_summary_by_resource(
resource_id=123,
start_date="2023-08-01",
end_date="2023-08-31"
)
print(f"Total hours: {time_summary['total_hours']}")
print(f"Billable hours: {time_summary['billable_hours']}")
print(f"Utilization: {time_summary['utilization_rate']}%")
# Time entry workflow management
client.time_entries.submit_time_entry(time_entry_id=789)
client.time_entries.approve_time_entry(time_entry_id=789, approver_notes="Approved for billing")
```
### Error Handling
```python
from py_autotask.exceptions import (
AutotaskAuthError,
AutotaskAPIError,
AutotaskRateLimitError
)
try:
ticket = client.tickets.get(12345)
except AutotaskAuthError:
print("Authentication failed - check credentials")
except AutotaskRateLimitError as e:
print(f"Rate limit exceeded, retry after {e.retry_after} seconds")
except AutotaskAPIError as e:
print(f"API error: {e.message}")
```
### Batch Operations
```python
# Bulk create
contacts_data = [
{"firstName": "John", "lastName": "Doe", "companyID": 123},
{"firstName": "Jane", "lastName": "Smith", "companyID": 123}
]
# Create multiple contacts
results = []
for contact_data in contacts_data:
result = client.contacts.create(contact_data)
results.append(result)
```
## Configuration
### Request Configuration
```python
from py_autotask.types import RequestConfig
config = RequestConfig(
timeout=60, # Request timeout in seconds
max_retries=5, # Maximum retry attempts
retry_delay=2.0, # Base retry delay
retry_backoff=2.0 # Exponential backoff multiplier
)
client = AutotaskClient(auth, config)
```
### Logging
```python
import logging
# Enable debug logging
logging.getLogger('py_autotask').setLevel(logging.DEBUG)
# Configure custom logging
logger = logging.getLogger('py_autotask.client')
logger.addHandler(logging.FileHandler('autotask.log'))
```
## Development
### Setup Development Environment
```bash
# Clone the repository
git clone https://github.com/asachs01/py-autotask.git
cd py-autotask
# Create virtual environment
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
# Install development dependencies
pip install -e ".[dev]"
# Install pre-commit hooks
pre-commit install
```
### Running Tests
```bash
# Run all tests
pytest
# Run with coverage
pytest --cov=py_autotask --cov-report=html
# Run specific test file
pytest tests/test_client.py
# Run integration tests (requires API credentials)
pytest tests/integration/ --integration
```
### Code Quality
```bash
# Format code
black py_autotask tests
# Sort imports
isort py_autotask tests
# Lint code
flake8 py_autotask tests
# Type checking
mypy py_autotask
```
## API Reference
For detailed API documentation, visit [our documentation site](https://py-autotask.readthedocs.io/).
### Core Classes
- **AutotaskClient** - Main client class for API operations
- **AutotaskAuth** - Authentication and zone detection
- **BaseEntity** - Base class for all entity operations
- **EntityManager** - Factory for entity handlers
### Exception Classes
- **AutotaskError** - Base exception class
- **AutotaskAPIError** - HTTP/API related errors
- **AutotaskAuthError** - Authentication failures
- **AutotaskValidationError** - Data validation errors
- **AutotaskRateLimitError** - Rate limiting errors
## Migration Guide
### From autotask-node (Node.js)
py-autotask provides similar functionality to the popular Node.js autotask library:
```javascript
// Node.js (autotask-node)
const autotask = require('autotask-node');
const at = new autotask(username, integration, secret);
at.tickets.get(12345).then(ticket => {
console.log(ticket.title);
});
```
```python
# Python (py-autotask)
from py_autotask import AutotaskClient
client = AutotaskClient.create(username, integration, secret)
ticket = client.tickets.get(12345)
print(ticket['title'])
```
## Contributing
We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
### Reporting Issues
- Use the [GitHub Issues](https://github.com/asachs01/py-autotask/issues) page
- Include Python version, library version, and error details
- Provide minimal reproduction code when possible
### Feature Requests
- Open an issue with the "enhancement" label
- Describe the use case and expected behavior
- Include relevant Autotask API documentation references
## Changelog
See [CHANGELOG.md](CHANGELOG.md) for version history and changes.
## License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
## Support
- ๐ [Documentation](https://py-autotask.readthedocs.io/)
- ๐ฌ [GitHub Discussions](https://github.com/asachs01/py-autotask/discussions)
- ๐ [Issue Tracker](https://github.com/asachs01/py-autotask/issues)
- ๐ง [Email Support](mailto:support@py-autotask.dev)
## Acknowledgments
- Autotask API team for excellent documentation
- Contributors to the autotask-node library for inspiration
- Python community for amazing libraries and tools
---
**Disclaimer**: This library is not officially affiliated with Datto/Autotask. It is an independent implementation of the Autotask REST API.
## Phase 4: Advanced Features
### Batch Operations
Efficiently process multiple entities with built-in batch support:
```python
# Batch create tickets
tickets_data = [
{
"title": "Server Down - Critical",
"description": "Production server unresponsive",
"accountID": 123,
"priority": 1,
"status": 1
},
{
"title": "Email Issues",
"description": "Users unable to send email",
"accountID": 123,
"priority": 2,
"status": 1
}
]
# Create multiple tickets in batches
results = client.tickets.batch_create(tickets_data, batch_size=200)
for result in results:
if result.item_id:
print(f"Created ticket: {result.item_id}")
else:
print(f"Failed to create ticket: {result.errors}")
# Batch update multiple entities
updates = [
{"id": 12345, "priority": 1, "status": 8}, # Set to high priority, in progress
{"id": 12346, "priority": 3, "status": 5} # Set to medium priority, complete
]
updated_tickets = client.tickets.batch_update(updates)
print(f"Updated {len(updated_tickets)} tickets")
# Batch delete entities (with confirmation)
ticket_ids = [12347, 12348, 12349]
deletion_results = client.tickets.batch_delete(ticket_ids)
successful_deletions = sum(deletion_results)
print(f"Deleted {successful_deletions}/{len(ticket_ids)} tickets")
# Batch operations work with all entities
company_updates = [
{"id": 1001, "isActive": True},
{"id": 1002, "isActive": False}
]
client.companies.batch_update(company_updates)
# Low-level batch operations (direct client access)
results = client.batch_create("Projects", project_data_list, batch_size=100)
updated = client.batch_update("Contracts", contract_updates, batch_size=50)
deleted = client.batch_delete("TimeEntries", time_entry_ids, batch_size=200)
```
### File Attachment Management
Upload, download, and manage file attachments for any entity:
```python
# Upload a file to a ticket
attachment = client.attachments.upload_file(
parent_type="Ticket",
parent_id=12345,
file_path="/path/to/screenshot.png",
title="Error Screenshot",
description="Screenshot showing the error state"
)
print(f"Uploaded attachment with ID: {attachment.id}")
# Upload from memory/data
with open("/path/to/document.pdf", "rb") as f:
file_data = f.read()
attachment = client.attachments.upload_from_data(
parent_type="Project",
parent_id=67890,
file_data=file_data,
filename="project_spec.pdf",
content_type="application/pdf",
title="Project Specification",
description="Detailed project requirements"
)
# Download attachments
file_data = client.attachments.download_file(
attachment_id=attachment.id,
output_path="/path/to/downloads/project_spec.pdf"
)
# List all attachments for an entity
attachments = client.attachments.get_attachments_for_entity(
parent_type="Ticket",
parent_id=12345
)
for attachment in attachments:
print(f"ID: {attachment.id}")
print(f" File: {attachment.file_name}")
print(f" Size: {attachment.file_size} bytes")
print(f" Type: {attachment.content_type}")
print(f" Title: {attachment.title}")
# Batch upload multiple files
file_paths = [
"/path/to/log1.txt",
"/path/to/log2.txt",
"/path/to/config.xml"
]
uploaded_attachments = client.attachments.batch_upload(
parent_type="Ticket",
parent_id=12345,
file_paths=file_paths,
batch_size=5 # Upload 5 files concurrently
)
print(f"Successfully uploaded {len(uploaded_attachments)} files")
# Delete attachments
client.attachments.delete_attachment(attachment_id=12345)
# Get attachment metadata only (without downloading)
attachment_info = client.attachments.get_attachment_info(attachment_id=12345)
if attachment_info:
print(f"Attachment exists: {attachment_info.file_name}")
```
### Enhanced CLI Interface
The CLI now supports batch operations and attachment management:
```bash
# Batch operations
py-autotask batch create Tickets tickets.json --batch-size 100
py-autotask batch update Companies company_updates.json --output summary
py-autotask batch delete Tickets --ids-file ticket_ids.txt --confirm
# With inline IDs
py-autotask batch delete Projects 1001 1002 1003 --confirm
# Attachment operations
py-autotask attachments upload Ticket 12345 /path/to/file.pdf --title "Documentation"
py-autotask attachments download 67890 /path/to/downloads/file.pdf
py-autotask attachments list Ticket 12345 --output table
py-autotask attachments delete-attachment 67890 --confirm
# Batch create example with JSON file
echo '[
{"title": "Issue 1", "description": "First issue", "accountID": 123},
{"title": "Issue 2", "description": "Second issue", "accountID": 123}
]' > tickets.json
py-autotask batch create Tickets tickets.json
```
## Performance Optimization
Phase 4 includes several performance improvements:
### Intelligent Batching
- Automatic batch size optimization (up to API limit of 200)
- Parallel processing where possible
- Progress tracking for large operations
- Graceful error handling with partial success reporting
### Connection Pooling
- HTTP session reuse for multiple requests
- Configurable connection timeouts and retries
- Built-in rate limiting awareness
### Memory Efficiency
- Streaming file uploads/downloads for large attachments
- Lazy loading of entity relationships
- Efficient pagination for large result sets
```python
# Configure performance settings
from py_autotask.types import RequestConfig
config = RequestConfig(
timeout=30, # Request timeout in seconds
max_retries=3, # Maximum retry attempts
retry_backoff=1.0, # Backoff factor for retries
max_records=1000 # Default pagination limit
)
client = AutotaskClient.create(
username="user@example.com",
integration_code="YOUR_CODE",
secret="YOUR_SECRET",
config=config
)
# Batch operations automatically optimize performance
large_dataset = client.tickets.query({
"filter": [{"op": "gte", "field": "createDate", "value": "2023-01-01"}]
}) # Handles pagination automatically
# Stream large file uploads
large_attachment = client.attachments.upload_file(
parent_type="Project",
parent_id=12345,
file_path="/path/to/large_file.zip" # Efficient streaming upload
)
```
Raw data
{
"_id": null,
"home_page": "https://github.com/asachs01/py-autotask",
"name": "py-autotask",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.8",
"maintainer_email": "Aaron Sachs <dev@sachshaus.net>",
"keywords": "autotask, psa, api, rest, integration",
"author": "Aaron Sachs",
"author_email": "Aaron Sachs <dev@sachshaus.net>",
"download_url": "https://files.pythonhosted.org/packages/87/aa/d99e5a4b454b9125cf8c514b78dc1ed1e242929fc77d133f239ebae1943f/py_autotask-1.0.3.tar.gz",
"platform": null,
"description": "# py-autotask\n\nA comprehensive Python SDK for the Autotask REST API providing **100% complete API coverage** with 193 entity implementations.\n\n[](https://badge.fury.io/py/py-autotask)\n[](https://pypi.org/project/py-autotask/)\n[](https://opensource.org/licenses/MIT)\n[](https://codecov.io/gh/asachs01/py-autotask)\n[](docs/API_COVERAGE.md)\n[](docs/API_COVERAGE.md)\n\n## Features\n\n- **\ud83c\udfaf 100% API Coverage** - Complete implementation of all 193 Autotask REST API entities\n- **\ud83d\ude80 Easy to Use** - Intuitive API that follows Python best practices\n- **\ud83d\udd10 Automatic Authentication** - Handles zone detection and authentication seamlessly \n- **\ud83d\udcca Full CRUD Operations** - Create, Read, Update, Delete for all Autotask entities\n- **\ud83d\udd0d Advanced Query Builder** - Fluent API for complex filtering and relationship queries\n- **\ud83c\udfd7\ufe0f Parent-Child Relationships** - Built-in support for entity relationships and hierarchies\n- **\u26a1 Batch Operations** - Efficient bulk operations for create, update, delete, and retrieve\n- **\ud83d\udcc4 Enhanced Pagination** - Automatic pagination with safety limits and cursor support\n- **\u26a1 Performance Optimized** - Intelligent retry logic and connection pooling\n- **\ud83d\udee1\ufe0f Type Safe** - Full type hints for better IDE support and code reliability\n- **\ud83e\uddea Well Tested** - Comprehensive test suite with 82+ test methods\n- **\ud83d\udcf1 CLI Interface** - Command-line tool for quick operations\n- **\ud83d\udcda Comprehensive Documentation** - Detailed examples and complete API reference\n- **\ud83d\udcbc Enterprise Ready** - Production-grade with extensive error handling and logging\n\n## Quick Start\n\n### Installation\n\n```bash\npip install py-autotask\n```\n\n### Basic Usage\n\n```python\nfrom py_autotask import AutotaskClient\n\n# Create client with credentials\nclient = AutotaskClient.create(\n username=\"user@example.com\",\n integration_code=\"YOUR_INTEGRATION_CODE\", \n secret=\"YOUR_SECRET\"\n)\n\n# Get a ticket\nticket = client.tickets.get(12345)\nprint(f\"Ticket: {ticket['title']}\")\n\n# Query companies\ncompanies = client.companies.query({\n \"filter\": [{\"op\": \"eq\", \"field\": \"isActive\", \"value\": \"true\"}]\n})\n\n# Create a new contact\nnew_contact = client.contacts.create({\n \"firstName\": \"John\",\n \"lastName\": \"Doe\", \n \"emailAddress\": \"john.doe@example.com\",\n \"companyID\": 12345\n})\n```\n\n### Environment Variables\n\nYou can also configure authentication using environment variables:\n\n```bash\nexport AUTOTASK_USERNAME=\"user@example.com\"\nexport AUTOTASK_INTEGRATION_CODE=\"YOUR_INTEGRATION_CODE\"\nexport AUTOTASK_SECRET=\"YOUR_SECRET\"\n```\n\n```python\nfrom py_autotask import AutotaskClient\n\n# Client will automatically use environment variables\nclient = AutotaskClient.from_env()\n```\n\n## CLI Usage\n\nThe library includes a powerful CLI for common operations:\n\n```bash\n# Test authentication\npy-autotask auth\n\n# Get a ticket\npy-autotask get ticket 12345\n\n# Query active companies\npy-autotask query companies --filter '{\"op\": \"eq\", \"field\": \"isActive\", \"value\": \"true\"}'\n\n# Get field information\npy-autotask info Tickets\n```\n\n## Supported Entities\n\npy-autotask supports all major Autotask entities:\n\n- **Tickets** - Service desk tickets and related operations\n- **Companies** - Customer and vendor company records \n- **Contacts** - Individual contact records\n- **Projects** - Project management and tracking\n- **Resources** - User and technician records\n- **Contracts** - Service contracts and agreements\n- **Time Entries** - Time tracking and billing\n- **Expenses** - Expense tracking and management\n- **Products** - Product catalog and inventory\n- **Services** - Service catalog management\n\n## Advanced Features\n\n### Advanced Query Builder\n\n```python\nfrom py_autotask.entities import FilterOperator\n\n# Fluent query building with method chaining\ntickets = (client.tickets.query_builder()\n .where(\"status\", FilterOperator.EQUAL, \"1\")\n .where(\"priority\", FilterOperator.GREATER_THAN_OR_EQUAL, 3)\n .where(\"title\", FilterOperator.CONTAINS, \"urgent\")\n .select([\"id\", \"title\", \"status\", \"priority\"])\n .limit(100)\n .order_by(\"createDateTime\", \"desc\")\n .execute_all())\n\n# Date range queries\nrecent_tickets = (client.tickets.query_builder()\n .where_date_range(\"createDateTime\", \"2023-01-01T00:00:00Z\", \"2023-12-31T23:59:59Z\")\n .where_in(\"status\", [\"1\", \"2\", \"3\"])\n .execute())\n\n# Relationship-based queries\ntickets_for_acme = (client.tickets.query_builder()\n .where_related(\"Companies\", \"companyName\", \"contains\", \"Acme\")\n .execute_all())\n```\n\n### Parent-Child Relationships\n\n```python\n# Get all tickets for a company\ncompany_tickets = client.companies.get_children(12345, \"Tickets\")\n\n# Get all active tickets for a project \nactive_tickets = client.projects.get_children(\n 67890, \n \"Tickets\", \n filters={\"field\": \"status\", \"op\": \"eq\", \"value\": \"1\"}\n)\n\n# Get parent company for a ticket\ncompany = client.tickets.get_parent(12345, \"Companies\")\n\n# Link entities\nclient.tickets.link_to_parent(ticket_id=1001, parent_id=123, parent_entity=\"Companies\")\n\n# Batch link multiple tickets to a company\nclient.companies.batch_link_children(12345, [1001, 1002, 1003], \"Tickets\")\n```\n\n### Batch Operations\n\n```python\n# Batch create multiple tickets\ntickets_data = [\n {\"title\": \"Issue 1\", \"accountID\": 123, \"status\": 1},\n {\"title\": \"Issue 2\", \"accountID\": 123, \"status\": 1},\n {\"title\": \"Issue 3\", \"accountID\": 456, \"status\": 1},\n]\nresults = client.tickets.batch_create(tickets_data)\n\n# Batch update\nupdates = [\n {\"id\": 1001, \"priority\": 4},\n {\"id\": 1002, \"priority\": 3},\n {\"id\": 1003, \"status\": 2},\n]\nupdated = client.tickets.batch_update(updates)\n\n# Batch retrieve\ntickets = client.tickets.batch_get([1001, 1002, 1003])\n\n# Batch delete with results\nresult = client.tickets.batch_delete([1001, 1002, 1003])\nprint(f\"Deleted: {result['success_count']}, Failed: {result['failure_count']}\")\n```\n\n### Enhanced Pagination\n\n```python\n# Automatic pagination with safety limits\nall_companies = client.companies.query_all(\n filters={\"field\": \"isActive\", \"op\": \"eq\", \"value\": \"true\"},\n max_total_records=10000, # Safety limit\n page_size=500 # Records per page\n)\n\n# Check if records exist without retrieving them\nhas_urgent_tickets = (client.tickets.query_builder()\n .where(\"priority\", \"gte\", 4)\n .exists())\n\n# Get just the first matching record\nfirst_urgent = (client.tickets.query_builder()\n .where(\"priority\", \"gte\", 4)\n .order_by(\"createDateTime\", \"desc\")\n .first())\n```\n\n### Time Entry Management\n\nTrack time against tickets, projects, and tasks with comprehensive time management features:\n\n```python\n# Create time entries\ntime_entry = client.time_entries.create_time_entry(\n resource_id=123,\n ticket_id=12345,\n start_date_time=\"2023-08-01T09:00:00\",\n end_date_time=\"2023-08-01T17:00:00\",\n hours_worked=8.0,\n hours_to_bill=8.0,\n description=\"Development work on feature X\",\n billable_to_account=True\n)\n\n# Get time entries for a resource\ntime_entries = client.time_entries.get_time_entries_by_resource(\n resource_id=123,\n start_date=\"2023-08-01\",\n end_date=\"2023-08-31\"\n)\n\n# Get billable time for invoicing\nbillable_time = client.time_entries.get_billable_time_entries(\n account_id=456,\n start_date=\"2023-08-01\",\n end_date=\"2023-08-31\"\n)\n\n# Time analytics and reporting\ntime_summary = client.time_entries.get_time_summary_by_resource(\n resource_id=123,\n start_date=\"2023-08-01\",\n end_date=\"2023-08-31\"\n)\nprint(f\"Total hours: {time_summary['total_hours']}\")\nprint(f\"Billable hours: {time_summary['billable_hours']}\")\nprint(f\"Utilization: {time_summary['utilization_rate']}%\")\n\n# Time entry workflow management\nclient.time_entries.submit_time_entry(time_entry_id=789)\nclient.time_entries.approve_time_entry(time_entry_id=789, approver_notes=\"Approved for billing\")\n```\n\n### Error Handling\n\n```python\nfrom py_autotask.exceptions import (\n AutotaskAuthError,\n AutotaskAPIError,\n AutotaskRateLimitError\n)\n\ntry:\n ticket = client.tickets.get(12345)\nexcept AutotaskAuthError:\n print(\"Authentication failed - check credentials\")\nexcept AutotaskRateLimitError as e:\n print(f\"Rate limit exceeded, retry after {e.retry_after} seconds\")\nexcept AutotaskAPIError as e:\n print(f\"API error: {e.message}\")\n```\n\n### Batch Operations\n\n```python\n# Bulk create\ncontacts_data = [\n {\"firstName\": \"John\", \"lastName\": \"Doe\", \"companyID\": 123},\n {\"firstName\": \"Jane\", \"lastName\": \"Smith\", \"companyID\": 123}\n]\n\n# Create multiple contacts\nresults = []\nfor contact_data in contacts_data:\n result = client.contacts.create(contact_data)\n results.append(result)\n```\n\n## Configuration\n\n### Request Configuration\n\n```python\nfrom py_autotask.types import RequestConfig\n\nconfig = RequestConfig(\n timeout=60, # Request timeout in seconds\n max_retries=5, # Maximum retry attempts\n retry_delay=2.0, # Base retry delay\n retry_backoff=2.0 # Exponential backoff multiplier\n)\n\nclient = AutotaskClient(auth, config)\n```\n\n### Logging\n\n```python\nimport logging\n\n# Enable debug logging\nlogging.getLogger('py_autotask').setLevel(logging.DEBUG)\n\n# Configure custom logging\nlogger = logging.getLogger('py_autotask.client')\nlogger.addHandler(logging.FileHandler('autotask.log'))\n```\n\n## Development\n\n### Setup Development Environment\n\n```bash\n# Clone the repository\ngit clone https://github.com/asachs01/py-autotask.git\ncd py-autotask\n\n# Create virtual environment\npython -m venv venv\nsource venv/bin/activate # On Windows: venv\\Scripts\\activate\n\n# Install development dependencies\npip install -e \".[dev]\"\n\n# Install pre-commit hooks\npre-commit install\n```\n\n### Running Tests\n\n```bash\n# Run all tests\npytest\n\n# Run with coverage\npytest --cov=py_autotask --cov-report=html\n\n# Run specific test file\npytest tests/test_client.py\n\n# Run integration tests (requires API credentials)\npytest tests/integration/ --integration\n```\n\n### Code Quality\n\n```bash\n# Format code\nblack py_autotask tests\n\n# Sort imports\nisort py_autotask tests\n\n# Lint code\nflake8 py_autotask tests\n\n# Type checking\nmypy py_autotask\n```\n\n## API Reference\n\nFor detailed API documentation, visit [our documentation site](https://py-autotask.readthedocs.io/).\n\n### Core Classes\n\n- **AutotaskClient** - Main client class for API operations\n- **AutotaskAuth** - Authentication and zone detection\n- **BaseEntity** - Base class for all entity operations\n- **EntityManager** - Factory for entity handlers\n\n### Exception Classes\n\n- **AutotaskError** - Base exception class\n- **AutotaskAPIError** - HTTP/API related errors\n- **AutotaskAuthError** - Authentication failures\n- **AutotaskValidationError** - Data validation errors\n- **AutotaskRateLimitError** - Rate limiting errors\n\n## Migration Guide\n\n### From autotask-node (Node.js)\n\npy-autotask provides similar functionality to the popular Node.js autotask library:\n\n```javascript\n// Node.js (autotask-node)\nconst autotask = require('autotask-node');\nconst at = new autotask(username, integration, secret);\n\nat.tickets.get(12345).then(ticket => {\n console.log(ticket.title);\n});\n```\n\n```python\n# Python (py-autotask)\nfrom py_autotask import AutotaskClient\n\nclient = AutotaskClient.create(username, integration, secret)\nticket = client.tickets.get(12345)\nprint(ticket['title'])\n```\n\n## Contributing\n\nWe welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.\n\n### Reporting Issues\n\n- Use the [GitHub Issues](https://github.com/asachs01/py-autotask/issues) page\n- Include Python version, library version, and error details\n- Provide minimal reproduction code when possible\n\n### Feature Requests\n\n- Open an issue with the \"enhancement\" label\n- Describe the use case and expected behavior\n- Include relevant Autotask API documentation references\n\n## Changelog\n\nSee [CHANGELOG.md](CHANGELOG.md) for version history and changes.\n\n## License\n\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.\n\n## Support\n\n- \ud83d\udcd6 [Documentation](https://py-autotask.readthedocs.io/)\n- \ud83d\udcac [GitHub Discussions](https://github.com/asachs01/py-autotask/discussions)\n- \ud83d\udc1b [Issue Tracker](https://github.com/asachs01/py-autotask/issues)\n- \ud83d\udce7 [Email Support](mailto:support@py-autotask.dev)\n\n## Acknowledgments\n\n- Autotask API team for excellent documentation\n- Contributors to the autotask-node library for inspiration\n- Python community for amazing libraries and tools\n\n---\n\n**Disclaimer**: This library is not officially affiliated with Datto/Autotask. It is an independent implementation of the Autotask REST API. \n\n## Phase 4: Advanced Features\n\n### Batch Operations\n\nEfficiently process multiple entities with built-in batch support:\n\n```python\n# Batch create tickets\ntickets_data = [\n {\n \"title\": \"Server Down - Critical\",\n \"description\": \"Production server unresponsive\",\n \"accountID\": 123,\n \"priority\": 1,\n \"status\": 1\n },\n {\n \"title\": \"Email Issues\",\n \"description\": \"Users unable to send email\",\n \"accountID\": 123,\n \"priority\": 2,\n \"status\": 1\n }\n]\n\n# Create multiple tickets in batches\nresults = client.tickets.batch_create(tickets_data, batch_size=200)\nfor result in results:\n if result.item_id:\n print(f\"Created ticket: {result.item_id}\")\n else:\n print(f\"Failed to create ticket: {result.errors}\")\n\n# Batch update multiple entities\nupdates = [\n {\"id\": 12345, \"priority\": 1, \"status\": 8}, # Set to high priority, in progress\n {\"id\": 12346, \"priority\": 3, \"status\": 5} # Set to medium priority, complete\n]\n\nupdated_tickets = client.tickets.batch_update(updates)\nprint(f\"Updated {len(updated_tickets)} tickets\")\n\n# Batch delete entities (with confirmation)\nticket_ids = [12347, 12348, 12349]\ndeletion_results = client.tickets.batch_delete(ticket_ids)\nsuccessful_deletions = sum(deletion_results)\nprint(f\"Deleted {successful_deletions}/{len(ticket_ids)} tickets\")\n\n# Batch operations work with all entities\ncompany_updates = [\n {\"id\": 1001, \"isActive\": True},\n {\"id\": 1002, \"isActive\": False}\n]\nclient.companies.batch_update(company_updates)\n\n# Low-level batch operations (direct client access)\nresults = client.batch_create(\"Projects\", project_data_list, batch_size=100)\nupdated = client.batch_update(\"Contracts\", contract_updates, batch_size=50)\ndeleted = client.batch_delete(\"TimeEntries\", time_entry_ids, batch_size=200)\n```\n\n### File Attachment Management\n\nUpload, download, and manage file attachments for any entity:\n\n```python\n# Upload a file to a ticket\nattachment = client.attachments.upload_file(\n parent_type=\"Ticket\",\n parent_id=12345,\n file_path=\"/path/to/screenshot.png\",\n title=\"Error Screenshot\",\n description=\"Screenshot showing the error state\"\n)\nprint(f\"Uploaded attachment with ID: {attachment.id}\")\n\n# Upload from memory/data\nwith open(\"/path/to/document.pdf\", \"rb\") as f:\n file_data = f.read()\n\nattachment = client.attachments.upload_from_data(\n parent_type=\"Project\",\n parent_id=67890,\n file_data=file_data,\n filename=\"project_spec.pdf\",\n content_type=\"application/pdf\",\n title=\"Project Specification\",\n description=\"Detailed project requirements\"\n)\n\n# Download attachments\nfile_data = client.attachments.download_file(\n attachment_id=attachment.id,\n output_path=\"/path/to/downloads/project_spec.pdf\"\n)\n\n# List all attachments for an entity\nattachments = client.attachments.get_attachments_for_entity(\n parent_type=\"Ticket\",\n parent_id=12345\n)\n\nfor attachment in attachments:\n print(f\"ID: {attachment.id}\")\n print(f\" File: {attachment.file_name}\")\n print(f\" Size: {attachment.file_size} bytes\")\n print(f\" Type: {attachment.content_type}\")\n print(f\" Title: {attachment.title}\")\n\n# Batch upload multiple files\nfile_paths = [\n \"/path/to/log1.txt\",\n \"/path/to/log2.txt\", \n \"/path/to/config.xml\"\n]\n\nuploaded_attachments = client.attachments.batch_upload(\n parent_type=\"Ticket\",\n parent_id=12345,\n file_paths=file_paths,\n batch_size=5 # Upload 5 files concurrently\n)\n\nprint(f\"Successfully uploaded {len(uploaded_attachments)} files\")\n\n# Delete attachments\nclient.attachments.delete_attachment(attachment_id=12345)\n\n# Get attachment metadata only (without downloading)\nattachment_info = client.attachments.get_attachment_info(attachment_id=12345)\nif attachment_info:\n print(f\"Attachment exists: {attachment_info.file_name}\")\n```\n\n### Enhanced CLI Interface\n\nThe CLI now supports batch operations and attachment management:\n\n```bash\n# Batch operations\npy-autotask batch create Tickets tickets.json --batch-size 100\npy-autotask batch update Companies company_updates.json --output summary\npy-autotask batch delete Tickets --ids-file ticket_ids.txt --confirm\n\n# With inline IDs\npy-autotask batch delete Projects 1001 1002 1003 --confirm\n\n# Attachment operations\npy-autotask attachments upload Ticket 12345 /path/to/file.pdf --title \"Documentation\"\npy-autotask attachments download 67890 /path/to/downloads/file.pdf\npy-autotask attachments list Ticket 12345 --output table\npy-autotask attachments delete-attachment 67890 --confirm\n\n# Batch create example with JSON file\necho '[\n {\"title\": \"Issue 1\", \"description\": \"First issue\", \"accountID\": 123},\n {\"title\": \"Issue 2\", \"description\": \"Second issue\", \"accountID\": 123}\n]' > tickets.json\n\npy-autotask batch create Tickets tickets.json\n```\n\n## Performance Optimization\n\nPhase 4 includes several performance improvements:\n\n### Intelligent Batching\n- Automatic batch size optimization (up to API limit of 200)\n- Parallel processing where possible\n- Progress tracking for large operations\n- Graceful error handling with partial success reporting\n\n### Connection Pooling\n- HTTP session reuse for multiple requests\n- Configurable connection timeouts and retries\n- Built-in rate limiting awareness\n\n### Memory Efficiency\n- Streaming file uploads/downloads for large attachments\n- Lazy loading of entity relationships\n- Efficient pagination for large result sets\n\n```python\n# Configure performance settings\nfrom py_autotask.types import RequestConfig\n\nconfig = RequestConfig(\n timeout=30, # Request timeout in seconds\n max_retries=3, # Maximum retry attempts\n retry_backoff=1.0, # Backoff factor for retries\n max_records=1000 # Default pagination limit\n)\n\nclient = AutotaskClient.create(\n username=\"user@example.com\",\n integration_code=\"YOUR_CODE\", \n secret=\"YOUR_SECRET\",\n config=config\n)\n\n# Batch operations automatically optimize performance\nlarge_dataset = client.tickets.query({\n \"filter\": [{\"op\": \"gte\", \"field\": \"createDate\", \"value\": \"2023-01-01\"}]\n}) # Handles pagination automatically\n\n# Stream large file uploads\nlarge_attachment = client.attachments.upload_file(\n parent_type=\"Project\",\n parent_id=12345,\n file_path=\"/path/to/large_file.zip\" # Efficient streaming upload\n)\n``` \n",
"bugtrack_url": null,
"license": "MIT",
"summary": "Python library for Autotask PSA REST API integration",
"version": "1.0.3",
"project_urls": {
"Changelog": "https://github.com/asachs01/py-autotask/blob/main/CHANGELOG.md",
"Documentation": "https://py-autotask.readthedocs.io/",
"Homepage": "https://github.com/asachs01/py-autotask",
"Issues": "https://github.com/asachs01/py-autotask/issues",
"Repository": "https://github.com/asachs01/py-autotask.git"
},
"split_keywords": [
"autotask",
" psa",
" api",
" rest",
" integration"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "514ddec47422367e786f584fc6b1635c1134e27b1957a396a63cb7128752b246",
"md5": "0cbf36951ea33e81d986a9c76c8471c6",
"sha256": "85aaeaca5ebfe2ff9f8b6a8addab7ab65c4a18ae44424a9ffc6c7b7ae0d7aad2"
},
"downloads": -1,
"filename": "py_autotask-1.0.3-py3-none-any.whl",
"has_sig": false,
"md5_digest": "0cbf36951ea33e81d986a9c76c8471c6",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.8",
"size": 814624,
"upload_time": "2025-08-31T03:07:54",
"upload_time_iso_8601": "2025-08-31T03:07:54.610094Z",
"url": "https://files.pythonhosted.org/packages/51/4d/dec47422367e786f584fc6b1635c1134e27b1957a396a63cb7128752b246/py_autotask-1.0.3-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "87aad99e5a4b454b9125cf8c514b78dc1ed1e242929fc77d133f239ebae1943f",
"md5": "662a2d90e7acc9a431209e299c4daf2a",
"sha256": "cd7a96a06b586f60bdd127ad942ef3e505396ae17cfc5ec4e022586ab2e6522d"
},
"downloads": -1,
"filename": "py_autotask-1.0.3.tar.gz",
"has_sig": false,
"md5_digest": "662a2d90e7acc9a431209e299c4daf2a",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.8",
"size": 718732,
"upload_time": "2025-08-31T03:07:56",
"upload_time_iso_8601": "2025-08-31T03:07:56.854040Z",
"url": "https://files.pythonhosted.org/packages/87/aa/d99e5a4b454b9125cf8c514b78dc1ed1e242929fc77d133f239ebae1943f/py_autotask-1.0.3.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-08-31 03:07:56",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "asachs01",
"github_project": "py-autotask",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"requirements": [
{
"name": "requests",
"specs": [
[
">=",
"2.31.0"
]
]
},
{
"name": "pydantic",
"specs": [
[
">=",
"2.0.0"
]
]
},
{
"name": "click",
"specs": [
[
">=",
"8.0.0"
]
]
},
{
"name": "python-dotenv",
"specs": [
[
">=",
"1.0.0"
]
]
},
{
"name": "tenacity",
"specs": [
[
">=",
"8.0.0"
]
]
},
{
"name": "httpx",
"specs": [
[
">=",
"0.24.0"
]
]
},
{
"name": "typing-extensions",
"specs": [
[
">=",
"4.0.0"
]
]
},
{
"name": "aiohttp",
"specs": [
[
">=",
"3.8.0"
]
]
},
{
"name": "redis",
"specs": [
[
">=",
"4.5.0"
]
]
},
{
"name": "pandas",
"specs": [
[
">=",
"2.0.0"
]
]
},
{
"name": "openpyxl",
"specs": [
[
">=",
"3.1.0"
]
]
},
{
"name": "pyarrow",
"specs": [
[
">=",
"12.0.0"
]
]
},
{
"name": "tqdm",
"specs": [
[
">=",
"4.65.0"
]
]
}
],
"lcname": "py-autotask"
}