# Python SDK

Official Python SDK for the AIDP API.

## Installation

### pip

```bash
pip install aidp-sdk
```

### poetry

```bash
poetry add aidp-sdk
```

### Requirements

* Python 3.8+
* requests >= 2.28.0
* typing-extensions >= 4.0.0 (for Python < 3.10)

***

## Quick Start

```python
from aidp import AIDPClient
import os

client = AIDPClient(api_key=os.environ['AIDP_API_KEY'])

# Search for businesses
results = client.search(
    query='coffee shops with outdoor seating',
    location={'lat': 45.5231, 'lon': -122.6765, 'distance': '5km'}
)

for business in results.data.businesses:
    print(f"{business.name} - {business.category}")
```

***

## Configuration

### Basic Configuration

```python
from aidp import AIDPClient

client = AIDPClient(
    api_key='your-api-key',
    environment='production'  # or 'sandbox'
)
```

### Advanced Configuration

```python
client = AIDPClient(
    api_key=os.environ['AIDP_API_KEY'],
    environment='production',
    timeout=30,                    # Request timeout (seconds)
    max_retries=3,                 # Number of retries
    retry_delay=1,                 # Initial retry delay (seconds)
    base_url='https://api.aidp.dev/v1',
    headers={                      # Custom headers
        'X-Custom-Header': 'value'
    }
)
```

***

## API Methods

### Search

Search for businesses using natural language queries.

```python
results = client.search(
    query='coffee shops',
    location={
        'lat': 45.5231,
        'lon': -122.6765,
        'distance': '5km'
    },
    category='restaurants',
    limit=10,
    offset=0
)

# Access response data
print(f"Found {results.data.total} businesses")
print(f"AI Response: {results.data.response}")

for business in results.data.businesses:
    print(f"{business.name} - {business.distance}m away")
```

### Get Business

Get detailed information about a specific business.

```python
business = client.businesses.get('biz_abc123')

print(f"Name: {business.data.name}")
print(f"Category: {business.data.category}")
print(f"Rating: {business.data.averageRating}")
```

### Directory Search

Browse the business directory with filters.

```python
directory = client.directory(
    category='restaurants',
    city='Portland',
    state='OR',
    verification_level='verified',
    sort_by='aiVisibilityScore',
    limit=20
)

for business in directory.data.businesses:
    print(f"{business.name} - Score: {business.aiVisibilityScore}")
```

### Analytics

Get analytics for your business.

```python
# Upstream metrics
upstream = client.analytics.upstream(
    business_id='biz_abc123',
    start_date='2024-01-01',
    end_date='2024-12-31'
)

print(f"Total impressions: {upstream.data.totalImpressions}")
print(f"Citation rate: {upstream.data.citationRate}%")

# Intent journeys
journeys = client.analytics.intent_journeys(
    business_id='biz_abc123',
    period='last_30_days'
)

# Attribution
attribution = client.analytics.attribution(
    business_id='biz_abc123',
    model='multi_touch'
)
```

***

## Type Hints

The SDK includes full type hints for better IDE support:

```python
from aidp import AIDPClient, SearchParams, SearchResponse, Business
from typing import List

client = AIDPClient(api_key=os.environ['AIDP_API_KEY'])

# Type-safe parameters
params: SearchParams = {
    'query': 'coffee shops',
    'location': {'lat': 45.5231, 'lon': -122.6765, 'distance': '5km'}
}

# Type-safe response
response: SearchResponse = client.search(**params)

# Type-safe business data
businesses: List[Business] = response.data.businesses
```

***

## Error Handling

### Exception Types

```python
from aidp import (
    AIDPError,
    RateLimitError,
    ValidationError,
    AuthenticationError,
    NotFoundError
)

try:
    results = client.search(query='coffee')
except RateLimitError as e:
    print(f"Rate limited. Retry after {e.retry_after}s")
except ValidationError as e:
    print(f"Validation errors: {e.details}")
except AuthenticationError as e:
    print(f"Authentication failed: {e.message}")
except NotFoundError as e:
    print(f"Resource not found: {e.message}")
except AIDPError as e:
    print(f"API Error: {e.message} ({e.code})")
```

### Automatic Retries

The SDK automatically retries failed requests:

```python
client = AIDPClient(
    api_key=os.environ['AIDP_API_KEY'],
    max_retries=3,
    retry_delay=1,
    should_retry=lambda error: error.status >= 500
)
```

***

## Async Support

The SDK supports async/await with asyncio:

