cohorly 0.1.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.
cohorly-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,184 @@
1
+ Metadata-Version: 2.4
2
+ Name: cohorly
3
+ Version: 0.1.0
4
+ Summary: Official Cohorly server-side Python SDK (Mixpanel-style product analytics, self-hosted)
5
+ Author: Cohorly
6
+ License: Apache-2.0
7
+ Keywords: analytics,cohorly,mixpanel,tracking,events
8
+ Classifier: Development Status :: 4 - Beta
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: License :: OSI Approved :: Apache Software License
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3 :: Only
14
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
15
+ Classifier: Typing :: Typed
16
+ Requires-Python: >=3.8
17
+ Description-Content-Type: text/markdown
18
+ Provides-Extra: dev
19
+ Requires-Dist: pytest>=7; extra == "dev"
20
+
21
+ # Cohorly Python SDK
22
+
23
+ The official server-side Python SDK for [Cohorly](../../README.md), a
24
+ self-hosted Mixpanel-style product analytics platform. The API mirrors
25
+ `mixpanel-python`, so migrating existing code is mostly a matter of swapping
26
+ the import and pointing at your Cohorly server.
27
+
28
+ - Zero runtime dependencies (stdlib `urllib` only)
29
+ - Python 3.8+, fully typed (`py.typed`)
30
+ - Synchronous `Consumer` and batching `BufferedConsumer` with the Cohorly
31
+ retry contract (exponential backoff, `Retry-After`, bounded queue)
32
+
33
+ ## Installation
34
+
35
+ ```bash
36
+ pip install cohorly
37
+ ```
38
+
39
+ Or from this repo:
40
+
41
+ ```bash
42
+ pip install ./sdks/python
43
+ ```
44
+
45
+ ## Quickstart
46
+
47
+ ```python
48
+ from cohorly import Cohorly
49
+
50
+ ch = Cohorly("YOUR_PROJECT_TOKEN", api_host="http://localhost:4000")
51
+
52
+ # Track an event
53
+ ch.track("user-1", "Signed Up", {"plan": "pro", "source": "landing"})
54
+
55
+ # Link an alias to an existing distinct_id
56
+ ch.alias("user-1", "anon-7f3a")
57
+
58
+ # Update a user profile
59
+ ch.people_set("user-1", {"$first_name": "Ada", "plan": "pro"})
60
+ ```
61
+
62
+ The project token comes from your Cohorly dashboard (Settings -> Projects).
63
+ `api_host` is the base URL of your Cohorly server (default
64
+ `http://localhost:4000`).
65
+
66
+ ## Tracking events
67
+
68
+ `track(distinct_id, event_name, properties=None, meta=None)` stamps these
69
+ default properties before sending:
70
+
71
+ | Property | Value |
72
+ | -------------- | --------------------------------------- |
73
+ | `distinct_id` | the id you pass |
74
+ | `time` | current unix time in **milliseconds** |
75
+ | `$insert_id` | random uuid4 hex (server-side dedup) |
76
+ | `$lib` | `"python"` |
77
+ | `$lib_version` | SDK version |
78
+ | `token` | your project token (stripped by server) |
79
+
80
+ Your `properties` merge over the defaults, so you may supply a custom `time`
81
+ or `$insert_id` (e.g. for idempotent re-sends):
82
+
83
+ ```python
84
+ ch.track("user-1", "Order Completed", {
85
+ "amount": 42.5,
86
+ "$insert_id": f"order-{order.id}", # dedup key
87
+ })
88
+ ```
89
+
90
+ ### Historical imports
91
+
92
+ Use `import_data` to record events with an explicit timestamp (unix
93
+ milliseconds - Cohorly's convention throughout):
94
+
95
+ ```python
96
+ ch.import_data("user-1", "Legacy Signup", 1600000000000, {"source": "csv"})
97
+ ```
98
+
99
+ Unlike Mixpanel there is no separate import endpoint, API secret, or 5-day
100
+ cutoff - it is the same `/track` pipeline.
101
+
102
+ ## User profiles (people)
103
+
104
+ ```python
105
+ ch.people_set("user-1", {"plan": "pro"}) # set/overwrite
106
+ ch.people_set_once("user-1", {"created": "..."}) # only if unset
107
+ ch.people_increment("user-1", {"logins": 1}) # numeric add
108
+ ch.people_unset("user-1", ["plan"]) # remove properties
109
+ ch.people_delete("user-1") # delete the profile
110
+ ch.people_update({"distinct_id": "user-1", "$set": {"x": 1}}) # raw op
111
+ ```
112
+
113
+ These map to the Cohorly `/engage` operations `$set`, `$set_once`, `$add`,
114
+ `$unset`, `$delete`.
115
+
116
+ ## Consumers
117
+
118
+ By default every call sends immediately via a synchronous `Consumer`. For
119
+ higher throughput use `BufferedConsumer`, which batches messages (default 50
120
+ per request, server max 500) and implements the Cohorly retry contract:
121
+
122
+ ```python
123
+ from cohorly import Cohorly, BufferedConsumer
124
+
125
+ consumer = BufferedConsumer(max_size=50, api_host="http://localhost:4000")
126
+ ch = Cohorly("YOUR_PROJECT_TOKEN", consumer=consumer)
127
+
128
+ for user in users:
129
+ ch.track(user.id, "Backfill Event", {"batch": True})
130
+
131
+ consumer.flush() # IMPORTANT: drain remaining messages before exit
132
+ ```
133
+
134
+ Retry behavior (BufferedConsumer):
135
+
136
+ - **429 / 5xx / network error** - the queue is kept and retried with
137
+ exponential backoff: base 2s, doubling per consecutive failure, capped at
138
+ 10 minutes, +/-20% jitter. A `Retry-After` header is honored when present.
139
+ - **413** - the flush batch size is halved (floor 1) and retried.
140
+ - **400** - the rejected batch is dropped and `CohorlyException` is raised.
141
+ - **401** (invalid token) - the queue is kept; backoff at the maximum delay.
142
+ - The in-memory queue is capped at 1000 messages per endpoint; the oldest
143
+ message is dropped on overflow.
144
+
145
+ Cohorly batch rejections are atomic (nothing partially inserted), so retrying
146
+ the same payload is always safe.
147
+
148
+ The synchronous `Consumer(api_host, request_timeout=10, retry_limit=4)`
149
+ retries 429/5xx/network errors inline with the same backoff schedule up to
150
+ `retry_limit` times, then raises `CohorlyException`.
151
+
152
+ Because the token travels with each message, several `Cohorly` instances with
153
+ different project tokens can share one consumer.
154
+
155
+ ## Error handling
156
+
157
+ Delivery failures raise `cohorly.CohorlyException`:
158
+
159
+ ```python
160
+ from cohorly import Cohorly, CohorlyException
161
+
162
+ try:
163
+ ch.track("user-1", "event")
164
+ except CohorlyException as exc:
165
+ log.warning("cohorly delivery failed: %s", exc)
166
+ ```
167
+
168
+ ## Serialization
169
+
170
+ Messages are JSON. `datetime`/`date` values are serialized to ISO-8601 by the
171
+ default `DatetimeSerializer`; pass your own `json.JSONEncoder` subclass via
172
+ `Cohorly(..., serializer=MyEncoder)` for custom types.
173
+
174
+ ## Development
175
+
176
+ ```bash
177
+ cd sdks/python
178
+ python3 -m venv .venv
179
+ .venv/bin/pip install pytest
180
+ .venv/bin/pytest
181
+ ```
182
+
183
+ Tests run against the source tree (no install needed) and use a mocked
184
+ transport - no network required.
@@ -0,0 +1,164 @@
1
+ # Cohorly Python SDK
2
+
3
+ The official server-side Python SDK for [Cohorly](../../README.md), a
4
+ self-hosted Mixpanel-style product analytics platform. The API mirrors
5
+ `mixpanel-python`, so migrating existing code is mostly a matter of swapping
6
+ the import and pointing at your Cohorly server.
7
+
8
+ - Zero runtime dependencies (stdlib `urllib` only)
9
+ - Python 3.8+, fully typed (`py.typed`)
10
+ - Synchronous `Consumer` and batching `BufferedConsumer` with the Cohorly
11
+ retry contract (exponential backoff, `Retry-After`, bounded queue)
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ pip install cohorly
17
+ ```
18
+
19
+ Or from this repo:
20
+
21
+ ```bash
22
+ pip install ./sdks/python
23
+ ```
24
+
25
+ ## Quickstart
26
+
27
+ ```python
28
+ from cohorly import Cohorly
29
+
30
+ ch = Cohorly("YOUR_PROJECT_TOKEN", api_host="http://localhost:4000")
31
+
32
+ # Track an event
33
+ ch.track("user-1", "Signed Up", {"plan": "pro", "source": "landing"})
34
+
35
+ # Link an alias to an existing distinct_id
36
+ ch.alias("user-1", "anon-7f3a")
37
+
38
+ # Update a user profile
39
+ ch.people_set("user-1", {"$first_name": "Ada", "plan": "pro"})
40
+ ```
41
+
42
+ The project token comes from your Cohorly dashboard (Settings -> Projects).
43
+ `api_host` is the base URL of your Cohorly server (default
44
+ `http://localhost:4000`).
45
+
46
+ ## Tracking events
47
+
48
+ `track(distinct_id, event_name, properties=None, meta=None)` stamps these
49
+ default properties before sending:
50
+
51
+ | Property | Value |
52
+ | -------------- | --------------------------------------- |
53
+ | `distinct_id` | the id you pass |
54
+ | `time` | current unix time in **milliseconds** |
55
+ | `$insert_id` | random uuid4 hex (server-side dedup) |
56
+ | `$lib` | `"python"` |
57
+ | `$lib_version` | SDK version |
58
+ | `token` | your project token (stripped by server) |
59
+
60
+ Your `properties` merge over the defaults, so you may supply a custom `time`
61
+ or `$insert_id` (e.g. for idempotent re-sends):
62
+
63
+ ```python
64
+ ch.track("user-1", "Order Completed", {
65
+ "amount": 42.5,
66
+ "$insert_id": f"order-{order.id}", # dedup key
67
+ })
68
+ ```
69
+
70
+ ### Historical imports
71
+
72
+ Use `import_data` to record events with an explicit timestamp (unix
73
+ milliseconds - Cohorly's convention throughout):
74
+
75
+ ```python
76
+ ch.import_data("user-1", "Legacy Signup", 1600000000000, {"source": "csv"})
77
+ ```
78
+
79
+ Unlike Mixpanel there is no separate import endpoint, API secret, or 5-day
80
+ cutoff - it is the same `/track` pipeline.
81
+
82
+ ## User profiles (people)
83
+
84
+ ```python
85
+ ch.people_set("user-1", {"plan": "pro"}) # set/overwrite
86
+ ch.people_set_once("user-1", {"created": "..."}) # only if unset
87
+ ch.people_increment("user-1", {"logins": 1}) # numeric add
88
+ ch.people_unset("user-1", ["plan"]) # remove properties
89
+ ch.people_delete("user-1") # delete the profile
90
+ ch.people_update({"distinct_id": "user-1", "$set": {"x": 1}}) # raw op
91
+ ```
92
+
93
+ These map to the Cohorly `/engage` operations `$set`, `$set_once`, `$add`,
94
+ `$unset`, `$delete`.
95
+
96
+ ## Consumers
97
+
98
+ By default every call sends immediately via a synchronous `Consumer`. For
99
+ higher throughput use `BufferedConsumer`, which batches messages (default 50
100
+ per request, server max 500) and implements the Cohorly retry contract:
101
+
102
+ ```python
103
+ from cohorly import Cohorly, BufferedConsumer
104
+
105
+ consumer = BufferedConsumer(max_size=50, api_host="http://localhost:4000")
106
+ ch = Cohorly("YOUR_PROJECT_TOKEN", consumer=consumer)
107
+
108
+ for user in users:
109
+ ch.track(user.id, "Backfill Event", {"batch": True})
110
+
111
+ consumer.flush() # IMPORTANT: drain remaining messages before exit
112
+ ```
113
+
114
+ Retry behavior (BufferedConsumer):
115
+
116
+ - **429 / 5xx / network error** - the queue is kept and retried with
117
+ exponential backoff: base 2s, doubling per consecutive failure, capped at
118
+ 10 minutes, +/-20% jitter. A `Retry-After` header is honored when present.
119
+ - **413** - the flush batch size is halved (floor 1) and retried.
120
+ - **400** - the rejected batch is dropped and `CohorlyException` is raised.
121
+ - **401** (invalid token) - the queue is kept; backoff at the maximum delay.
122
+ - The in-memory queue is capped at 1000 messages per endpoint; the oldest
123
+ message is dropped on overflow.
124
+
125
+ Cohorly batch rejections are atomic (nothing partially inserted), so retrying
126
+ the same payload is always safe.
127
+
128
+ The synchronous `Consumer(api_host, request_timeout=10, retry_limit=4)`
129
+ retries 429/5xx/network errors inline with the same backoff schedule up to
130
+ `retry_limit` times, then raises `CohorlyException`.
131
+
132
+ Because the token travels with each message, several `Cohorly` instances with
133
+ different project tokens can share one consumer.
134
+
135
+ ## Error handling
136
+
137
+ Delivery failures raise `cohorly.CohorlyException`:
138
+
139
+ ```python
140
+ from cohorly import Cohorly, CohorlyException
141
+
142
+ try:
143
+ ch.track("user-1", "event")
144
+ except CohorlyException as exc:
145
+ log.warning("cohorly delivery failed: %s", exc)
146
+ ```
147
+
148
+ ## Serialization
149
+
150
+ Messages are JSON. `datetime`/`date` values are serialized to ISO-8601 by the
151
+ default `DatetimeSerializer`; pass your own `json.JSONEncoder` subclass via
152
+ `Cohorly(..., serializer=MyEncoder)` for custom types.
153
+
154
+ ## Development
155
+
156
+ ```bash
157
+ cd sdks/python
158
+ python3 -m venv .venv
159
+ .venv/bin/pip install pytest
160
+ .venv/bin/pytest
161
+ ```
162
+
163
+ Tests run against the source tree (no install needed) and use a mocked
164
+ transport - no network required.
@@ -0,0 +1,36 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "cohorly"
7
+ version = "0.1.0"
8
+ description = "Official Cohorly server-side Python SDK (Mixpanel-style product analytics, self-hosted)"
9
+ readme = "README.md"
10
+ requires-python = ">=3.8"
11
+ license = { text = "Apache-2.0" }
12
+ authors = [{ name = "Cohorly" }]
13
+ keywords = ["analytics", "cohorly", "mixpanel", "tracking", "events"]
14
+ classifiers = [
15
+ "Development Status :: 4 - Beta",
16
+ "Intended Audience :: Developers",
17
+ "License :: OSI Approved :: Apache Software License",
18
+ "Operating System :: OS Independent",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3 :: Only",
21
+ "Topic :: Software Development :: Libraries :: Python Modules",
22
+ "Typing :: Typed",
23
+ ]
24
+ dependencies = []
25
+
26
+ [project.optional-dependencies]
27
+ dev = ["pytest>=7"]
28
+
29
+ [tool.setuptools.packages.find]
30
+ where = ["src"]
31
+
32
+ [tool.setuptools.package-data]
33
+ cohorly = ["py.typed"]
34
+
35
+ [tool.pytest.ini_options]
36
+ testpaths = ["tests"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+