Skip to content

Commit 79f99a5

Browse files
author
clamp-bot
committed
sync from monorepo @ 524c500
0 parents  commit 79f99a5

7 files changed

Lines changed: 713 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# clamp-analytics (Python) changelog
2+
3+
## 0.2.0
4+
5+
- Added `capture_error(exception, context=None, anonymous_id=None, timestamp=None)` for sending exceptions as `$error` events. Extracts message, type, and stack from the exception via `traceback.format_exception`. Server adds a stable fingerprint at ingest so the same bug groups across occurrences.
6+
- Context properties (extra primitive key-value pairs) are passed through; the reserved key `handled` is ignored to keep `error.handled` honest.
7+
8+
## 0.1.0
9+
10+
Initial release.
11+
12+
- `init(project_id, api_key, endpoint=None)`: configure the SDK once at process start.
13+
- `track(name, properties=None, anonymous_id=None, timestamp=None)`: send a server event. Returns `True`; raises `ClampHTTPError` on non-2xx, `ClampNotInitializedError` when called before init.
14+
- `Money(amount, currency)`: typed monetary value for revenue, refunds, taxes.
15+
- Property values: `str`, `int`, `float`, `bool`, `Money`. Arrays and nested dicts (other than Money) rejected at call time.
16+
- Single dependency: `httpx`.
17+
- Tested on Python 3.9 through 3.13.

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2025 Clamp
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.

