Skip to main content

Python SDK for March AI Conversation History API

Project description

March Conversation History SDK

A Python SDK for the March AI Conversation History API with full type safety, automatic retries, and pagination support.

Features

Type-Safe: Full type hints with Pydantic v2 models 🔄 Auto-Retry: Exponential backoff with configurable retry logic 📄 Auto-Pagination: Iterate over large result sets effortlessly 🚀 Sync & Async: Support for both synchronous and asynchronous operations 🧹 Auto-Cleanup: No context managers needed - automatic resource management 🎯 Best Practices: Follows Python SDK design patterns used by major APIs

Installation

pip install march-history

Or install from source:

git clone https://github.com/march/march-history-sdk
cd march-history-sdk
pip install -e .

Quick Start

from march_history import MarchHistoryClient
from march_history.models import MessageRole, ConversationStatus

# Create client - no context manager needed!
client = MarchHistoryClient(base_url="http://localhost:8000")

# Create a conversation
conv = client.conversations.create(
    tenant_name="acme-corp",
    user_id="user-123",
    title="Customer Support Session",
    metadata={"session_id": "sess-456"}
)

# Add messages
msg1 = client.messages.create(
    conversation_id=conv.id,
    role=MessageRole.USER,
    content="Hello, I need help with my order"
)

msg2 = client.messages.create(
    conversation_id=conv.id,
    role=MessageRole.ASSISTANT,
    content="I'd be happy to help! What's your order number?"
)

# List all messages in conversation
messages = client.messages.list(conversation_id=conv.id)
for msg in messages:
    print(f"{msg.role}: {msg.content}")

# Auto-pagination - iterate over all conversations
for conversation in client.conversations.list_iter(tenant_name="acme-corp"):
    print(f"Conversation: {conversation.title}")

# Search conversations
results = client.conversations.search(
    q="support",
    status=ConversationStatus.ACTIVE
)

# Archive conversation when done
client.conversations.archive(conv.id)

# No need to call client.close() - automatic cleanup!

Async Usage

The SDK provides a fully async client for use with asyncio:

import asyncio
from march_history import AsyncMarchHistoryClient
from march_history.models import MessageRole, ConversationStatus

async def main():
    # Create async client - no context manager needed!
    client = AsyncMarchHistoryClient(base_url="http://localhost:8000")

    # Create a conversation
    conv = await client.conversations.create(
        tenant_name="acme-corp",
        user_id="user-123",
        title="Async Support Session"
    )

    # Add messages
    msg = await client.messages.create(
        conversation_id=conv.id,
        role=MessageRole.USER,
        content="Hello from async!"
    )

    # Auto-pagination with async for
    async for conversation in client.conversations.list_iter(tenant_name="acme-corp"):
        print(f"Conversation: {conversation.title}")

    # Search conversations
    results = await client.conversations.search(
        q="support",
        status=ConversationStatus.ACTIVE
    )

    # Archive when done
    await client.conversations.archive(conv.id)

asyncio.run(main())

Async Context Manager

async with AsyncMarchHistoryClient(base_url="http://localhost:8000") as client:
    conv = await client.conversations.create(
        tenant_name="acme-corp",
        user_id="user-123",
        title="Chat"
    )
# Connections automatically closed

Usage Guide

Creating Conversations

# Basic conversation
conv = client.conversations.create(
    tenant_name="acme-corp",
    user_id="user-123",
    title="Support Chat"
)

# With full metadata
conv = client.conversations.create(
    tenant_name="acme-corp",
    user_id="user-123",
    title="Sales Inquiry",
    agent_identifier="sales-bot-v2",
    status=ConversationStatus.ACTIVE,
    metadata={
        "session_id": "sess-456",
        "source": "web_chat",
        "priority": "high"
    }
)

Working with Messages

# Single message
msg = client.messages.create(
    conversation_id=conv.id,
    role=MessageRole.USER,
    content="What are your business hours?",
    metadata={"client_ip": "192.168.1.1"}
)

