causality-engine 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- causality_engine-1.0.0/LICENSE +21 -0
- causality_engine-1.0.0/PKG-INFO +216 -0
- causality_engine-1.0.0/README.md +181 -0
- causality_engine-1.0.0/causality/__init__.py +32 -0
- causality_engine-1.0.0/causality_engine/__init__.py +45 -0
- causality_engine-1.0.0/causality_engine/_http.py +171 -0
- causality_engine-1.0.0/causality_engine/client.py +111 -0
- causality_engine-1.0.0/causality_engine/exceptions.py +67 -0
- causality_engine-1.0.0/causality_engine/models.py +52 -0
- causality_engine-1.0.0/causality_engine/py.typed +1 -0
- causality_engine-1.0.0/causality_engine/resources/__init__.py +27 -0
- causality_engine-1.0.0/causality_engine/resources/_base.py +31 -0
- causality_engine-1.0.0/causality_engine/resources/agents.py +67 -0
- causality_engine-1.0.0/causality_engine/resources/attribution.py +102 -0
- causality_engine-1.0.0/causality_engine/resources/auth.py +59 -0
- causality_engine-1.0.0/causality_engine/resources/billing.py +50 -0
- causality_engine-1.0.0/causality_engine/resources/brand.py +42 -0
- causality_engine-1.0.0/causality_engine/resources/campaigns.py +41 -0
- causality_engine-1.0.0/causality_engine/resources/channels.py +66 -0
- causality_engine-1.0.0/causality_engine/resources/commissions.py +61 -0
- causality_engine-1.0.0/causality_engine/resources/health.py +40 -0
- causality_engine-1.0.0/causality_engine/resources/journeys.py +68 -0
- causality_engine-1.0.0/causality_engine/resources/referrals.py +37 -0
- causality_engine-1.0.0/pyproject.toml +60 -0
- causality_engine-1.0.0/tests/test_sdk.py +476 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Causality Engine
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: causality-engine
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Official Python SDK for the Causality Engine API — causal attribution for e-commerce.
|
|
5
|
+
Project-URL: Homepage, https://causalityengine.ai
|
|
6
|
+
Project-URL: Documentation, https://developers.causalityengine.ai
|
|
7
|
+
Project-URL: Repository, https://github.com/causalityengine/causality-engine-python-sdk
|
|
8
|
+
Project-URL: Bug Tracker, https://github.com/causalityengine/causality-engine-python-sdk/issues
|
|
9
|
+
Project-URL: API Reference, https://developers.causalityengine.ai/api-reference
|
|
10
|
+
Author-email: Causality Engine <dev@causalityengine.ai>
|
|
11
|
+
License-Expression: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: analytics,attribution,causal-inference,causality,ecommerce,marketing,marketing-attribution,shopify
|
|
14
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
25
|
+
Classifier: Topic :: Scientific/Engineering
|
|
26
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
27
|
+
Classifier: Typing :: Typed
|
|
28
|
+
Requires-Python: >=3.8
|
|
29
|
+
Requires-Dist: httpx<1.0.0,>=0.24.0
|
|
30
|
+
Provides-Extra: dev
|
|
31
|
+
Requires-Dist: pytest-asyncio>=0.21; extra == 'dev'
|
|
32
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
33
|
+
Requires-Dist: respx>=0.20; extra == 'dev'
|
|
34
|
+
Description-Content-Type: text/markdown
|
|
35
|
+
|
|
36
|
+
# Causality Engine Python SDK
|
|
37
|
+
|
|
38
|
+
The official Python SDK for the [Causality Engine](https://causalityengine.ai) API — causal attribution for e-commerce. Replace last-click with math.
|
|
39
|
+
|
|
40
|
+
[](https://pypi.org/project/causality-engine/)
|
|
41
|
+
[](https://pypi.org/project/causality-engine/)
|
|
42
|
+
[](https://opensource.org/licenses/MIT)
|
|
43
|
+
|
|
44
|
+
## Installation
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install causality-engine
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Quick Start
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
import causality
|
|
54
|
+
|
|
55
|
+
ce = causality.CausalityEngine(api_key="ce_live_sk_...")
|
|
56
|
+
|
|
57
|
+
# Run causal attribution analysis
|
|
58
|
+
result = ce.attribution.analyze(
|
|
59
|
+
store="my-store.myshopify.com",
|
|
60
|
+
date_range=["2026-01-01", "2026-01-31"],
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
for ch in result.data["channel_impact"]:
|
|
64
|
+
print(f"{ch['channel']}: {ch['causal_lift']:.0%} lift, ROI {ch['roi']:.1f}x")
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Authentication
|
|
68
|
+
|
|
69
|
+
Get your API key at [developers.causalityengine.ai/api-keys](https://developers.causalityengine.ai/api-keys).
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
# Option 1: Pass directly
|
|
73
|
+
ce = causality.CausalityEngine(api_key="ce_live_sk_...")
|
|
74
|
+
|
|
75
|
+
# Option 2: Environment variable
|
|
76
|
+
# export CAUSALITY_ENGINE_API_KEY="ce_live_sk_..."
|
|
77
|
+
ce = causality.CausalityEngine()
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## API Coverage
|
|
81
|
+
|
|
82
|
+
| Resource | Methods | Description |
|
|
83
|
+
|---|---|---|
|
|
84
|
+
| `ce.attribution` | `analyze()`, `retrieve()`, `list()` | Causal attribution analysis |
|
|
85
|
+
| `ce.channels` | `performance()`, `amplification()` | Cross-channel performance |
|
|
86
|
+
| `ce.journeys` | `flow()`, `leakage()` | Customer journey mapping |
|
|
87
|
+
| `ce.campaigns` | `overview()` | Campaign-level intelligence |
|
|
88
|
+
| `ce.health` | `score()` | Marketing health diagnostics |
|
|
89
|
+
| `ce.brand` | `decompose()` | Brand awareness decomposition |
|
|
90
|
+
| `ce.agents` | `register()`, `me()`, `usage()` | AI agent management |
|
|
91
|
+
| `ce.referrals` | `list()` | Referral chain tracking |
|
|
92
|
+
| `ce.commissions` | `list()`, `verify()` | Commission ledger |
|
|
93
|
+
| `ce.billing` | `summary()`, `value_proofs()` | Billing & Delta_R tracking |
|
|
94
|
+
| `ce.auth` | `token()`, `rotate_key()` | OAuth & key management |
|
|
95
|
+
|
|
96
|
+
## Examples
|
|
97
|
+
|
|
98
|
+
### Channel Performance
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
perf = ce.channels.performance(
|
|
102
|
+
data_source_id="ds_abc123",
|
|
103
|
+
date_range=["2026-01-01", "2026-01-31"],
|
|
104
|
+
)
|
|
105
|
+
for ch in perf.data["channels"]:
|
|
106
|
+
print(f"{ch['name']}: score {ch['performance_score']}")
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Cross-Channel Amplification
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
amp = ce.channels.amplification(
|
|
113
|
+
data_source_id="ds_abc123",
|
|
114
|
+
date_range=["2026-01-01", "2026-01-31"],
|
|
115
|
+
)
|
|
116
|
+
print(f"Strongest pair: {amp.data['strongest_pair']}")
|
|
117
|
+
print(f"Total synergy: {amp.data['total_synergy_score']}")
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Customer Journey Flow
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
flow = ce.journeys.flow(
|
|
124
|
+
data_source_id="ds_abc123",
|
|
125
|
+
date_range=["2026-01-01", "2026-01-31"],
|
|
126
|
+
min_conversions=10,
|
|
127
|
+
)
|
|
128
|
+
for path in flow.data["top_paths"]:
|
|
129
|
+
print(f"{' → '.join(path['path'])}: {path['conversions']} conversions")
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Register an AI Agent
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
agent = ce.agents.register(
|
|
136
|
+
name="my-attribution-agent",
|
|
137
|
+
operator_email="dev@yourcompany.com",
|
|
138
|
+
capabilities=["attribution", "channel_analysis"],
|
|
139
|
+
)
|
|
140
|
+
print(f"Agent ID: {agent.data['agent_id']}")
|
|
141
|
+
print(f"Referral code: {agent.data['referral_code']}")
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Marketing Health Score
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
health = ce.health.score(
|
|
148
|
+
data_source_id="ds_abc123",
|
|
149
|
+
date_range=["2026-01-01", "2026-01-31"],
|
|
150
|
+
)
|
|
151
|
+
print(f"Score: {health.data['score']}/100 (Grade: {health.data['grade']})")
|
|
152
|
+
print(f"Recommendation: {health.data['top_recommendation']}")
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Error Handling
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
from causality_engine import (
|
|
159
|
+
AuthenticationError,
|
|
160
|
+
RateLimitError,
|
|
161
|
+
NotFoundError,
|
|
162
|
+
ValidationError,
|
|
163
|
+
CausalityEngineError,
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
try:
|
|
167
|
+
result = ce.attribution.analyze(
|
|
168
|
+
data_source_id="ds_abc123",
|
|
169
|
+
date_range=["2026-01-01", "2026-01-31"],
|
|
170
|
+
)
|
|
171
|
+
except AuthenticationError:
|
|
172
|
+
print("Invalid API key")
|
|
173
|
+
except RateLimitError as e:
|
|
174
|
+
print(f"Rate limited. Retry after {e.retry_after}s")
|
|
175
|
+
except NotFoundError:
|
|
176
|
+
print("Resource not found")
|
|
177
|
+
except ValidationError as e:
|
|
178
|
+
print(f"Invalid request: {e.message}")
|
|
179
|
+
except CausalityEngineError as e:
|
|
180
|
+
print(f"API error: {e.message} (status {e.status_code})")
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## Configuration
|
|
184
|
+
|
|
185
|
+
```python
|
|
186
|
+
ce = causality.CausalityEngine(
|
|
187
|
+
api_key="ce_live_sk_...",
|
|
188
|
+
base_url="https://api.causalityengine.ai", # default
|
|
189
|
+
timeout=30.0, # request timeout in seconds
|
|
190
|
+
max_retries=3, # retries on 429/5xx errors
|
|
191
|
+
)
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
The SDK automatically retries on rate limits (429) and server errors (5xx) with exponential backoff.
|
|
195
|
+
|
|
196
|
+
## Context Manager
|
|
197
|
+
|
|
198
|
+
```python
|
|
199
|
+
with causality.CausalityEngine(api_key="ce_live_sk_...") as ce:
|
|
200
|
+
result = ce.attribution.analyze(
|
|
201
|
+
store="demo.myshopify.com",
|
|
202
|
+
date_range=["2026-01-01", "2026-01-31"],
|
|
203
|
+
)
|
|
204
|
+
# Transport is automatically closed
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
## Documentation
|
|
208
|
+
|
|
209
|
+
- [Developer Portal](https://developers.causalityengine.ai)
|
|
210
|
+
- [API Reference](https://developers.causalityengine.ai/api-reference)
|
|
211
|
+
- [Quickstart Guide](https://developers.causalityengine.ai/quickstart)
|
|
212
|
+
- [Agent Partner Program](https://developers.causalityengine.ai/agent-program)
|
|
213
|
+
|
|
214
|
+
## License
|
|
215
|
+
|
|
216
|
+
MIT
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# Causality Engine Python SDK
|
|
2
|
+
|
|
3
|
+
The official Python SDK for the [Causality Engine](https://causalityengine.ai) API — causal attribution for e-commerce. Replace last-click with math.
|
|
4
|
+
|
|
5
|
+
[](https://pypi.org/project/causality-engine/)
|
|
6
|
+
[](https://pypi.org/project/causality-engine/)
|
|
7
|
+
[](https://opensource.org/licenses/MIT)
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install causality-engine
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
import causality
|
|
19
|
+
|
|
20
|
+
ce = causality.CausalityEngine(api_key="ce_live_sk_...")
|
|
21
|
+
|
|
22
|
+
# Run causal attribution analysis
|
|
23
|
+
result = ce.attribution.analyze(
|
|
24
|
+
store="my-store.myshopify.com",
|
|
25
|
+
date_range=["2026-01-01", "2026-01-31"],
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
for ch in result.data["channel_impact"]:
|
|
29
|
+
print(f"{ch['channel']}: {ch['causal_lift']:.0%} lift, ROI {ch['roi']:.1f}x")
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Authentication
|
|
33
|
+
|
|
34
|
+
Get your API key at [developers.causalityengine.ai/api-keys](https://developers.causalityengine.ai/api-keys).
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
# Option 1: Pass directly
|
|
38
|
+
ce = causality.CausalityEngine(api_key="ce_live_sk_...")
|
|
39
|
+
|
|
40
|
+
# Option 2: Environment variable
|
|
41
|
+
# export CAUSALITY_ENGINE_API_KEY="ce_live_sk_..."
|
|
42
|
+
ce = causality.CausalityEngine()
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## API Coverage
|
|
46
|
+
|
|
47
|
+
| Resource | Methods | Description |
|
|
48
|
+
|---|---|---|
|
|
49
|
+
| `ce.attribution` | `analyze()`, `retrieve()`, `list()` | Causal attribution analysis |
|
|
50
|
+
| `ce.channels` | `performance()`, `amplification()` | Cross-channel performance |
|
|
51
|
+
| `ce.journeys` | `flow()`, `leakage()` | Customer journey mapping |
|
|
52
|
+
| `ce.campaigns` | `overview()` | Campaign-level intelligence |
|
|
53
|
+
| `ce.health` | `score()` | Marketing health diagnostics |
|
|
54
|
+
| `ce.brand` | `decompose()` | Brand awareness decomposition |
|
|
55
|
+
| `ce.agents` | `register()`, `me()`, `usage()` | AI agent management |
|
|
56
|
+
| `ce.referrals` | `list()` | Referral chain tracking |
|
|
57
|
+
| `ce.commissions` | `list()`, `verify()` | Commission ledger |
|
|
58
|
+
| `ce.billing` | `summary()`, `value_proofs()` | Billing & Delta_R tracking |
|
|
59
|
+
| `ce.auth` | `token()`, `rotate_key()` | OAuth & key management |
|
|
60
|
+
|
|
61
|
+
## Examples
|
|
62
|
+
|
|
63
|
+
### Channel Performance
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
perf = ce.channels.performance(
|
|
67
|
+
data_source_id="ds_abc123",
|
|
68
|
+
date_range=["2026-01-01", "2026-01-31"],
|
|
69
|
+
)
|
|
70
|
+
for ch in perf.data["channels"]:
|
|
71
|
+
print(f"{ch['name']}: score {ch['performance_score']}")
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Cross-Channel Amplification
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
amp = ce.channels.amplification(
|
|
78
|
+
data_source_id="ds_abc123",
|
|
79
|
+
date_range=["2026-01-01", "2026-01-31"],
|
|
80
|
+
)
|
|
81
|
+
print(f"Strongest pair: {amp.data['strongest_pair']}")
|
|
82
|
+
print(f"Total synergy: {amp.data['total_synergy_score']}")
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Customer Journey Flow
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
flow = ce.journeys.flow(
|
|
89
|
+
data_source_id="ds_abc123",
|
|
90
|
+
date_range=["2026-01-01", "2026-01-31"],
|
|
91
|
+
min_conversions=10,
|
|
92
|
+
)
|
|
93
|
+
for path in flow.data["top_paths"]:
|
|
94
|
+
print(f"{' → '.join(path['path'])}: {path['conversions']} conversions")
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Register an AI Agent
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
agent = ce.agents.register(
|
|
101
|
+
name="my-attribution-agent",
|
|
102
|
+
operator_email="dev@yourcompany.com",
|
|
103
|
+
capabilities=["attribution", "channel_analysis"],
|
|
104
|
+
)
|
|
105
|
+
print(f"Agent ID: {agent.data['agent_id']}")
|
|
106
|
+
print(f"Referral code: {agent.data['referral_code']}")
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Marketing Health Score
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
health = ce.health.score(
|
|
113
|
+
data_source_id="ds_abc123",
|
|
114
|
+
date_range=["2026-01-01", "2026-01-31"],
|
|
115
|
+
)
|
|
116
|
+
print(f"Score: {health.data['score']}/100 (Grade: {health.data['grade']})")
|
|
117
|
+
print(f"Recommendation: {health.data['top_recommendation']}")
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Error Handling
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
from causality_engine import (
|
|
124
|
+
AuthenticationError,
|
|
125
|
+
RateLimitError,
|
|
126
|
+
NotFoundError,
|
|
127
|
+
ValidationError,
|
|
128
|
+
CausalityEngineError,
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
try:
|
|
132
|
+
result = ce.attribution.analyze(
|
|
133
|
+
data_source_id="ds_abc123",
|
|
134
|
+
date_range=["2026-01-01", "2026-01-31"],
|
|
135
|
+
)
|
|
136
|
+
except AuthenticationError:
|
|
137
|
+
print("Invalid API key")
|
|
138
|
+
except RateLimitError as e:
|
|
139
|
+
print(f"Rate limited. Retry after {e.retry_after}s")
|
|
140
|
+
except NotFoundError:
|
|
141
|
+
print("Resource not found")
|
|
142
|
+
except ValidationError as e:
|
|
143
|
+
print(f"Invalid request: {e.message}")
|
|
144
|
+
except CausalityEngineError as e:
|
|
145
|
+
print(f"API error: {e.message} (status {e.status_code})")
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Configuration
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
ce = causality.CausalityEngine(
|
|
152
|
+
api_key="ce_live_sk_...",
|
|
153
|
+
base_url="https://api.causalityengine.ai", # default
|
|
154
|
+
timeout=30.0, # request timeout in seconds
|
|
155
|
+
max_retries=3, # retries on 429/5xx errors
|
|
156
|
+
)
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
The SDK automatically retries on rate limits (429) and server errors (5xx) with exponential backoff.
|
|
160
|
+
|
|
161
|
+
## Context Manager
|
|
162
|
+
|
|
163
|
+
```python
|
|
164
|
+
with causality.CausalityEngine(api_key="ce_live_sk_...") as ce:
|
|
165
|
+
result = ce.attribution.analyze(
|
|
166
|
+
store="demo.myshopify.com",
|
|
167
|
+
date_range=["2026-01-01", "2026-01-31"],
|
|
168
|
+
)
|
|
169
|
+
# Transport is automatically closed
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## Documentation
|
|
173
|
+
|
|
174
|
+
- [Developer Portal](https://developers.causalityengine.ai)
|
|
175
|
+
- [API Reference](https://developers.causalityengine.ai/api-reference)
|
|
176
|
+
- [Quickstart Guide](https://developers.causalityengine.ai/quickstart)
|
|
177
|
+
- [Agent Partner Program](https://developers.causalityengine.ai/agent-program)
|
|
178
|
+
|
|
179
|
+
## License
|
|
180
|
+
|
|
181
|
+
MIT
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Alias module so ``import causality`` works.
|
|
3
|
+
|
|
4
|
+
This re-exports everything from ``causality_engine`` for convenience,
|
|
5
|
+
matching the documented usage pattern::
|
|
6
|
+
|
|
7
|
+
import causality
|
|
8
|
+
ce = causality.CausalityEngine(api_key="ce_live_sk_...")
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from causality_engine import * # noqa: F401, F403
|
|
12
|
+
from causality_engine import (
|
|
13
|
+
CausalityEngine,
|
|
14
|
+
CausalityEngineError,
|
|
15
|
+
AuthenticationError,
|
|
16
|
+
RateLimitError,
|
|
17
|
+
NotFoundError,
|
|
18
|
+
ValidationError,
|
|
19
|
+
APIResponse,
|
|
20
|
+
__version__,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"CausalityEngine",
|
|
25
|
+
"CausalityEngineError",
|
|
26
|
+
"AuthenticationError",
|
|
27
|
+
"RateLimitError",
|
|
28
|
+
"NotFoundError",
|
|
29
|
+
"ValidationError",
|
|
30
|
+
"APIResponse",
|
|
31
|
+
"__version__",
|
|
32
|
+
]
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Causality Engine Python SDK
|
|
3
|
+
============================
|
|
4
|
+
|
|
5
|
+
Official Python client for the Causality Engine API.
|
|
6
|
+
Causal attribution for e-commerce — replace last-click with math.
|
|
7
|
+
|
|
8
|
+
Quick start::
|
|
9
|
+
|
|
10
|
+
import causality_engine as ce
|
|
11
|
+
|
|
12
|
+
client = ce.CausalityEngine(api_key="ce_live_sk_...")
|
|
13
|
+
result = client.attribution.analyze(
|
|
14
|
+
data_source_id="ds_abc123",
|
|
15
|
+
date_range=["2026-01-01", "2026-01-31"],
|
|
16
|
+
)
|
|
17
|
+
print(result.data["channel_impact"])
|
|
18
|
+
|
|
19
|
+
Or use the top-level ``causality`` alias::
|
|
20
|
+
|
|
21
|
+
import causality
|
|
22
|
+
ce = causality.CausalityEngine(api_key="ce_live_sk_...")
|
|
23
|
+
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
__version__ = "1.0.0"
|
|
27
|
+
__all__ = [
|
|
28
|
+
"CausalityEngine",
|
|
29
|
+
"CausalityEngineError",
|
|
30
|
+
"AuthenticationError",
|
|
31
|
+
"RateLimitError",
|
|
32
|
+
"NotFoundError",
|
|
33
|
+
"ValidationError",
|
|
34
|
+
"APIResponse",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
from causality_engine.client import CausalityEngine
|
|
38
|
+
from causality_engine.exceptions import (
|
|
39
|
+
CausalityEngineError,
|
|
40
|
+
AuthenticationError,
|
|
41
|
+
RateLimitError,
|
|
42
|
+
NotFoundError,
|
|
43
|
+
ValidationError,
|
|
44
|
+
)
|
|
45
|
+
from causality_engine.models import APIResponse
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Low-level HTTP transport for the Causality Engine SDK.
|
|
3
|
+
|
|
4
|
+
Handles authentication, retries with exponential backoff, and
|
|
5
|
+
error-to-exception mapping. Uses httpx for both sync and async.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import time
|
|
11
|
+
import logging
|
|
12
|
+
from typing import Any, Dict, Optional, Union
|
|
13
|
+
|
|
14
|
+
import httpx
|
|
15
|
+
|
|
16
|
+
from causality_engine.exceptions import (
|
|
17
|
+
CausalityEngineError,
|
|
18
|
+
AuthenticationError,
|
|
19
|
+
RateLimitError,
|
|
20
|
+
NotFoundError,
|
|
21
|
+
ValidationError,
|
|
22
|
+
ServerError,
|
|
23
|
+
)
|
|
24
|
+
from causality_engine.models import APIResponse
|
|
25
|
+
|
|
26
|
+
logger = logging.getLogger("causality_engine")
|
|
27
|
+
|
|
28
|
+
DEFAULT_BASE_URL = "https://api.causalityengine.ai"
|
|
29
|
+
DEFAULT_TIMEOUT = 30.0
|
|
30
|
+
MAX_RETRIES = 3
|
|
31
|
+
RETRY_BACKOFF = 0.5 # seconds, doubles each retry
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _raise_for_status(response: httpx.Response) -> None:
|
|
35
|
+
"""Map HTTP error codes to typed SDK exceptions."""
|
|
36
|
+
status = response.status_code
|
|
37
|
+
request_id = response.headers.get("x-request-id")
|
|
38
|
+
|
|
39
|
+
if status < 400:
|
|
40
|
+
return
|
|
41
|
+
|
|
42
|
+
try:
|
|
43
|
+
body = response.json()
|
|
44
|
+
except Exception:
|
|
45
|
+
body = {"error": response.text}
|
|
46
|
+
|
|
47
|
+
message = body.get("error", body.get("message", f"HTTP {status}"))
|
|
48
|
+
if isinstance(message, dict):
|
|
49
|
+
message = message.get("message", str(message))
|
|
50
|
+
|
|
51
|
+
kwargs = dict(
|
|
52
|
+
message=str(message),
|
|
53
|
+
status_code=status,
|
|
54
|
+
body=body,
|
|
55
|
+
request_id=request_id,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
if status == 401:
|
|
59
|
+
raise AuthenticationError(**kwargs)
|
|
60
|
+
elif status == 404:
|
|
61
|
+
raise NotFoundError(**kwargs)
|
|
62
|
+
elif status == 422:
|
|
63
|
+
raise ValidationError(**kwargs)
|
|
64
|
+
elif status == 429:
|
|
65
|
+
retry_after = response.headers.get("retry-after")
|
|
66
|
+
raise RateLimitError(
|
|
67
|
+
retry_after=float(retry_after) if retry_after else None,
|
|
68
|
+
**kwargs,
|
|
69
|
+
)
|
|
70
|
+
elif status >= 500:
|
|
71
|
+
raise ServerError(**kwargs)
|
|
72
|
+
else:
|
|
73
|
+
raise CausalityEngineError(**kwargs)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class Transport:
|
|
77
|
+
"""Synchronous HTTP transport with retry logic."""
|
|
78
|
+
|
|
79
|
+
def __init__(
|
|
80
|
+
self,
|
|
81
|
+
api_key: str,
|
|
82
|
+
base_url: str = DEFAULT_BASE_URL,
|
|
83
|
+
timeout: float = DEFAULT_TIMEOUT,
|
|
84
|
+
max_retries: int = MAX_RETRIES,
|
|
85
|
+
) -> None:
|
|
86
|
+
self.api_key = api_key
|
|
87
|
+
self.base_url = base_url.rstrip("/")
|
|
88
|
+
self.max_retries = max_retries
|
|
89
|
+
self._client = httpx.Client(
|
|
90
|
+
base_url=self.base_url,
|
|
91
|
+
timeout=timeout,
|
|
92
|
+
headers={
|
|
93
|
+
"Authorization": f"Bearer {api_key}",
|
|
94
|
+
"Content-Type": "application/json",
|
|
95
|
+
"User-Agent": "causality-engine-python/1.0.0",
|
|
96
|
+
"Accept": "application/json",
|
|
97
|
+
},
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
def request(
|
|
101
|
+
self,
|
|
102
|
+
method: str,
|
|
103
|
+
path: str,
|
|
104
|
+
*,
|
|
105
|
+
json: Optional[Dict[str, Any]] = None,
|
|
106
|
+
params: Optional[Dict[str, Any]] = None,
|
|
107
|
+
) -> APIResponse:
|
|
108
|
+
"""Execute an HTTP request with automatic retries on 429/5xx."""
|
|
109
|
+
last_exc: Optional[Exception] = None
|
|
110
|
+
|
|
111
|
+
for attempt in range(self.max_retries + 1):
|
|
112
|
+
try:
|
|
113
|
+
response = self._client.request(
|
|
114
|
+
method,
|
|
115
|
+
path,
|
|
116
|
+
json=json,
|
|
117
|
+
params=_clean_params(params),
|
|
118
|
+
)
|
|
119
|
+
_raise_for_status(response)
|
|
120
|
+
|
|
121
|
+
data = response.json() if response.content else {}
|
|
122
|
+
return APIResponse(
|
|
123
|
+
data=data,
|
|
124
|
+
status_code=response.status_code,
|
|
125
|
+
request_id=response.headers.get("x-request-id"),
|
|
126
|
+
headers=dict(response.headers),
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
except (RateLimitError, ServerError) as exc:
|
|
130
|
+
last_exc = exc
|
|
131
|
+
if attempt < self.max_retries:
|
|
132
|
+
wait = getattr(exc, "retry_after", None)
|
|
133
|
+
if wait is None:
|
|
134
|
+
wait = RETRY_BACKOFF * (2 ** attempt)
|
|
135
|
+
logger.warning(
|
|
136
|
+
"Retrying %s %s (attempt %d/%d, wait %.1fs): %s",
|
|
137
|
+
method, path, attempt + 1, self.max_retries, wait, exc,
|
|
138
|
+
)
|
|
139
|
+
time.sleep(wait)
|
|
140
|
+
else:
|
|
141
|
+
raise
|
|
142
|
+
|
|
143
|
+
except httpx.TransportError as exc:
|
|
144
|
+
last_exc = exc
|
|
145
|
+
if attempt < self.max_retries:
|
|
146
|
+
wait = RETRY_BACKOFF * (2 ** attempt)
|
|
147
|
+
logger.warning(
|
|
148
|
+
"Transport error on %s %s (attempt %d/%d): %s",
|
|
149
|
+
method, path, attempt + 1, self.max_retries, exc,
|
|
150
|
+
)
|
|
151
|
+
time.sleep(wait)
|
|
152
|
+
else:
|
|
153
|
+
raise CausalityEngineError(
|
|
154
|
+
message=f"Transport error after {self.max_retries} retries: {exc}",
|
|
155
|
+
) from exc
|
|
156
|
+
|
|
157
|
+
# Should not reach here, but just in case
|
|
158
|
+
raise CausalityEngineError(
|
|
159
|
+
message=f"Request failed after {self.max_retries} retries",
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
def close(self) -> None:
|
|
163
|
+
"""Close the underlying HTTP client."""
|
|
164
|
+
self._client.close()
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _clean_params(params: Optional[Dict[str, Any]]) -> Optional[Dict[str, Any]]:
|
|
168
|
+
"""Remove None values from query parameters."""
|
|
169
|
+
if params is None:
|
|
170
|
+
return None
|
|
171
|
+
return {k: v for k, v in params.items() if v is not None}
|