```python
import asyncio
from aidp import AsyncAIDPClient

async def search_businesses():
    client = AsyncAIDPClient(api_key=os.environ['AIDP_API_KEY'])

    results = await client.search(
        query='coffee shops',
        location={'lat': 45.5231, 'lon': -122.6765, 'distance': '5km'}
    )

    return results.data.businesses

# Run async function
businesses = asyncio.run(search_businesses())
```

### Concurrent Requests

```python
import asyncio
from aidp import AsyncAIDPClient

async def search_multiple_locations():
    client = AsyncAIDPClient(api_key=os.environ['AIDP_API_KEY'])

    locations = [
        {'lat': 45.5231, 'lon': -122.6765},  # Portland
        {'lat': 47.6062, 'lon': -122.3321},  # Seattle
        {'lat': 37.7749, 'lon': -122.4194},  # San Francisco
    ]

    tasks = [
        client.search(query='coffee', location=loc)
        for loc in locations
    ]

    results = await asyncio.gather(*tasks)
    return results

# Run concurrent searches
all_results = asyncio.run(search_multiple_locations())
```

***

## Advanced Usage

### Pagination

```python
def get_all_businesses(query: str):
    all_businesses = []
    offset = 0
    limit = 50

    while True:
        response = client.search(
            query=query,
            limit=limit,
            offset=offset
        )

        all_businesses.extend(response.data.businesses)

        if not response.data.pagination.has_more:
            break

        offset += limit

    return all_businesses

# Get all results
businesses = get_all_businesses('coffee shops')
print(f"Found {len(businesses)} total businesses")
```

### Custom Headers

```python
results = client.search(
    query='coffee',
    headers={
        'X-Session-ID': 'session-123',
        'X-User-ID': 'user-456'
    }
)
```

### Request Timeout

```python
# Per-request timeout
results = client.search(
    query='coffee',
    timeout=10  # 10 seconds
)

# Global timeout
client = AIDPClient(
    api_key=os.environ['AIDP_API_KEY'],
    timeout=30  # 30 seconds for all requests
)
```

***

## Framework Integration

### Django

```python
# settings.py
AIDP_API_KEY = os.environ.get('AIDP_API_KEY')

# views.py
from django.http import JsonResponse
from aidp import AIDPClient
from django.conf import settings

client = AIDPClient(api_key=settings.AIDP_API_KEY)

def search_businesses(request):
    query = request.GET.get('query')

    try:
        results = client.search(query=query)
        return JsonResponse({
            'businesses': [b.dict() for b in results.data.businesses]
        })
    except Exception as e:
        return JsonResponse({'error': str(e)}, status=500)
```

### Flask

```python
from flask import Flask, request, jsonify
from aidp import AIDPClient
import os

app = Flask(__name__)
client = AIDPClient(api_key=os.environ['AIDP_API_KEY'])

@app.route('/api/search', methods=['POST'])
def search():
    data = request.json

    try:
        results = client.search(**data)
        return jsonify({
            'businesses': [b.dict() for b in results.data.businesses]
        })
    except Exception as e:
        return jsonify({'error': str(e)}), 500

if __name__ == '__main__':
    app.run()
```

### FastAPI

```python
from fastapi import FastAPI, HTTPException
from aidp import AsyncAIDPClient
from pydantic import BaseModel
import os

app = FastAPI()
client = AsyncAIDPClient(api_key=os.environ['AIDP_API_KEY'])

class SearchRequest(BaseModel):
    query: str
    location: dict

@app.post('/api/search')
async def search(request: SearchRequest):
    try:
        results = await client.search(
            query=request.query,
            location=request.location
        )
        return results.data.dict()
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))
```

***

## Testing

### Mock Client

```python
from unittest.mock import Mock
from aidp import AIDPClient

# Create mock client
mock_client = Mock(spec=AIDPClient)
mock_client.search.return_value = Mock(
    success=True,
    data=Mock(
        businesses=[
            Mock(id='biz_test', name='Test Business')
        ]
    )
)

# Use in tests
def test_search():
    results = mock_client.search(query='coffee')
    assert len(results.data.businesses) == 1
    assert results.data.businesses[0].name == 'Test Business'
```

### pytest

```python
import pytest
from aidp import AIDPClient

@pytest.fixture
def client():
    return AIDPClient(api_key='test_key')

def test_search(client):
    results = client.search(query='coffee')
    assert results.success is True
    assert len(results.data.businesses) > 0
```

***

## Need Help?

* 📧 Email: <sdk@aidp.dev>
* 💬 GitHub Discussions: [github.com/aidp/platform/discussions](https://github.com/aidp/platform/discussions)
* 📚 API Reference: [docs.aidp.dev/api](https://docs.aidp.dev/api)
* 🐛 Issues: [github.com/aidp/sdk-python/issues](https://github.com/aidp/sdk-python/issues)