# Batch create messages
msgs = client.messages.create_batch(
    conversation_id=conv.id,
    messages=[
        {"role": "user", "content": "Hello"},
        {"role": "assistant", "content": "Hi! How can I help?"},
        {"role": "user", "content": "I need information"},
    ]
)

# Search messages in a conversation
results = client.messages.search(
    conversation_id=conv.id,
    q="business hours"
)

# Search across all conversations
all_results = client.messages.search_global(
    q="error",
    role=MessageRole.ASSISTANT
)

Pagination

# Manual pagination - single page
conversations = client.conversations.list(
    tenant_name="acme-corp",
    offset=0,
    limit=50
)

# Auto-pagination - iterate over all items
for conv in client.conversations.list_iter(
    tenant_name="acme-corp",
    page_size=100  # Items per page
):
    print(conv.title)

# Limit total items fetched
for conv in client.conversations.list_iter(
    tenant_name="acme-corp",
    max_items=500  # Stop after 500 items
):
    print(conv.title)

Searching

# Search conversations by title
results = client.conversations.search(q="customer support")

# Search by metadata
results = client.conversations.search(
    metadata_key="environment",
    metadata_value="production"
)

# Combined search with filters
results = client.conversations.search(
    q="order",
    status=ConversationStatus.ACTIVE,
    tenant_name="acme-corp",
    agent_identifier="support-bot-v1"
)

# Search with auto-pagination
for conv in client.conversations.search_iter(q="urgent"):
    print(conv.title)

Managing Tenants

# List all tenants
tenants = client.tenants.list()

# Get tenant by ID
tenant = client.tenants.get(tenant_id=1)

# Get tenant by name
tenant = client.tenants.get_by_name("acme-corp")

# Iterate over all tenants
for tenant in client.tenants.list_iter():
    print(tenant.name)

Configuration

Basic Configuration

client = MarchHistoryClient(
    base_url="http://localhost:8000",
    timeout=60.0,  # Request timeout in seconds
    max_retries=5,  # Maximum retry attempts
)

Advanced Retry Configuration

from march_history.config import RetryConfig

client = MarchHistoryClient(
    base_url="http://localhost:8000",
    retry_config=RetryConfig(
        max_retries=5,
        backoff_factor=2.0,  # Exponential backoff multiplier
        retry_status_codes=(408, 429, 500, 502, 503, 504),
        max_backoff_seconds=120.0  # Max backoff time
    )
)

API Key Authentication

# Using api_key parameter (recommended)
client = MarchHistoryClient(
    base_url="http://localhost:8000",
    api_key="your-api-key-123"  # Sent as X-API-Key header
)

Custom Headers

client = MarchHistoryClient(
    base_url="http://localhost:8000",
    api_key="your-api-key",  # API key authentication
    custom_headers={
        "X-Custom-Header": "value"  # Additional custom headers
    }
)

Error Handling

The SDK provides a comprehensive exception hierarchy:

from march_history.exceptions import (
    MarchHistoryError,  # Base exception
    APIError,  # Base for API errors
    ValidationError,  # 422 validation errors
    NotFoundError,  # 404 not found
    ConflictError,  # 409 conflict (e.g., duplicate sequence number)
    BadRequestError,  # 400 bad request
    ServerError,  # 500+ server errors
    NetworkError,  # Network/connection errors
    RetryError,  # Max retries exceeded
)

try:
    conv = client.conversations.get(999)
except NotFoundError as e:
    print(f"Conversation not found: {e.message}")
    print(f"Status code: {e.status_code}")
    print(f"Error details: {e.details}")
except ValidationError as e:
    print(f"Validation failed: {e.message}")
    for detail in e.details:
        print(f"  - {detail['field']}: {detail['message']}")
except RetryError as e:
    print(f"Max retries exceeded: {e}")
    print(f"Last error: {e.last_exception}")
except APIError as e:
    print(f"API error: {e}")

Advanced Usage

Optional Context Manager

While not required, you can use context managers for explicit resource control:

# Guaranteed cleanup even if exceptions occur
with MarchHistoryClient(base_url="http://localhost:8000") as client:
    conv = client.conversations.create(
        tenant_name="acme-corp",
        user_id="user-123",
        title="Chat"
    )
    # ... work with client ...