README.md

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
# clamp-analytics
2+
3+
Server-side analytics SDK for [Clamp Analytics](https://clamp.sh) in Python.
4+
5+
Send tracked events from any Python server runtime to Clamp. Works with Django, FastAPI, Flask, Celery workers, scheduled jobs, and anything else that runs Python and can make outbound HTTPS calls.
6+
7+
## Install
8+
9+
```bash
10+
pip install clamp-analytics
11+
```
12+
13+
Python 3.9+ supported.
14+
15+
## Quick start
16+
17+
```python
18+
from clamp_analytics import init, track, Money
19+
20+
init(project_id="proj_xxx", api_key="sk_proj_xxx")
21+
22+
# Simple event
23+
track("signup", properties={"plan": "pro", "method": "email"})
24+
25+
# Link a server event to a browser visitor (e.g. inside a Stripe webhook)
26+
track(
27+
"subscription_started",
28+
anonymous_id="aid_xxx",
29+
properties={"plan": "pro", "total": Money(29.00, "USD")},
30+
)
31+
```
32+
33+
Get a server API key at <https://clamp.sh/dashboard> (Settings → API Keys, format `sk_proj_...`). Set it as an environment variable; never commit it.
34+
35+
## API
36+
37+
### `init(project_id, api_key, endpoint=None)`
38+
39+
Initializes the SDK. Call once at process startup (e.g. in your Django `settings.py`, FastAPI lifespan, Flask app factory). Stores config in module-level state; subsequent `track()` calls use it.
40+
41+
`endpoint` is optional and overrides the default `https://api.clamp.sh`. Use this for self-hosted Clamp deployments or integration testing.
42+
43+
### `track(name, properties=None, anonymous_id=None, timestamp=None)`
44+
45+
Sends a server event.
46+
47+
- **`name`**: event name string. Examples: `"signup"`, `"subscription_started"`, `"feature_used"`.
48+
- **`properties`**: optional dict. Values may be `str`, `int`, `float`, `bool`, or `Money`. No nested dicts (other than `Money`) and no arrays.
49+
- **`anonymous_id`**: optional string. Links the server event to a browser visitor. Read the browser's anonymous ID via the JS SDK's `getAnonymousId()` and pass it through your auth flow (e.g. Stripe's `client_reference_id`).
50+
- **`timestamp`**: optional. Pass a `datetime` (timezone-aware preferred; naive datetimes are assumed UTC) or an ISO 8601 string. If omitted, the SDK uses the current UTC time.
51+
52+
Returns `True` on success. Raises `ClampHTTPError` on a non-2xx response or `ClampNotInitializedError` if `init()` wasn't called.
53+
54+
### `Money(amount, currency)`
55+
56+
A typed monetary value. Use it for revenue, refunds, taxes; anywhere a currency-denominated amount belongs.
57+
58+
```python
59+
track("purchase", properties={
60+
"plan": "pro",
61+
"total": Money(29.00, "USD"),
62+
"tax": Money(4.35, "USD"),
63+
})
64+
```
65+
66+
`amount` is in major units (29.00, not 2900). `currency` is an ISO 4217 code (uppercase, three letters).
67+
68+
### `capture_error(exception, context=None, anonymous_id=None, timestamp=None)`
69+
70+
Sends an exception as a `$error` event. Convenience over `track()` that extracts message, type, and stack from the exception. The server adds a stable fingerprint at ingest so the same bug groups across occurrences.
71+
72+
```python
73+
from clamp_analytics import capture_error
74+
75+
try:
76+
process_webhook(payload)
77+
except Exception as e:
78+
capture_error(e, context={"webhook": "stripe"})
79+
```
80+
81+
- **`exception`**: any exception instance. Stack trace is captured via `traceback.format_exception`.
82+
- **`context`**: optional flat mapping of additional properties. Values must be primitives (`str`, `int`, `float`, `bool`); the reserved key `handled` is ignored.
83+
- **`anonymous_id`**: optional. Links the error to a browser visitor via the same anonymous ID flow as `track`.
84+
- **`timestamp`**: optional `datetime` or ISO 8601 string.
85+
86+
Same return value and exceptions as `track()`. Lengths are capped (`error.message` 1KB, `error.type` 64 chars, `error.stack` 16KB) to match server-side limits.
87+
88+
## Framework integrations
89+
90+
Per-framework integration patterns (Django middleware, FastAPI dependency, Flask `after_request`, Celery task hook) are documented at <https://clamp.sh/docs/sdk/python>.
91+
92+
## Errors
93+
94+
The SDK is synchronous and raises on failure. There are no automatic retries. If you want fire-and-forget behavior, wrap the call yourself:
95+
96+
```python
97+
import logging
98+
99+
try:
100+
track("subscription_started", properties={...})
101+
except Exception:
102+
logging.exception("failed to send to Clamp")
103+
```
104+
105+
For high-throughput webhook handlers, consider sending events through a background task queue (Celery, RQ, Dramatiq).
106+
107+
## Links
108+
109+
- Dashboard: <https://clamp.sh/dashboard>
110+
- Docs: <https://clamp.sh/docs/sdk/python>
111+
- Source: <https://github.com/clamp-sh/analytics-python>
112+
- Issues: <https://github.com/clamp-sh/analytics-python/issues>

pyproject.toml

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
[build-system]
2+
requires = ["hatchling"]
3+
build-backend = "hatchling.build"
4+
5+
[project]
6+
name = "clamp-analytics"
7+
version = "0.2.0"
8+
description = "Server-side analytics SDK for Clamp. Send tracked events from Python apps (Django, FastAPI, Flask, etc.)."
9+
readme = "README.md"
10+
license = { text = "MIT" }
11+
requires-python = ">=3.9"
12+
authors = [
13+
{ name = "Clamp Analytics", email = "sidney@mail.clamp.sh" },
14+
]
15+
keywords = ["analytics", "tracking", "events", "clamp"]
16+
classifiers = [
17+
"Development Status :: 4 - Beta",
18+
"Intended Audience :: Developers",
19+
"License :: OSI Approved :: MIT License",
20+
"Programming Language :: Python :: 3",
21+
"Programming Language :: Python :: 3.9",
22+
"Programming Language :: Python :: 3.10",
23+
"Programming Language :: Python :: 3.11",
24+
"Programming Language :: Python :: 3.12",
25+
"Programming Language :: Python :: 3.13",
26+
"Topic :: Software Development :: Libraries :: Python Modules",
27+
"Topic :: Internet :: WWW/HTTP :: Site Management",
28+
]
29+
dependencies = [
30+
"httpx>=0.24",
31+
]
32+
33+
[project.urls]
34+
Homepage = "https://clamp.sh"
35+
Documentation = "https://clamp.sh/docs/sdk/python"
36+
Repository = "https://github.com/clamp-sh/analytics-python"
37+
Issues = "https://github.com/clamp-sh/analytics-python/issues"
38+
39+
[project.optional-dependencies]
40+
dev = [
41+
"pytest>=7",
42+
"pytest-httpx>=0.30",
43+
"mypy>=1.0",
44+
"ruff>=0.4",
45+
]
46+
47+
[tool.hatch.build.targets.wheel]
48+
packages = ["src/clamp_analytics"]
49+
50+
[tool.pytest.ini_options]
51+
testpaths = ["tests"]
52+
addopts = "-ra"
53+
54+
[tool.ruff]
55+
line-length = 100
56+
target-version = "py39"
57+
58+
[tool.ruff.lint]
59+
select = ["E", "F", "I", "B", "UP", "N", "W"]
60+
61+
[tool.mypy]
62+
strict = true
63+
python_version = "3.9"

src/clamp_analytics/__init__.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
"""Server-side analytics SDK for Clamp.
2+
3+
Send tracked events from any Python server runtime to Clamp. Idiomatic Python
4+
API; the wire shape stays consistent with every other Clamp SDK.
5+
6+
Quick start:
7+
8+
from clamp_analytics import init, track, Money
9+
10+
init(project_id="proj_abc", api_key="sk_proj_xxx")
11+
12+
track("signup", properties={"plan": "pro", "method": "email"})
13+
14+
track(
15+
"subscription_started",
16+
anonymous_id="aid_xxx",
17+
properties={"plan": "pro", "total": Money(29.0, "USD")},
18+
)
19+
"""
20+
21+
from __future__ import annotations
22+
23+
from clamp_analytics._client import (
24+
ClampError,
25+
ClampHTTPError,
26+
ClampNotInitializedError,
27+
Money,
28+
capture_error,
29+
init,
30+
track,
31+
)
32+
33+
__all__ = [
34+
"ClampError",
35+
"ClampHTTPError",
36+
"ClampNotInitializedError",
37+
"Money",
38+
"capture_error",
39+
"init",
40+
"track",
41+
]
42+
43+
__version__ = "0.2.0"

0 commit comments

Comments
 (0)