> ## Documentation Index
> Fetch the complete documentation index at: https://canopy.wnstn.space/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Getting Started with the Cano Analytics Python SDK

> Install and use the Cano Python SDK to track events and identify users from backend services, with synchronous and async support for any Python framework.

The Cano Python SDK lets you track events and identify users from backend services using a simple, synchronous API — with optional async support for modern frameworks like FastAPI. The SDK queues events in a background thread, batches them automatically, and retries on transient failures so your application code stays clean and non-blocking.

## Requirements

* Python **3.8** or higher
* No other runtime dependencies beyond the standard library and `requests`

## Installation

Install the package from PyPI using pip:

```bash theme={null}
pip install cano-analytics
```

The package is also available via conda:

```bash theme={null}
conda install -c conda-forge cano-analytics
```

## Initialization

Create a single `CanoClient` instance when your application starts and reuse it across your codebase. The client spawns a background worker thread that handles batching and delivery automatically.

```python cano_client.py theme={null}
from cano import CanoClient

client = CanoClient(
    api_key='cano_live_sk_your_key_here',
    # Optional settings:
    batch_size=100,        # events per batch
    flush_interval=10.0,   # seconds between auto-flushes
    timeout=5.0,           # request timeout in seconds
    debug=False
)
```

## Core Methods

### `client.track(event, user_id=None, anonymous_id=None, properties=None, timestamp=None)`

Call `track` whenever a user performs an action you want to measure. Supply either a `user_id` (for authenticated users) or an `anonymous_id` (for unidentified visitors) — at least one is required.

```python theme={null}
client.track(
    event='purchase_completed',
    user_id='usr_123',
    properties={
        'plan': 'pro',
        'amount': 49.00,
        'currency': 'USD'
    }
)
```

Pass an ISO 8601 string to `timestamp` if you need to record an event that occurred in the past:

```python theme={null}
client.track(
    event='file_exported',
    user_id='usr_123',
    timestamp='2024-06-01T14:32:00Z'
)
```

### `client.identify(user_id, traits=None, timestamp=None)`

Call `identify` to associate a user ID with a set of traits. This enriches all future events from that user with the traits you provide.

```python theme={null}
client.identify(
    user_id='usr_123',
    traits={
        'name': 'Ada Lovelace',
        'email': 'ada@example.com',
        'company': 'Analytical Engines Ltd'
    }
)
```

### `client.page(user_id, name=None, properties=None)`

Call `page` to record a server-side page or screen view — useful for server-rendered apps where the frontend SDK isn't available.

```python theme={null}
client.page(
    user_id='usr_123',
    name='Dashboard',
    properties={
        'url': 'https://app.example.com/dashboard',
        'referrer': 'https://app.example.com/login'
    }
)
```

### `client.alias(user_id, previous_id)`

Call `alias` to merge two identities — for example, when a previously anonymous user signs up and you want to link their historical events to their new account.

```python theme={null}
client.alias(user_id='usr_123', previous_id='anon_abc987')
```

### `client.flush()`

Call `flush` to synchronously send all events currently queued in memory. This is a blocking call and waits until the request completes or times out.

```python theme={null}
client.flush()
```

### `client.shutdown()`

Call `shutdown` to flush all remaining queued events and gracefully stop the background worker thread. Always call this before your process exits.

```python theme={null}
client.shutdown()
```

<Tip>
  Always call `client.shutdown()` before your process exits — for example, in an `atexit` handler or a SIGTERM handler. Without it, any events still in the queue will be lost when the process terminates.

  ```python theme={null}
  import atexit
  atexit.register(client.shutdown)
  ```
</Tip>

## Django Middleware

You can automatically track every incoming HTTP request in a Django application by adding a lightweight middleware class. Place this in your project's `middleware.py` file and add it to your `MIDDLEWARE` setting.

```python middleware.py theme={null}
from cano import CanoClient

cano_client = CanoClient(api_key='cano_live_sk_your_key_here')

class CanoTrackingMiddleware:
    """Automatically tracks each request as a page view in Cano Analytics."""

    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        response = self.get_response(request)

        user_id = (
            str(request.user.pk)
            if request.user.is_authenticated
            else None
        )
        anonymous_id = request.session.session_key

        cano_client.page(
            user_id=user_id,
            name=request.resolver_match.view_name if request.resolver_match else None,
            properties={
                'path': request.path,
                'method': request.method,
                'status_code': response.status_code,
                'anonymous_id': anonymous_id
            }
        )

        return response
```

Add the middleware to your `settings.py`:

```python settings.py theme={null}
MIDDLEWARE = [
    # ... your existing middleware ...
    'yourapp.middleware.CanoTrackingMiddleware',
]
```

## Async Support

<Note>
  For async frameworks like FastAPI, Starlette, or async Django views, use `AsyncCanoClient` instead of `CanoClient`. The async client exposes the same interface but all methods are coroutines — `await` each call accordingly.

  ```python theme={null}
  from cano import AsyncCanoClient

  client = AsyncCanoClient(api_key='cano_live_sk_your_key_here')

  # In an async route handler:
  await client.track('event_name', user_id='usr_123')
  await client.identify('usr_123', traits={'plan': 'pro'})
  ```
</Note>

## Error Handling and Retries

The SDK automatically retries failed requests on transient errors (HTTP `429 Too Many Requests` and `5xx` server errors) with exponential backoff. By default it retries up to **3 times** before dropping the batch and logging an error.

To handle errors explicitly, pass an `on_error` callback when initializing the client:

```python error-handling.py theme={null}
import logging

logger = logging.getLogger(__name__)

def handle_error(error, batch):
    logger.error(
        'Cano Analytics delivery failed: %s — %d events dropped',
        error,
        len(batch)
    )
    # Optionally forward to your alerting system here

client = CanoClient(
    api_key='cano_live_sk_your_key_here',
    on_error=handle_error
)
```

The `batch` argument passed to `on_error` is the list of event dictionaries that failed to deliver, so you can log or re-queue them as needed.
