# EspoCRM Python Client
<div align="center">
[](https://www.python.org)
[](LICENSE)
[](https://www.espocrm.com)
**Modern, Type-Safe, Production-Ready Python Client for EspoCRM API**
[Installation](#installation) โข [Quick Start](#quick-start) โข [Documentation](#documentation) โข [Examples](#examples) โข [API Reference](#api-reference)
</div>
---
## ๐ Table of Contents
- [Features](#features)
- [Installation](#installation)
- [Quick Start](#quick-start)
- [Authentication](#authentication)
- [CRUD Operations](#crud-operations)
- [Advanced Features](#advanced-features)
- [Error Handling](#error-handling)
- [Configuration](#configuration)
- [Testing](#testing)
- [API Reference](#api-reference)
- [Contributing](#contributing)
- [License](#license)
---
## โจ Features
### Core Features
- ๐ **Multiple Authentication Methods**: API Key, HMAC, Basic Auth
- ๐ **Full CRUD Operations**: Create, Read, Update, Delete, List, Search
- ๐ **Automatic Retry Logic**: With exponential backoff
- ๐ฏ **Type Safety**: Full type hints and Pydantic models
- ๐ **Async Support**: Coming soon
- ๐ **Bulk Operations**: Efficient batch processing
- ๐ **Advanced Search**: Complex queries with SearchParams
- โก **Connection Pooling**: Optimized HTTP connections
- ๐ก๏ธ **Comprehensive Error Handling**: Detailed exceptions
- ๐ **Rate Limiting**: Built-in rate limit management
- ๐งช **Well Tested**: Extensive test coverage
### Architecture
- **Modular Design**: Separate modules for auth, models, utils
- **Interceptor Pattern**: Request/Response interceptors
- **Factory Pattern**: Entity factories
- **Builder Pattern**: Query builders
- **Strategy Pattern**: Authentication strategies
---
## ๐ฆ Installation
### From PyPI (Recommended)
```bash
pip install espocrm-python-client
```
### From Source
```bash
git clone https://github.com/yourusername/espocrm-client.git
cd espocrm-client
pip install -e .
```
### Development Installation
```bash
git clone https://github.com/yourusername/espocrm-client.git
cd espocrm-client
pip install -e ".[dev]"
```
### Requirements
- Python 3.8+
- requests >= 2.31.0
- pydantic >= 2.5.0
- structlog >= 23.2.0
- typing-extensions >= 4.8.0
---
## ๐ Quick Start
### Basic Usage
```python
from espocrm import EspoCRMClient
from espocrm.auth import APIKeyAuth
from espocrm.config import ClientConfig
# Configure authentication
auth = APIKeyAuth(api_key="your-api-key")
# Configure client
config = ClientConfig(
base_url="https://your-espocrm.com",
api_key="your-api-key",
timeout=30,
verify_ssl=True
)
# Create client
client = EspoCRMClient(
base_url="https://your-espocrm.com",
auth=auth,
config=config
)
# Create a contact
contact = client.crud.create("Contact", {
"firstName": "John",
"lastName": "Doe",
"emailAddress": "john.doe@example.com"
})
print(f"Created contact with ID: {contact.get_id()}")
```
### Using Context Manager
```python
from espocrm import EspoCRMClient
from espocrm.auth import APIKeyAuth
from espocrm.config import ClientConfig
config = ClientConfig(
base_url="https://your-espocrm.com",
api_key="your-api-key"
)
auth = APIKeyAuth(api_key="your-api-key")
with EspoCRMClient(config.base_url, auth, config) as client:
# Client will be automatically closed after the block
contacts = client.crud.list("Contact", max_size=10)
for contact in contacts.list:
print(f"{contact.firstName} {contact.lastName}")
```
---
## ๐ Authentication
### API Key Authentication (Recommended)
```python
from espocrm.auth import APIKeyAuth
# Create API User in EspoCRM Admin Panel
auth = APIKeyAuth(api_key="your-api-key-from-espocrm")
```
### HMAC Authentication (Most Secure)
```python
from espocrm.auth import HMACAuth
auth = HMACAuth(
api_key="your-api-key",
secret_key="your-secret-key"
)
```
### Basic Authentication
```python
from espocrm.auth import BasicAuth
# Using password
auth = BasicAuth(
username="admin",
password="your-password"
)
# Using token (recommended over password)
auth = BasicAuth(
username="admin",
token="your-auth-token"
)
```
### Custom Authentication
```python
from espocrm.auth.base import AuthenticationBase
class CustomAuth(AuthenticationBase):
def get_headers(self, method: str, uri: str) -> Dict[str, str]:
return {
"X-Custom-Auth": "your-custom-token"
}
```
---
## ๐ CRUD Operations
### Create
```python
# Simple create
contact = client.crud.create("Contact", {
"firstName": "Jane",
"lastName": "Smith",
"emailAddress": "jane.smith@example.com",
"phoneNumber": "+1 555 123 4567",
"title": "Sales Manager",
"description": "VIP Customer"
})
# Using models
from espocrm.models.entities import Contact
contact_model = Contact(
firstName="Jane",
lastName="Smith",
emailAddress="jane.smith@example.com"
)
contact = client.crud.create("Contact", contact_model)
```
### Read
```python
# Get by ID
contact = client.crud.read("Contact", "contact-id-here")
# Get specific fields only
contact = client.crud.read(
"Contact",
"contact-id-here",
select=["firstName", "lastName", "emailAddress"]
)
# Access data
print(contact.data.firstName)
print(contact.data.emailAddress)
```
### Update
```python
# Update specific fields
updated = client.crud.update(
"Contact",
"contact-id-here",
{
"title": "Senior Sales Manager",
"phoneNumber": "+1 555 987 6543"
}
)
# Full update (PUT)
updated = client.crud.update(
"Contact",
"contact-id-here",
full_contact_data,
partial=False # Use PUT instead of PATCH
)
```
### Delete
```python
# Delete by ID
success = client.crud.delete("Contact", "contact-id-here")
if success:
print("Contact deleted successfully")
```
### List
```python
# Simple list
contacts = client.crud.list("Contact", max_size=50)
for contact in contacts.list:
print(f"{contact.firstName} {contact.lastName}")
# With pagination
contacts = client.crud.list(
"Contact",
offset=0,
max_size=20,
order_by="createdAt",
order="desc"
)
print(f"Total contacts: {contacts.total}")
print(f"Current page: {len(contacts.list)}")
```
### Search
```python
from espocrm.models.search import SearchParams
# Simple search
search_params = SearchParams(
query="john",
maxSize=10
)
results = client.crud.search("Contact", search_params)
# Advanced search with filters
search_params = SearchParams()
search_params.add_equals("type", "Customer")
search_params.add_contains("name", "Corp")
search_params.add_greater_than("createdAt", "2024-01-01")
search_params.set_order("createdAt", "desc")
search_params.set_pagination(0, 50)
results = client.crud.search("Account", search_params)
# Using where clauses
from espocrm.models.search import equals, contains, in_array
search_params = SearchParams()
search_params.add_where_clause(equals("status", "Active"))
search_params.add_where_clause(contains("email", "@company.com"))
search_params.add_where_clause(in_array("type", ["Customer", "Partner"]))
results = client.crud.search("Contact", search_params)
```
---
## ๐ง Advanced Features
### Bulk Operations
```python
# Bulk create
contacts_data = [
{"firstName": "Alice", "lastName": "Johnson"},
{"firstName": "Bob", "lastName": "Williams"},
{"firstName": "Charlie", "lastName": "Brown"}
]
result = client.crud.bulk_create("Contact", contacts_data)
print(f"Created: {result.successful}/{result.total}")
# Bulk update
updates = [
{"id": "id1", "status": "Active"},
{"id": "id2", "status": "Active"},
{"id": "id3", "status": "Inactive"}
]
result = client.crud.bulk_update("Contact", updates)
# Bulk delete
ids = ["id1", "id2", "id3"]
result = client.crud.bulk_delete("Contact", ids)
# Check results
for item in result.results:
if item["success"]:
print(f"โ Processed: {item['id']}")
else:
print(f"โ Failed: {item['id']} - {item.get('error')}")
```
### Related Records
```python
# Get account with contacts
account = client.crud.read("Account", "account-id")
# Get related contacts
contacts = client.crud.list(
"Contact",
where=[{
"type": "equals",
"attribute": "accountId",
"value": "account-id"
}]
)
# Link records
client.request(
"POST",
f"Account/{account_id}/contacts",
json={"id": contact_id}
)
# Unlink records
client.request(
"DELETE",
f"Account/{account_id}/contacts/{contact_id}"
)
```
### Custom Entities
```python
# Work with custom entities
custom_record = client.crud.create("CustomEntity", {
"name": "Custom Record",
"customField1": "Value 1",
"customField2": 123
})
# List custom entity records
records = client.crud.list("CustomEntity", max_size=100)
```
### Stream (Activity Feed)
```python
# Get stream
stream = client.request("GET", "Stream")
# Post to stream
client.request("POST", "Note", json={
"post": "This is a note",
"type": "Post",
"parentType": "Account",
"parentId": "account-id"
})
```
### File Attachments
```python
import base64
# Upload attachment
with open("document.pdf", "rb") as f:
file_content = base64.b64encode(f.read()).decode()
attachment = client.request("POST", "Attachment", json={
"name": "document.pdf",
"type": "application/pdf",
"file": file_content,
"parentType": "Contact",
"parentId": "contact-id"
})
# Download attachment
attachment_data = client.request("GET", f"Attachment/{attachment_id}")
file_content = base64.b64decode(attachment_data["file"])
```
### Request Interceptors
```python
# Add custom headers to all requests
def add_custom_header(request):
request.headers["X-Custom-Header"] = "custom-value"
return request
client.http_client.add_request_interceptor(add_custom_header)
# Log all responses
def log_response(response):
print(f"Response: {response.status_code}")
return response
client.http_client.add_response_interceptor(log_response)
```
### Rate Limiting
```python
# Configure rate limiting
config = ClientConfig(
base_url="https://your-espocrm.com",
api_key="your-api-key",
rate_limit_per_minute=60 # Max 60 requests per minute
)
# Handle rate limit errors
from espocrm.exceptions import EspoCRMRateLimitError
try:
contacts = client.crud.list("Contact")
except EspoCRMRateLimitError as e:
print(f"Rate limited. Retry after: {e.retry_after} seconds")
time.sleep(e.retry_after)
contacts = client.crud.list("Contact")
```
---
## โ ๏ธ Error Handling
### Exception Hierarchy
```python
from espocrm.exceptions import (
EspoCRMError, # Base exception
EspoCRMAPIError, # API errors
EspoCRMAuthenticationError, # 401 errors
EspoCRMForbiddenError, # 403 errors
EspoCRMNotFoundError, # 404 errors
EspoCRMValidationError, # 400 validation errors
EspoCRMRateLimitError, # 429 rate limit errors
EspoCRMServerError, # 5xx server errors
EspoCRMConnectionError # Connection errors
)
```
### Error Handling Examples
```python
from espocrm.exceptions import *
try:
contact = client.crud.read("Contact", "invalid-id")
except EspoCRMNotFoundError:
print("Contact not found")
except EspoCRMAuthenticationError:
print("Authentication failed - check your API key")
except EspoCRMValidationError as e:
print(f"Validation error: {e.message}")
print(f"Fields: {e.validation_errors}")
except EspoCRMServerError as e:
print(f"Server error: {e.status_code} - {e.message}")
except EspoCRMConnectionError:
print("Connection failed - check network")
except EspoCRMError as e:
print(f"General error: {e}")
```
### Retry on Errors
```python
from espocrm.utils.retry import retry_on_error
@retry_on_error(max_retries=3, delay=1.0)
def create_contact(client, data):
return client.crud.create("Contact", data)
# Or use built-in retry
config = ClientConfig(
base_url="https://your-espocrm.com",
api_key="your-api-key",
max_retries=3,
retry_delay=1.0
)
```
---
## โ๏ธ Configuration
### Client Configuration
```python
from espocrm.config import ClientConfig
config = ClientConfig(
# Required
base_url="https://your-espocrm.com",
# Authentication (one of these)
api_key="your-api-key",
# OR
username="admin",
password="password",
# Optional settings
timeout=30, # Request timeout in seconds
verify_ssl=True, # SSL certificate verification
max_retries=3, # Maximum retry attempts
retry_delay=1.0, # Delay between retries
rate_limit_per_minute=None, # Rate limiting
user_agent="MyApp/1.0", # Custom user agent
extra_headers={ # Additional headers
"X-Custom": "value"
},
pool_connections=10, # Connection pool size
pool_maxsize=10, # Max pool size
# Logging
log_level="INFO", # Logging level
log_requests=False, # Log all requests
log_responses=False, # Log all responses
)
```
### Environment Variables
```bash
# .env file
ESPO_URL=https://your-espocrm.com
ESPO_API_KEY=your-api-key
# OR
ESPO_USERNAME=admin
ESPO_PASSWORD=your-password
# Optional
ESPO_TIMEOUT=30
ESPO_VERIFY_SSL=true
ESPO_MAX_RETRIES=3
```
```python
import os
from dotenv import load_dotenv
load_dotenv()
config = ClientConfig(
base_url=os.getenv("ESPO_URL"),
api_key=os.getenv("ESPO_API_KEY")
)
```
### Logging Configuration
```python
import logging
import structlog
# Standard logging
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
# Structured logging with structlog
structlog.configure(
processors=[
structlog.stdlib.filter_by_level,
structlog.stdlib.add_logger_name,
structlog.stdlib.add_log_level,
structlog.stdlib.PositionalArgumentsFormatter(),
structlog.processors.TimeStamper(fmt="iso"),
structlog.processors.StackInfoRenderer(),
structlog.processors.format_exc_info,
structlog.dev.ConsoleRenderer()
],
context_class=dict,
logger_factory=structlog.stdlib.LoggerFactory(),
cache_logger_on_first_use=True,
)
```
---
## ๐งช Testing
### Running Tests
```bash
# Install test dependencies
pip install -r requirements-test.txt
# Run all tests
pytest
# Run with coverage
pytest --cov=espocrm --cov-report=html
# Run specific test
pytest tests/test_crud.py::TestContactCRUD::test_create_contact
# Run with verbose output
pytest -v -s
```
### Writing Tests
```python
import pytest
from unittest.mock import Mock, patch
from espocrm import EspoCRMClient
@pytest.fixture
def mock_client():
"""Create a mock client for testing"""
with patch('espocrm.client.EspoCRMClient') as mock:
client = mock.return_value
client.crud.create.return_value = {"id": "test-id"}
yield client
def test_create_contact(mock_client):
"""Test contact creation"""
result = mock_client.crud.create("Contact", {
"firstName": "Test"
})
assert result["id"] == "test-id"
mock_client.crud.create.assert_called_once()
```
### Integration Tests
```python
# tests/integration/test_real_api.py
import os
import pytest
from espocrm import EspoCRMClient
from espocrm.auth import APIKeyAuth
from espocrm.config import ClientConfig
@pytest.mark.integration
def test_real_api_connection():
"""Test real API connection"""
config = ClientConfig(
base_url=os.getenv("ESPO_TEST_URL"),
api_key=os.getenv("ESPO_TEST_API_KEY")
)
auth = APIKeyAuth(api_key=os.getenv("ESPO_TEST_API_KEY"))
with EspoCRMClient(config.base_url, auth, config) as client:
# Test connection
result = client.test_connection()
assert result is True
# Test CRUD
contact = client.crud.create("Contact", {
"firstName": "Test",
"lastName": "User"
})
assert contact.get_id() is not None
# Cleanup
client.crud.delete("Contact", contact.get_id())
```
---
## ๐ API Reference
### Main Classes
#### EspoCRMClient
Main client class for interacting with EspoCRM API.
```python
class EspoCRMClient:
def __init__(self, base_url: str, auth: AuthenticationBase, config: ClientConfig = None)
def test_connection(self) -> bool
def get_server_info(self) -> Dict[str, Any]
def request(self, method: str, endpoint: str, **kwargs) -> Dict[str, Any]
def close(self)
```
#### CrudClient
CRUD operations client.
```python
class CrudClient:
def create(self, entity_type: str, data: Union[Dict, EntityRecord]) -> EntityResponse
def read(self, entity_type: str, entity_id: str, select: List[str] = None) -> EntityResponse
def update(self, entity_type: str, entity_id: str, data: Union[Dict, EntityRecord], partial: bool = True) -> EntityResponse
def delete(self, entity_type: str, entity_id: str) -> bool
def list(self, entity_type: str, search_params: SearchParams = None, **kwargs) -> ListResponse
def search(self, entity_type: str, search_params: SearchParams) -> ListResponse
def bulk_create(self, entity_type: str, entities: List[Union[Dict, EntityRecord]]) -> BulkOperationResult
def bulk_update(self, entity_type: str, updates: List[Dict], partial: bool = True) -> BulkOperationResult
def bulk_delete(self, entity_type: str, entity_ids: List[str]) -> BulkOperationResult
def count(self, entity_type: str, where: List[Dict] = None) -> int
def exists(self, entity_type: str, entity_id: str) -> bool
```
#### SearchParams
Search parameters builder.
```python
class SearchParams:
def __init__(self, query: str = None, maxSize: int = None, offset: int = None)
def add_where_clause(self, clause: WhereClause)
def add_equals(self, field: str, value: Any)
def add_not_equals(self, field: str, value: Any)
def add_contains(self, field: str, value: str)
def add_starts_with(self, field: str, value: str)
def add_ends_with(self, field: str, value: str)
def add_greater_than(self, field: str, value: Any)
def add_less_than(self, field: str, value: Any)
def add_in_array(self, field: str, values: List[Any])
def add_not_in_array(self, field: str, values: List[Any])
def add_is_null(self, field: str)
def add_is_not_null(self, field: str)
def set_order(self, field: str, direction: str = "asc")
def set_pagination(self, offset: int, limit: int)
def to_query_params(self) -> Dict[str, Any]
```
### Models
#### EntityResponse
Response wrapper for single entity operations.
```python
class EntityResponse:
data: Dict[str, Any]
entity_type: str
def get_id(self) -> str
def get_field(self, field: str) -> Any
def to_dict(self) -> Dict[str, Any]
```
#### ListResponse
Response wrapper for list operations.
```python
class ListResponse:
list: List[Dict[str, Any]]
total: int
entity_type: str
def get_entities(self) -> List[EntityRecord]
def get_ids(self) -> List[str]
def is_empty(self) -> bool
```
#### BulkOperationResult
Result of bulk operations.
```python
class BulkOperationResult:
success: bool
total: int
successful: int
failed: int
results: List[Dict[str, Any]]
errors: List[Dict[str, Any]] = None
```
### Authentication Classes
```python
# API Key Authentication
class APIKeyAuth(AuthenticationBase):
def __init__(self, api_key: str)
# HMAC Authentication
class HMACAuth(AuthenticationBase):
def __init__(self, api_key: str, secret_key: str)
# Basic Authentication
class BasicAuth(AuthenticationBase):
def __init__(self, username: str, password: str = None, token: str = None)
```
### Exception Classes
```python
class EspoCRMError(Exception): ...
class EspoCRMAPIError(EspoCRMError): ...
class EspoCRMAuthenticationError(EspoCRMAPIError): ... # 401
class EspoCRMForbiddenError(EspoCRMAPIError): ... # 403
class EspoCRMNotFoundError(EspoCRMAPIError): ... # 404
class EspoCRMValidationError(EspoCRMAPIError): ... # 400
class EspoCRMRateLimitError(EspoCRMAPIError): ... # 429
class EspoCRMServerError(EspoCRMAPIError): ... # 5xx
class EspoCRMConnectionError(EspoCRMError): ...
```
---
## ๐ Examples
### Complete CRUD Example
```python
from espocrm import EspoCRMClient
from espocrm.auth import APIKeyAuth
from espocrm.config import ClientConfig
from espocrm.models.search import SearchParams
from espocrm.exceptions import EspoCRMNotFoundError
# Setup
config = ClientConfig(
base_url="https://your-espocrm.com",
api_key="your-api-key"
)
auth = APIKeyAuth(api_key="your-api-key")
with EspoCRMClient(config.base_url, auth, config) as client:
# CREATE
print("Creating contact...")
contact = client.crud.create("Contact", {
"firstName": "John",
"lastName": "Doe",
"emailAddress": "john.doe@example.com",
"phoneNumber": "+1 555 123 4567",
"title": "CEO",
"addressStreet": "123 Main St",
"addressCity": "New York",
"addressCountry": "USA"
})
contact_id = contact.get_id()
print(f"โ Created contact: {contact_id}")
# READ
print("\nReading contact...")
fetched = client.crud.read("Contact", contact_id)
print(f"โ Contact name: {fetched.data.firstName} {fetched.data.lastName}")
# UPDATE
print("\nUpdating contact...")
updated = client.crud.update("Contact", contact_id, {
"title": "CTO",
"description": "Updated via API"
})
print(f"โ Updated title to: {updated.data.title}")
# SEARCH
print("\nSearching contacts...")
search = SearchParams(query="john", maxSize=5)
results = client.crud.search("Contact", search)
print(f"โ Found {len(results.list)} contacts")
# LIST
print("\nListing all contacts...")
all_contacts = client.crud.list("Contact", max_size=10)
print(f"โ Total contacts: {all_contacts.total}")
# DELETE
print("\nDeleting contact...")
deleted = client.crud.delete("Contact", contact_id)
print(f"โ Contact deleted: {deleted}")
# VERIFY DELETION
print("\nVerifying deletion...")
try:
client.crud.read("Contact", contact_id)
print("โ Contact still exists!")
except EspoCRMNotFoundError:
print("โ Contact successfully deleted")
```
### Account with Contacts Example
```python
# Create account
account = client.crud.create("Account", {
"name": "Acme Corporation",
"website": "https://acme.com",
"type": "Customer",
"industry": "Technology"
})
# Create contacts for the account
contacts = []
for i in range(3):
contact = client.crud.create("Contact", {
"firstName": f"Employee {i+1}",
"lastName": "Smith",
"accountId": account.get_id(),
"emailAddress": f"employee{i+1}@acme.com"
})
contacts.append(contact)
# Get account with related contacts
account_data = client.crud.read("Account", account.get_id())
related_contacts = client.crud.list(
"Contact",
where=[{
"type": "equals",
"attribute": "accountId",
"value": account.get_id()
}]
)
print(f"Account: {account_data.data.name}")
print(f"Contacts: {related_contacts.total}")
for contact in related_contacts.list:
print(f" - {contact.firstName} {contact.lastName}")
```
### Lead Conversion Example
```python
# Create a lead
lead = client.crud.create("Lead", {
"firstName": "Jane",
"lastName": "Prospect",
"emailAddress": "jane@prospect.com",
"companyName": "Prospect Inc",
"title": "CEO",
"status": "New"
})
# Update lead status
client.crud.update("Lead", lead.get_id(), {
"status": "Assigned",
"assignedUserId": "user-id"
})
# Convert lead to contact and account
conversion_result = client.request(
"POST",
f"Lead/{lead.get_id()}/convert",
json={
"createAccount": True,
"createContact": True,
"accountName": "Prospect Inc",
"opportunityName": "New Opportunity"
}
)
print(f"Lead converted:")
print(f" Account ID: {conversion_result.get('accountId')}")
print(f" Contact ID: {conversion_result.get('contactId')}")
print(f" Opportunity ID: {conversion_result.get('opportunityId')}")
```
---
## ๐ค Contributing
We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
### Development Setup
```bash
# Clone repository
git clone https://github.com/yourusername/espocrm-client.git
cd espocrm-client
# Create virtual environment
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
# Install in development mode
pip install -e ".[dev]"
# Run tests
pytest
# Run linting
flake8 espocrm
mypy espocrm
# Format code
black espocrm
isort espocrm
```
### Pull Request Process
1. Fork the repository
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'Add amazing feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request
---
## ๐ License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
---
## ๐ Acknowledgments
- EspoCRM Team for the excellent CRM platform
- All contributors who have helped improve this library
- Python community for the amazing ecosystem
---
## ๐ Support
- **Documentation**: [Full Documentation](https://espocrm-python-client.readthedocs.io)
- **Issues**: [GitHub Issues](https://github.com/yourusername/espocrm-client/issues)
- **Discussions**: [GitHub Discussions](https://github.com/yourusername/espocrm-client/discussions)
- **Email**: support@example.com
---
<div align="center">
**Made with โค๏ธ by the Open Source Community**
[โฌ Back to Top](#espocrm-python-client)
</div>
Raw data
{
"_id": null,
"home_page": null,
"name": "espocrm-client",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.8",
"maintainer_email": "EspoCRM Python Client Team <support@espocrm-python-client.com>",
"keywords": "espocrm, api, client, crm, rest, api-client, customer-relationship-management, business-automation, sales-management, lead-management, contact-management, opportunity-management, type-safe, pydantic, structured-logging",
"author": null,
"author_email": "EspoCRM Python Client Team <support@espocrm-python-client.com>",
"download_url": "https://files.pythonhosted.org/packages/8d/13/1bfba531398eddc1d92ed8dfec2b21216b178fc996f7ace61c6cf0a10eff/espocrm_client-0.2.0.tar.gz",
"platform": null,
"description": "# EspoCRM Python Client\n\n<div align=\"center\">\n\n[](https://www.python.org)\n[](LICENSE)\n[](https://www.espocrm.com)\n\n**Modern, Type-Safe, Production-Ready Python Client for EspoCRM API**\n\n[Installation](#installation) \u2022 [Quick Start](#quick-start) \u2022 [Documentation](#documentation) \u2022 [Examples](#examples) \u2022 [API Reference](#api-reference)\n\n</div>\n\n---\n\n## \ud83d\udccb Table of Contents\n\n- [Features](#features)\n- [Installation](#installation)\n- [Quick Start](#quick-start)\n- [Authentication](#authentication)\n- [CRUD Operations](#crud-operations)\n- [Advanced Features](#advanced-features)\n- [Error Handling](#error-handling)\n- [Configuration](#configuration)\n- [Testing](#testing)\n- [API Reference](#api-reference)\n- [Contributing](#contributing)\n- [License](#license)\n\n---\n\n## \u2728 Features\n\n### Core Features\n- \ud83d\udd10 **Multiple Authentication Methods**: API Key, HMAC, Basic Auth\n- \ud83d\udcdd **Full CRUD Operations**: Create, Read, Update, Delete, List, Search\n- \ud83d\udd04 **Automatic Retry Logic**: With exponential backoff\n- \ud83c\udfaf **Type Safety**: Full type hints and Pydantic models\n- \ud83d\ude80 **Async Support**: Coming soon\n- \ud83d\udcca **Bulk Operations**: Efficient batch processing\n- \ud83d\udd0d **Advanced Search**: Complex queries with SearchParams\n- \u26a1 **Connection Pooling**: Optimized HTTP connections\n- \ud83d\udee1\ufe0f **Comprehensive Error Handling**: Detailed exceptions\n- \ud83d\udcc8 **Rate Limiting**: Built-in rate limit management\n- \ud83e\uddea **Well Tested**: Extensive test coverage\n\n### Architecture\n- **Modular Design**: Separate modules for auth, models, utils\n- **Interceptor Pattern**: Request/Response interceptors\n- **Factory Pattern**: Entity factories\n- **Builder Pattern**: Query builders\n- **Strategy Pattern**: Authentication strategies\n\n---\n\n## \ud83d\udce6 Installation\n\n### From PyPI (Recommended)\n```bash\npip install espocrm-python-client\n```\n\n### From Source\n```bash\ngit clone https://github.com/yourusername/espocrm-client.git\ncd espocrm-client\npip install -e .\n```\n\n### Development Installation\n```bash\ngit clone https://github.com/yourusername/espocrm-client.git\ncd espocrm-client\npip install -e \".[dev]\"\n```\n\n### Requirements\n- Python 3.8+\n- requests >= 2.31.0\n- pydantic >= 2.5.0\n- structlog >= 23.2.0\n- typing-extensions >= 4.8.0\n\n---\n\n## \ud83d\ude80 Quick Start\n\n### Basic Usage\n\n```python\nfrom espocrm import EspoCRMClient\nfrom espocrm.auth import APIKeyAuth\nfrom espocrm.config import ClientConfig\n\n# Configure authentication\nauth = APIKeyAuth(api_key=\"your-api-key\")\n\n# Configure client\nconfig = ClientConfig(\n base_url=\"https://your-espocrm.com\",\n api_key=\"your-api-key\",\n timeout=30,\n verify_ssl=True\n)\n\n# Create client\nclient = EspoCRMClient(\n base_url=\"https://your-espocrm.com\",\n auth=auth,\n config=config\n)\n\n# Create a contact\ncontact = client.crud.create(\"Contact\", {\n \"firstName\": \"John\",\n \"lastName\": \"Doe\",\n \"emailAddress\": \"john.doe@example.com\"\n})\n\nprint(f\"Created contact with ID: {contact.get_id()}\")\n```\n\n### Using Context Manager\n\n```python\nfrom espocrm import EspoCRMClient\nfrom espocrm.auth import APIKeyAuth\nfrom espocrm.config import ClientConfig\n\nconfig = ClientConfig(\n base_url=\"https://your-espocrm.com\",\n api_key=\"your-api-key\"\n)\n\nauth = APIKeyAuth(api_key=\"your-api-key\")\n\nwith EspoCRMClient(config.base_url, auth, config) as client:\n # Client will be automatically closed after the block\n contacts = client.crud.list(\"Contact\", max_size=10)\n for contact in contacts.list:\n print(f\"{contact.firstName} {contact.lastName}\")\n```\n\n---\n\n## \ud83d\udd10 Authentication\n\n### API Key Authentication (Recommended)\n\n```python\nfrom espocrm.auth import APIKeyAuth\n\n# Create API User in EspoCRM Admin Panel\nauth = APIKeyAuth(api_key=\"your-api-key-from-espocrm\")\n```\n\n### HMAC Authentication (Most Secure)\n\n```python\nfrom espocrm.auth import HMACAuth\n\nauth = HMACAuth(\n api_key=\"your-api-key\",\n secret_key=\"your-secret-key\"\n)\n```\n\n### Basic Authentication\n\n```python\nfrom espocrm.auth import BasicAuth\n\n# Using password\nauth = BasicAuth(\n username=\"admin\",\n password=\"your-password\"\n)\n\n# Using token (recommended over password)\nauth = BasicAuth(\n username=\"admin\",\n token=\"your-auth-token\"\n)\n```\n\n### Custom Authentication\n\n```python\nfrom espocrm.auth.base import AuthenticationBase\n\nclass CustomAuth(AuthenticationBase):\n def get_headers(self, method: str, uri: str) -> Dict[str, str]:\n return {\n \"X-Custom-Auth\": \"your-custom-token\"\n }\n```\n\n---\n\n## \ud83d\udcdd CRUD Operations\n\n### Create\n\n```python\n# Simple create\ncontact = client.crud.create(\"Contact\", {\n \"firstName\": \"Jane\",\n \"lastName\": \"Smith\",\n \"emailAddress\": \"jane.smith@example.com\",\n \"phoneNumber\": \"+1 555 123 4567\",\n \"title\": \"Sales Manager\",\n \"description\": \"VIP Customer\"\n})\n\n# Using models\nfrom espocrm.models.entities import Contact\n\ncontact_model = Contact(\n firstName=\"Jane\",\n lastName=\"Smith\",\n emailAddress=\"jane.smith@example.com\"\n)\n\ncontact = client.crud.create(\"Contact\", contact_model)\n```\n\n### Read\n\n```python\n# Get by ID\ncontact = client.crud.read(\"Contact\", \"contact-id-here\")\n\n# Get specific fields only\ncontact = client.crud.read(\n \"Contact\", \n \"contact-id-here\",\n select=[\"firstName\", \"lastName\", \"emailAddress\"]\n)\n\n# Access data\nprint(contact.data.firstName)\nprint(contact.data.emailAddress)\n```\n\n### Update\n\n```python\n# Update specific fields\nupdated = client.crud.update(\n \"Contact\",\n \"contact-id-here\",\n {\n \"title\": \"Senior Sales Manager\",\n \"phoneNumber\": \"+1 555 987 6543\"\n }\n)\n\n# Full update (PUT)\nupdated = client.crud.update(\n \"Contact\",\n \"contact-id-here\",\n full_contact_data,\n partial=False # Use PUT instead of PATCH\n)\n```\n\n### Delete\n\n```python\n# Delete by ID\nsuccess = client.crud.delete(\"Contact\", \"contact-id-here\")\n\nif success:\n print(\"Contact deleted successfully\")\n```\n\n### List\n\n```python\n# Simple list\ncontacts = client.crud.list(\"Contact\", max_size=50)\n\nfor contact in contacts.list:\n print(f\"{contact.firstName} {contact.lastName}\")\n\n# With pagination\ncontacts = client.crud.list(\n \"Contact\",\n offset=0,\n max_size=20,\n order_by=\"createdAt\",\n order=\"desc\"\n)\n\nprint(f\"Total contacts: {contacts.total}\")\nprint(f\"Current page: {len(contacts.list)}\")\n```\n\n### Search\n\n```python\nfrom espocrm.models.search import SearchParams\n\n# Simple search\nsearch_params = SearchParams(\n query=\"john\",\n maxSize=10\n)\n\nresults = client.crud.search(\"Contact\", search_params)\n\n# Advanced search with filters\nsearch_params = SearchParams()\nsearch_params.add_equals(\"type\", \"Customer\")\nsearch_params.add_contains(\"name\", \"Corp\")\nsearch_params.add_greater_than(\"createdAt\", \"2024-01-01\")\nsearch_params.set_order(\"createdAt\", \"desc\")\nsearch_params.set_pagination(0, 50)\n\nresults = client.crud.search(\"Account\", search_params)\n\n# Using where clauses\nfrom espocrm.models.search import equals, contains, in_array\n\nsearch_params = SearchParams()\nsearch_params.add_where_clause(equals(\"status\", \"Active\"))\nsearch_params.add_where_clause(contains(\"email\", \"@company.com\"))\nsearch_params.add_where_clause(in_array(\"type\", [\"Customer\", \"Partner\"]))\n\nresults = client.crud.search(\"Contact\", search_params)\n```\n\n---\n\n## \ud83d\udd27 Advanced Features\n\n### Bulk Operations\n\n```python\n# Bulk create\ncontacts_data = [\n {\"firstName\": \"Alice\", \"lastName\": \"Johnson\"},\n {\"firstName\": \"Bob\", \"lastName\": \"Williams\"},\n {\"firstName\": \"Charlie\", \"lastName\": \"Brown\"}\n]\n\nresult = client.crud.bulk_create(\"Contact\", contacts_data)\nprint(f\"Created: {result.successful}/{result.total}\")\n\n# Bulk update\nupdates = [\n {\"id\": \"id1\", \"status\": \"Active\"},\n {\"id\": \"id2\", \"status\": \"Active\"},\n {\"id\": \"id3\", \"status\": \"Inactive\"}\n]\n\nresult = client.crud.bulk_update(\"Contact\", updates)\n\n# Bulk delete\nids = [\"id1\", \"id2\", \"id3\"]\nresult = client.crud.bulk_delete(\"Contact\", ids)\n\n# Check results\nfor item in result.results:\n if item[\"success\"]:\n print(f\"\u2713 Processed: {item['id']}\")\n else:\n print(f\"\u2717 Failed: {item['id']} - {item.get('error')}\")\n```\n\n### Related Records\n\n```python\n# Get account with contacts\naccount = client.crud.read(\"Account\", \"account-id\")\n\n# Get related contacts\ncontacts = client.crud.list(\n \"Contact\",\n where=[{\n \"type\": \"equals\",\n \"attribute\": \"accountId\",\n \"value\": \"account-id\"\n }]\n)\n\n# Link records\nclient.request(\n \"POST\",\n f\"Account/{account_id}/contacts\",\n json={\"id\": contact_id}\n)\n\n# Unlink records\nclient.request(\n \"DELETE\",\n f\"Account/{account_id}/contacts/{contact_id}\"\n)\n```\n\n### Custom Entities\n\n```python\n# Work with custom entities\ncustom_record = client.crud.create(\"CustomEntity\", {\n \"name\": \"Custom Record\",\n \"customField1\": \"Value 1\",\n \"customField2\": 123\n})\n\n# List custom entity records\nrecords = client.crud.list(\"CustomEntity\", max_size=100)\n```\n\n### Stream (Activity Feed)\n\n```python\n# Get stream\nstream = client.request(\"GET\", \"Stream\")\n\n# Post to stream\nclient.request(\"POST\", \"Note\", json={\n \"post\": \"This is a note\",\n \"type\": \"Post\",\n \"parentType\": \"Account\",\n \"parentId\": \"account-id\"\n})\n```\n\n### File Attachments\n\n```python\nimport base64\n\n# Upload attachment\nwith open(\"document.pdf\", \"rb\") as f:\n file_content = base64.b64encode(f.read()).decode()\n\nattachment = client.request(\"POST\", \"Attachment\", json={\n \"name\": \"document.pdf\",\n \"type\": \"application/pdf\",\n \"file\": file_content,\n \"parentType\": \"Contact\",\n \"parentId\": \"contact-id\"\n})\n\n# Download attachment\nattachment_data = client.request(\"GET\", f\"Attachment/{attachment_id}\")\nfile_content = base64.b64decode(attachment_data[\"file\"])\n```\n\n### Request Interceptors\n\n```python\n# Add custom headers to all requests\ndef add_custom_header(request):\n request.headers[\"X-Custom-Header\"] = \"custom-value\"\n return request\n\nclient.http_client.add_request_interceptor(add_custom_header)\n\n# Log all responses\ndef log_response(response):\n print(f\"Response: {response.status_code}\")\n return response\n\nclient.http_client.add_response_interceptor(log_response)\n```\n\n### Rate Limiting\n\n```python\n# Configure rate limiting\nconfig = ClientConfig(\n base_url=\"https://your-espocrm.com\",\n api_key=\"your-api-key\",\n rate_limit_per_minute=60 # Max 60 requests per minute\n)\n\n# Handle rate limit errors\nfrom espocrm.exceptions import EspoCRMRateLimitError\n\ntry:\n contacts = client.crud.list(\"Contact\")\nexcept EspoCRMRateLimitError as e:\n print(f\"Rate limited. Retry after: {e.retry_after} seconds\")\n time.sleep(e.retry_after)\n contacts = client.crud.list(\"Contact\")\n```\n\n---\n\n## \u26a0\ufe0f Error Handling\n\n### Exception Hierarchy\n\n```python\nfrom espocrm.exceptions import (\n EspoCRMError, # Base exception\n EspoCRMAPIError, # API errors\n EspoCRMAuthenticationError, # 401 errors\n EspoCRMForbiddenError, # 403 errors\n EspoCRMNotFoundError, # 404 errors\n EspoCRMValidationError, # 400 validation errors\n EspoCRMRateLimitError, # 429 rate limit errors\n EspoCRMServerError, # 5xx server errors\n EspoCRMConnectionError # Connection errors\n)\n```\n\n### Error Handling Examples\n\n```python\nfrom espocrm.exceptions import *\n\ntry:\n contact = client.crud.read(\"Contact\", \"invalid-id\")\nexcept EspoCRMNotFoundError:\n print(\"Contact not found\")\nexcept EspoCRMAuthenticationError:\n print(\"Authentication failed - check your API key\")\nexcept EspoCRMValidationError as e:\n print(f\"Validation error: {e.message}\")\n print(f\"Fields: {e.validation_errors}\")\nexcept EspoCRMServerError as e:\n print(f\"Server error: {e.status_code} - {e.message}\")\nexcept EspoCRMConnectionError:\n print(\"Connection failed - check network\")\nexcept EspoCRMError as e:\n print(f\"General error: {e}\")\n```\n\n### Retry on Errors\n\n```python\nfrom espocrm.utils.retry import retry_on_error\n\n@retry_on_error(max_retries=3, delay=1.0)\ndef create_contact(client, data):\n return client.crud.create(\"Contact\", data)\n\n# Or use built-in retry\nconfig = ClientConfig(\n base_url=\"https://your-espocrm.com\",\n api_key=\"your-api-key\",\n max_retries=3,\n retry_delay=1.0\n)\n```\n\n---\n\n## \u2699\ufe0f Configuration\n\n### Client Configuration\n\n```python\nfrom espocrm.config import ClientConfig\n\nconfig = ClientConfig(\n # Required\n base_url=\"https://your-espocrm.com\",\n \n # Authentication (one of these)\n api_key=\"your-api-key\",\n # OR\n username=\"admin\",\n password=\"password\",\n \n # Optional settings\n timeout=30, # Request timeout in seconds\n verify_ssl=True, # SSL certificate verification\n max_retries=3, # Maximum retry attempts\n retry_delay=1.0, # Delay between retries\n rate_limit_per_minute=None, # Rate limiting\n user_agent=\"MyApp/1.0\", # Custom user agent\n extra_headers={ # Additional headers\n \"X-Custom\": \"value\"\n },\n pool_connections=10, # Connection pool size\n pool_maxsize=10, # Max pool size\n \n # Logging\n log_level=\"INFO\", # Logging level\n log_requests=False, # Log all requests\n log_responses=False, # Log all responses\n)\n```\n\n### Environment Variables\n\n```bash\n# .env file\nESPO_URL=https://your-espocrm.com\nESPO_API_KEY=your-api-key\n# OR\nESPO_USERNAME=admin\nESPO_PASSWORD=your-password\n\n# Optional\nESPO_TIMEOUT=30\nESPO_VERIFY_SSL=true\nESPO_MAX_RETRIES=3\n```\n\n```python\nimport os\nfrom dotenv import load_dotenv\n\nload_dotenv()\n\nconfig = ClientConfig(\n base_url=os.getenv(\"ESPO_URL\"),\n api_key=os.getenv(\"ESPO_API_KEY\")\n)\n```\n\n### Logging Configuration\n\n```python\nimport logging\nimport structlog\n\n# Standard logging\nlogging.basicConfig(\n level=logging.DEBUG,\n format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'\n)\n\n# Structured logging with structlog\nstructlog.configure(\n processors=[\n structlog.stdlib.filter_by_level,\n structlog.stdlib.add_logger_name,\n structlog.stdlib.add_log_level,\n structlog.stdlib.PositionalArgumentsFormatter(),\n structlog.processors.TimeStamper(fmt=\"iso\"),\n structlog.processors.StackInfoRenderer(),\n structlog.processors.format_exc_info,\n structlog.dev.ConsoleRenderer()\n ],\n context_class=dict,\n logger_factory=structlog.stdlib.LoggerFactory(),\n cache_logger_on_first_use=True,\n)\n```\n\n---\n\n## \ud83e\uddea Testing\n\n### Running Tests\n\n```bash\n# Install test dependencies\npip install -r requirements-test.txt\n\n# Run all tests\npytest\n\n# Run with coverage\npytest --cov=espocrm --cov-report=html\n\n# Run specific test\npytest tests/test_crud.py::TestContactCRUD::test_create_contact\n\n# Run with verbose output\npytest -v -s\n```\n\n### Writing Tests\n\n```python\nimport pytest\nfrom unittest.mock import Mock, patch\nfrom espocrm import EspoCRMClient\n\n@pytest.fixture\ndef mock_client():\n \"\"\"Create a mock client for testing\"\"\"\n with patch('espocrm.client.EspoCRMClient') as mock:\n client = mock.return_value\n client.crud.create.return_value = {\"id\": \"test-id\"}\n yield client\n\ndef test_create_contact(mock_client):\n \"\"\"Test contact creation\"\"\"\n result = mock_client.crud.create(\"Contact\", {\n \"firstName\": \"Test\"\n })\n \n assert result[\"id\"] == \"test-id\"\n mock_client.crud.create.assert_called_once()\n```\n\n### Integration Tests\n\n```python\n# tests/integration/test_real_api.py\nimport os\nimport pytest\nfrom espocrm import EspoCRMClient\nfrom espocrm.auth import APIKeyAuth\nfrom espocrm.config import ClientConfig\n\n@pytest.mark.integration\ndef test_real_api_connection():\n \"\"\"Test real API connection\"\"\"\n config = ClientConfig(\n base_url=os.getenv(\"ESPO_TEST_URL\"),\n api_key=os.getenv(\"ESPO_TEST_API_KEY\")\n )\n \n auth = APIKeyAuth(api_key=os.getenv(\"ESPO_TEST_API_KEY\"))\n \n with EspoCRMClient(config.base_url, auth, config) as client:\n # Test connection\n result = client.test_connection()\n assert result is True\n \n # Test CRUD\n contact = client.crud.create(\"Contact\", {\n \"firstName\": \"Test\",\n \"lastName\": \"User\"\n })\n assert contact.get_id() is not None\n \n # Cleanup\n client.crud.delete(\"Contact\", contact.get_id())\n```\n\n---\n\n## \ud83d\udcda API Reference\n\n### Main Classes\n\n#### EspoCRMClient\nMain client class for interacting with EspoCRM API.\n\n```python\nclass EspoCRMClient:\n def __init__(self, base_url: str, auth: AuthenticationBase, config: ClientConfig = None)\n def test_connection(self) -> bool\n def get_server_info(self) -> Dict[str, Any]\n def request(self, method: str, endpoint: str, **kwargs) -> Dict[str, Any]\n def close(self)\n```\n\n#### CrudClient\nCRUD operations client.\n\n```python\nclass CrudClient:\n def create(self, entity_type: str, data: Union[Dict, EntityRecord]) -> EntityResponse\n def read(self, entity_type: str, entity_id: str, select: List[str] = None) -> EntityResponse\n def update(self, entity_type: str, entity_id: str, data: Union[Dict, EntityRecord], partial: bool = True) -> EntityResponse\n def delete(self, entity_type: str, entity_id: str) -> bool\n def list(self, entity_type: str, search_params: SearchParams = None, **kwargs) -> ListResponse\n def search(self, entity_type: str, search_params: SearchParams) -> ListResponse\n def bulk_create(self, entity_type: str, entities: List[Union[Dict, EntityRecord]]) -> BulkOperationResult\n def bulk_update(self, entity_type: str, updates: List[Dict], partial: bool = True) -> BulkOperationResult\n def bulk_delete(self, entity_type: str, entity_ids: List[str]) -> BulkOperationResult\n def count(self, entity_type: str, where: List[Dict] = None) -> int\n def exists(self, entity_type: str, entity_id: str) -> bool\n```\n\n#### SearchParams\nSearch parameters builder.\n\n```python\nclass SearchParams:\n def __init__(self, query: str = None, maxSize: int = None, offset: int = None)\n def add_where_clause(self, clause: WhereClause)\n def add_equals(self, field: str, value: Any)\n def add_not_equals(self, field: str, value: Any)\n def add_contains(self, field: str, value: str)\n def add_starts_with(self, field: str, value: str)\n def add_ends_with(self, field: str, value: str)\n def add_greater_than(self, field: str, value: Any)\n def add_less_than(self, field: str, value: Any)\n def add_in_array(self, field: str, values: List[Any])\n def add_not_in_array(self, field: str, values: List[Any])\n def add_is_null(self, field: str)\n def add_is_not_null(self, field: str)\n def set_order(self, field: str, direction: str = \"asc\")\n def set_pagination(self, offset: int, limit: int)\n def to_query_params(self) -> Dict[str, Any]\n```\n\n### Models\n\n#### EntityResponse\nResponse wrapper for single entity operations.\n\n```python\nclass EntityResponse:\n data: Dict[str, Any]\n entity_type: str\n \n def get_id(self) -> str\n def get_field(self, field: str) -> Any\n def to_dict(self) -> Dict[str, Any]\n```\n\n#### ListResponse\nResponse wrapper for list operations.\n\n```python\nclass ListResponse:\n list: List[Dict[str, Any]]\n total: int\n entity_type: str\n \n def get_entities(self) -> List[EntityRecord]\n def get_ids(self) -> List[str]\n def is_empty(self) -> bool\n```\n\n#### BulkOperationResult\nResult of bulk operations.\n\n```python\nclass BulkOperationResult:\n success: bool\n total: int\n successful: int\n failed: int\n results: List[Dict[str, Any]]\n errors: List[Dict[str, Any]] = None\n```\n\n### Authentication Classes\n\n```python\n# API Key Authentication\nclass APIKeyAuth(AuthenticationBase):\n def __init__(self, api_key: str)\n\n# HMAC Authentication\nclass HMACAuth(AuthenticationBase):\n def __init__(self, api_key: str, secret_key: str)\n\n# Basic Authentication\nclass BasicAuth(AuthenticationBase):\n def __init__(self, username: str, password: str = None, token: str = None)\n```\n\n### Exception Classes\n\n```python\nclass EspoCRMError(Exception): ...\nclass EspoCRMAPIError(EspoCRMError): ...\nclass EspoCRMAuthenticationError(EspoCRMAPIError): ... # 401\nclass EspoCRMForbiddenError(EspoCRMAPIError): ... # 403\nclass EspoCRMNotFoundError(EspoCRMAPIError): ... # 404\nclass EspoCRMValidationError(EspoCRMAPIError): ... # 400\nclass EspoCRMRateLimitError(EspoCRMAPIError): ... # 429\nclass EspoCRMServerError(EspoCRMAPIError): ... # 5xx\nclass EspoCRMConnectionError(EspoCRMError): ...\n```\n\n---\n\n## \ud83d\udcd8 Examples\n\n### Complete CRUD Example\n\n```python\nfrom espocrm import EspoCRMClient\nfrom espocrm.auth import APIKeyAuth\nfrom espocrm.config import ClientConfig\nfrom espocrm.models.search import SearchParams\nfrom espocrm.exceptions import EspoCRMNotFoundError\n\n# Setup\nconfig = ClientConfig(\n base_url=\"https://your-espocrm.com\",\n api_key=\"your-api-key\"\n)\nauth = APIKeyAuth(api_key=\"your-api-key\")\n\nwith EspoCRMClient(config.base_url, auth, config) as client:\n # CREATE\n print(\"Creating contact...\")\n contact = client.crud.create(\"Contact\", {\n \"firstName\": \"John\",\n \"lastName\": \"Doe\",\n \"emailAddress\": \"john.doe@example.com\",\n \"phoneNumber\": \"+1 555 123 4567\",\n \"title\": \"CEO\",\n \"addressStreet\": \"123 Main St\",\n \"addressCity\": \"New York\",\n \"addressCountry\": \"USA\"\n })\n contact_id = contact.get_id()\n print(f\"\u2713 Created contact: {contact_id}\")\n \n # READ\n print(\"\\nReading contact...\")\n fetched = client.crud.read(\"Contact\", contact_id)\n print(f\"\u2713 Contact name: {fetched.data.firstName} {fetched.data.lastName}\")\n \n # UPDATE\n print(\"\\nUpdating contact...\")\n updated = client.crud.update(\"Contact\", contact_id, {\n \"title\": \"CTO\",\n \"description\": \"Updated via API\"\n })\n print(f\"\u2713 Updated title to: {updated.data.title}\")\n \n # SEARCH\n print(\"\\nSearching contacts...\")\n search = SearchParams(query=\"john\", maxSize=5)\n results = client.crud.search(\"Contact\", search)\n print(f\"\u2713 Found {len(results.list)} contacts\")\n \n # LIST\n print(\"\\nListing all contacts...\")\n all_contacts = client.crud.list(\"Contact\", max_size=10)\n print(f\"\u2713 Total contacts: {all_contacts.total}\")\n \n # DELETE\n print(\"\\nDeleting contact...\")\n deleted = client.crud.delete(\"Contact\", contact_id)\n print(f\"\u2713 Contact deleted: {deleted}\")\n \n # VERIFY DELETION\n print(\"\\nVerifying deletion...\")\n try:\n client.crud.read(\"Contact\", contact_id)\n print(\"\u2717 Contact still exists!\")\n except EspoCRMNotFoundError:\n print(\"\u2713 Contact successfully deleted\")\n```\n\n### Account with Contacts Example\n\n```python\n# Create account\naccount = client.crud.create(\"Account\", {\n \"name\": \"Acme Corporation\",\n \"website\": \"https://acme.com\",\n \"type\": \"Customer\",\n \"industry\": \"Technology\"\n})\n\n# Create contacts for the account\ncontacts = []\nfor i in range(3):\n contact = client.crud.create(\"Contact\", {\n \"firstName\": f\"Employee {i+1}\",\n \"lastName\": \"Smith\",\n \"accountId\": account.get_id(),\n \"emailAddress\": f\"employee{i+1}@acme.com\"\n })\n contacts.append(contact)\n\n# Get account with related contacts\naccount_data = client.crud.read(\"Account\", account.get_id())\nrelated_contacts = client.crud.list(\n \"Contact\",\n where=[{\n \"type\": \"equals\",\n \"attribute\": \"accountId\",\n \"value\": account.get_id()\n }]\n)\n\nprint(f\"Account: {account_data.data.name}\")\nprint(f\"Contacts: {related_contacts.total}\")\nfor contact in related_contacts.list:\n print(f\" - {contact.firstName} {contact.lastName}\")\n```\n\n### Lead Conversion Example\n\n```python\n# Create a lead\nlead = client.crud.create(\"Lead\", {\n \"firstName\": \"Jane\",\n \"lastName\": \"Prospect\",\n \"emailAddress\": \"jane@prospect.com\",\n \"companyName\": \"Prospect Inc\",\n \"title\": \"CEO\",\n \"status\": \"New\"\n})\n\n# Update lead status\nclient.crud.update(\"Lead\", lead.get_id(), {\n \"status\": \"Assigned\",\n \"assignedUserId\": \"user-id\"\n})\n\n# Convert lead to contact and account\nconversion_result = client.request(\n \"POST\",\n f\"Lead/{lead.get_id()}/convert\",\n json={\n \"createAccount\": True,\n \"createContact\": True,\n \"accountName\": \"Prospect Inc\",\n \"opportunityName\": \"New Opportunity\"\n }\n)\n\nprint(f\"Lead converted:\")\nprint(f\" Account ID: {conversion_result.get('accountId')}\")\nprint(f\" Contact ID: {conversion_result.get('contactId')}\")\nprint(f\" Opportunity ID: {conversion_result.get('opportunityId')}\")\n```\n\n---\n\n## \ud83e\udd1d Contributing\n\nWe welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.\n\n### Development Setup\n\n```bash\n# Clone repository\ngit clone https://github.com/yourusername/espocrm-client.git\ncd espocrm-client\n\n# Create virtual environment\npython -m venv venv\nsource venv/bin/activate # On Windows: venv\\Scripts\\activate\n\n# Install in development mode\npip install -e \".[dev]\"\n\n# Run tests\npytest\n\n# Run linting\nflake8 espocrm\nmypy espocrm\n\n# Format code\nblack espocrm\nisort espocrm\n```\n\n### Pull Request Process\n\n1. Fork the repository\n2. Create a feature branch (`git checkout -b feature/amazing-feature`)\n3. Commit your changes (`git commit -m 'Add amazing feature'`)\n4. Push to the branch (`git push origin feature/amazing-feature`)\n5. Open a Pull Request\n\n---\n\n## \ud83d\udcc4 License\n\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.\n\n---\n\n## \ud83d\ude4f Acknowledgments\n\n- EspoCRM Team for the excellent CRM platform\n- All contributors who have helped improve this library\n- Python community for the amazing ecosystem\n\n---\n\n## \ud83d\udcde Support\n\n- **Documentation**: [Full Documentation](https://espocrm-python-client.readthedocs.io)\n- **Issues**: [GitHub Issues](https://github.com/yourusername/espocrm-client/issues)\n- **Discussions**: [GitHub Discussions](https://github.com/yourusername/espocrm-client/discussions)\n- **Email**: support@example.com\n\n---\n\n<div align=\"center\">\n\n**Made with \u2764\ufe0f by the Open Source Community**\n\n[\u2b06 Back to Top](#espocrm-python-client)\n\n</div>\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "Modern, type-safe and comprehensive EspoCRM API client library for Python",
"version": "0.2.0",
"project_urls": {
"Bug Reports": "https://github.com/espocrm/espocrm-client/issues",
"Changelog": "https://github.com/espocrm/espocrm-client/blob/main/CHANGELOG.md",
"Documentation": "https://espocrm-client.readthedocs.io",
"Download": "https://pypi.org/project/espocrm-client/",
"Feature Requests": "https://github.com/espocrm/espocrm-client/issues/new?template=feature_request.md",
"Funding": "https://github.com/sponsors/espocrm",
"Homepage": "https://github.com/espocrm/espocrm-client",
"Issues": "https://github.com/espocrm/espocrm-client/issues",
"Repository": "https://github.com/espocrm/espocrm-client.git",
"Source Code": "https://github.com/espocrm/espocrm-client"
},
"split_keywords": [
"espocrm",
" api",
" client",
" crm",
" rest",
" api-client",
" customer-relationship-management",
" business-automation",
" sales-management",
" lead-management",
" contact-management",
" opportunity-management",
" type-safe",
" pydantic",
" structured-logging"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "ac6dd653e33a96d41a143f7b70fb17af15705fe436156e7b87eac0c958b103e5",
"md5": "dbdbdacd74d13df631222bb290432914",
"sha256": "d07599f4a336681b7785651424a49dd9edab34be6a9503730b2d261511b2793e"
},
"downloads": -1,
"filename": "espocrm_client-0.2.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "dbdbdacd74d13df631222bb290432914",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.8",
"size": 88921,
"upload_time": "2025-08-20T16:19:35",
"upload_time_iso_8601": "2025-08-20T16:19:35.548938Z",
"url": "https://files.pythonhosted.org/packages/ac/6d/d653e33a96d41a143f7b70fb17af15705fe436156e7b87eac0c958b103e5/espocrm_client-0.2.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "8d131bfba531398eddc1d92ed8dfec2b21216b178fc996f7ace61c6cf0a10eff",
"md5": "2c9c36edf5aa9cf73f9e471ffa48be85",
"sha256": "e260a7eb5b481164775e4d77ef838cc51a62de2255952604b3b8cfea09d2b859"
},
"downloads": -1,
"filename": "espocrm_client-0.2.0.tar.gz",
"has_sig": false,
"md5_digest": "2c9c36edf5aa9cf73f9e471ffa48be85",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.8",
"size": 4311616,
"upload_time": "2025-08-20T16:19:38",
"upload_time_iso_8601": "2025-08-20T16:19:38.117417Z",
"url": "https://files.pythonhosted.org/packages/8d/13/1bfba531398eddc1d92ed8dfec2b21216b178fc996f7ace61c6cf0a10eff/espocrm_client-0.2.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-08-20 16:19:38",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "espocrm",
"github_project": "espocrm-client",
"github_not_found": true,
"lcname": "espocrm-client"
}