# Connections automatically closed

Accessing Response Metadata

All models provide access to timestamps and IDs:

conv = client.conversations.create(
    tenant_name="acme-corp",
    user_id="user-123",
    title="Support"
)

print(f"ID: {conv.id}")
print(f"Tenant ID: {conv.tenant_id}")
print(f"User ID: {conv.user_id}")
print(f"Created: {conv.created_at}")
print(f"Updated: {conv.updated_at}")
print(f"Metadata: {conv.metadata}")

Working with Metadata

Metadata fields accept arbitrary JSON:

# Store custom data
conv = client.conversations.create(
    tenant_name="acme-corp",
    user_id="user-123",
    title="Support",
    metadata={
        "user_email": "user@example.com",
        "session": {
            "id": "sess-456",
            "tier": "premium"
        },
        "source": "mobile_app",
        "version": "1.2.3",
        "tags": ["urgent", "billing"]
    }
)

# Update metadata
conv = client.conversations.update(
    conv.id,
    metadata={
        **conv.metadata,
        "resolved": True,
        "resolution_time": 300
    }
)

Type Safety

The SDK is fully typed for excellent IDE support:

from march_history.models import MessageRole, ConversationStatus

# Type-safe enums
role: MessageRole = MessageRole.USER
status: ConversationStatus = ConversationStatus.ACTIVE

# Pydantic models validate at runtime
try:
    client.messages.create(
        conversation_id=1,
        role="invalid_role",  # Will raise ValidationError
        content="Hello"
    )
except ValidationError as e:
    print(f"Invalid role: {e}")

Examples

See the examples/ directory for more examples:

Requirements

  • Python 3.11+
  • httpx >= 0.25.0
  • pydantic >= 2.0.0

Development

# Install development dependencies
pip install -e ".[dev]"

# Run tests
pytest

# Run tests with coverage
pytest --cov=march_history --cov-report=html

# Run type checking
mypy src/march_history

# Run linting
ruff check src/march_history

# Format code
ruff format src/march_history

Testing

The SDK includes comprehensive tests:

  • Unit tests: Models, exceptions, configuration
  • Integration tests: Resources, API interactions (mocked with respx)
  • Pagination tests: Auto-pagination, page iterators
  • Retry tests: Exponential backoff, retry configuration

Run all tests:

pytest

Run specific test files:

pytest tests/test_models.py
pytest tests/test_resources.py
pytest tests/test_retry.py

Run with verbose output:

pytest -v

Test coverage:

pytest --cov=march_history --cov-report=term-missing

License

MIT License - see LICENSE file for details.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Support

For issues and questions:

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

march_history-0.2.2.tar.gz (39.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

march_history-0.2.2-py3-none-any.whl (35.0 kB view details)

Uploaded Python 3

File details

Details for the file march_history-0.2.2.tar.gz.

File metadata

  • Download URL: march_history-0.2.2.tar.gz
  • Upload date:
  • Size: 39.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.10

File hashes

Hashes for march_history-0.2.2.tar.gz
Algorithm Hash digest
SHA256 b854becb795d693dd8936b6e33078a857a3e1e7e5013c20825a858422618b523
MD5 78fda8ee7a0e9da17fea8595a95d7d0e
BLAKE2b-256 80e238f361c342bc615e4dbb28ab51576f23f8c818c9a0ebca54343a41cab79c

See more details on using hashes here.

File details

Details for the file march_history-0.2.2-py3-none-any.whl.

File metadata

  • Download URL: march_history-0.2.2-py3-none-any.whl
  • Upload date:
  • Size: 35.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.10

File hashes

Hashes for march_history-0.2.2-py3-none-any.whl
Algorithm Hash digest
SHA256 75c5dc126665d42ab56c149f427d896cc5f9998ca47508e2dd3a9a0f08105350
MD5 523998681459b5aed7360737dad2ddf5
BLAKE2b-256 adb241b98126054ae9a509fd8762bd256e98b850c0f909f76cbf7e902984c77c

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page