|
| 1 | +--- |
| 2 | +description: |
| 3 | +globs: |
| 4 | +alwaysApply: false |
| 5 | +--- |
| 6 | +# Project Context: Siren AI Python SDK |
| 7 | + |
| 8 | +## Project Summary |
| 9 | + |
| 10 | +Official Python SDK for the [Siren notification platform](mdc:https:/docs.trysiren.io). Provides type-safe interface for managing templates, workflows, users, messaging, and webhooks. Built with Pydantic validation, structured error handling, and modular client-based architecture. |
| 11 | + |
| 12 | +## Key Features / Functional Modules |
| 13 | + |
| 14 | +- **Templates** - Create, update, delete, publish templates and channel configurations → `siren/clients/templates.py` |
| 15 | +- **Users** - Add, update, delete users → `siren/clients/users.py` |
| 16 | +- **Messaging** - Send messages, get replies, track status → `siren/clients/messaging.py` |
| 17 | +- **Webhooks** - Configure notification and inbound webhooks → `siren/clients/webhooks.py` |
| 18 | +- **Workflows** - Trigger single/bulk workflows and scheduling → `siren/clients/workflows.py` |
| 19 | +- **Client** - Unified API entry point → `siren/client.py` |
| 20 | + |
| 21 | +## Codebase Structure Overview |
| 22 | + |
| 23 | +``` |
| 24 | +siren-ai/ |
| 25 | +├── siren/ # Main SDK package |
| 26 | +│ ├── client.py # Main SirenClient - unified API entry point |
| 27 | +│ ├── exceptions.py # Custom exception classes (SirenAPIError, SirenSDKError) |
| 28 | +│ ├── clients/ # Domain client implementations (core pattern) |
| 29 | +│ │ ├── base.py # BaseClient - shared HTTP/error handling |
| 30 | +│ │ ├── templates.py # TemplateClient - template operations |
| 31 | +│ │ ├── users.py # UserClient - user management |
| 32 | +│ │ ├── messaging.py # MessageClient - message operations |
| 33 | +│ │ ├── webhooks.py # WebhookClient - webhook configuration |
| 34 | +│ │ └── workflows.py # WorkflowClient - workflow operations |
| 35 | +│ └── models/ # Pydantic data models |
| 36 | +│ ├── base.py # Base response models and common patterns |
| 37 | +│ ├── templates.py # Template-specific models |
| 38 | +│ ├── user.py # User-specific models |
| 39 | +│ ├── messaging.py # Messaging models |
| 40 | +│ ├── webhooks.py # Webhook models |
| 41 | +│ └── workflows.py # Workflow models |
| 42 | +├── tests/ # Comprehensive test suite with ~92% coverage |
| 43 | +├── examples/ # Usage examples for each module |
| 44 | +├── pyproject.toml # Project configuration, dependencies, tools |
| 45 | +└── README.md # Installation, usage, and API documentation |
| 46 | +``` |
| 47 | + |
| 48 | +## Architecture & Data Flow |
| 49 | + |
| 50 | +**Layered Architecture**: |
| 51 | +- **Client** (`SirenClient`) - Thin facade delegating to domain clients |
| 52 | +- **Domain Clients** (`TemplateClient`, `UserClient`, etc.) - Domain-specific API handlers, inherit from `BaseClient` for unified HTTP/error handling |
| 53 | +- **Models** (Pydantic) - Request/response validation, field aliasing (snake_case ↔ camelCase) |
| 54 | +- **Exceptions** - `SirenAPIError` (API errors: 400/401/404) vs `SirenSDKError` (SDK issues: network/validation) |
| 55 | + |
| 56 | +**BaseClient Pattern** (Core Architecture): |
| 57 | +- All domain clients inherit from `BaseClient` for consistent HTTP handling |
| 58 | +- Requires both `request_model` and `response_model` for JSON operations |
| 59 | +- Automatic Pydantic validation, error handling, and response parsing |
| 60 | +- Common patterns: `DeleteResponse[None]` for 204 responses, flexible models with optional fields |
| 61 | + |
| 62 | +**Request Flow**: Client → Domain Client → HTTP Request → API → Response → Model → Client |
| 63 | +- Domain clients prepare requests with Pydantic validation → HTTP to Siren API → Responses parsed through models → Errors become structured exceptions |
| 64 | + |
| 65 | +**Implementation Details**: |
| 66 | +- **HTTP Client**: `requests` library with 10s timeout (hardcoded, TODO: make configurable) |
| 67 | +- **Authentication**: Bearer token in `Authorization` header |
| 68 | +- **Status Handling**: Explicit `if status_code == 200` checks instead of `response.ok` |
| 69 | +- **API Versioning**: Templates/Users/Messaging/Webhooks use `/api/v1/public/`, Workflows use `/api/v2/` |
| 70 | +- **Environment Support**: Both `SirenClient` *and* `AsyncSirenClient` automatically read `SIREN_API_KEY` and optional `SIREN_ENV` on instantiation. Production (`https://api.trysiren.io`) is the default; switch to dev (`https://api.dev.trysiren.io`) by setting `SIREN_ENV=dev` or passing `env="dev"` explicitly. |
| 71 | + |
| 72 | +## Tech Stack |
| 73 | + |
| 74 | +**Core**: Python 3.8+, `requests`, `pydantic[email]` |
| 75 | +**Dev Tools**: `pytest` + mocking, `ruff`, `pyright`, `pre-commit`, `uv` |
| 76 | + |
| 77 | +## Testing |
| 78 | + |
| 79 | +**Strategy**: `requests-mock` with realistic API data |
| 80 | +**Organization**: One test file per domain client, shared `client` fixture |
| 81 | +**Philosophy**: SDK testing focuses on request formatting, response parsing, error propagation - not API business logic |
| 82 | + |
| 83 | +## Key Files |
| 84 | + |
| 85 | +- **`siren/client.py`** - Main client interface |
| 86 | +- **`siren/clients/base.py`** - BaseClient with unified HTTP/error handling (core pattern) |
| 87 | +- **`siren/clients/templates.py`** - Most complex domain client, full BaseClient patterns |
| 88 | +- **`siren/models/base.py`** - Core models and error handling |
| 89 | +- **`siren/exceptions.py`** - Exception patterns |
| 90 | + |
| 91 | +## Gotchas |
| 92 | + |
| 93 | +**Field Serialization**: Always use `by_alias=True` when calling `model_dump()` |
| 94 | +**BaseClient Requirements**: Both request_model and response_model needed for JSON operations |
| 95 | + |
| 96 | +## TODO / Future Areas |
| 97 | + |
| 98 | +**Architecture Enhancements**: |
| 99 | +- Add retry logic for transient network failures |
| 100 | +- Add request/response logging capabilities |
| 101 | + |
| 102 | +**Testing Gaps**: |
| 103 | +- Integration tests against live API (currently only unit tests with mocks) |
| 104 | + |
| 105 | +## Documentation / Examples |
| 106 | + |
| 107 | +**Example Script Guidelines**: |
| 108 | +- Call `dotenv.load_dotenv()` first so environment variables from a `.env` file are available. |
| 109 | +- Instantiate the sync (`SirenClient()`) or async (`AsyncSirenClient()`) client **without arguments** – the constructor will pick up `SIREN_API_KEY` & `SIREN_ENV` automatically. |
| 110 | +- Wrap core SDK calls in minimal error handling: |
| 111 | + ```python |
| 112 | + try: |
| 113 | + ... # SDK call(s) |
| 114 | + except SirenAPIError as e: |
| 115 | + print(f"API error: {e.error_code} - {e.api_message}") |
| 116 | + except SirenSDKError as e: |
| 117 | + print(f"SDK error: {e.message}") |
| 118 | + ``` |
| 119 | +- Print only the key fields from responses (e.g., `id`, `url`, `status`) to keep output concise. |
| 120 | +- Scripts should demonstrate one or two primary operations per domain—avoid extra verbosity. |
| 121 | + |
| 122 | +## HTTP Transport & Sync/Async Support |
| 123 | +- Under the hood the SDK now uses a pluggable transport layer (`siren/http/transport.py`). |
| 124 | +- **Sync** clients delegate to `SyncTransport` (currently wraps `requests`, easy to flip to `httpx.Client`). |
| 125 | +- **Async** clients delegate to `AsyncTransport` which wraps `httpx.AsyncClient`. |
| 126 | +- Every domain client has a 1-to-1 async counterpart; `AsyncSirenClient` exposes them. |
| 127 | +- Sync and async share identical method names and signatures—just `await` the async version. |
| 128 | +- Testing: existing sync tests use `requests-mock`; async tests use **respx** for `httpx`. |
| 129 | +- Examples: each domain has both `*_async.py` and sync counterpart in `examples/` demonstrating identical flows. |
0 commit comments