alitycs 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.
Files changed (33) hide show
  1. alitycs-1.0.0/.gitignore +9 -0
  2. alitycs-1.0.0/LICENSE +21 -0
  3. alitycs-1.0.0/PKG-INFO +155 -0
  4. alitycs-1.0.0/README.md +125 -0
  5. alitycs-1.0.0/pyproject.toml +63 -0
  6. alitycs-1.0.0/scripts/coderabbit-schema.v2.json +2273 -0
  7. alitycs-1.0.0/scripts/coderabbit-validator-requirements.txt +451 -0
  8. alitycs-1.0.0/scripts/coverage_gate.py +68 -0
  9. alitycs-1.0.0/scripts/e2e_run.py +99 -0
  10. alitycs-1.0.0/scripts/test-coderabbit-policy.rb +168 -0
  11. alitycs-1.0.0/scripts/validate-coderabbit.sh +44 -0
  12. alitycs-1.0.0/scripts/verify-workflow-pins.rb +528 -0
  13. alitycs-1.0.0/src/alitycs/__init__.py +63 -0
  14. alitycs-1.0.0/src/alitycs/batch.py +500 -0
  15. alitycs-1.0.0/src/alitycs/client.py +507 -0
  16. alitycs-1.0.0/src/alitycs/config.py +85 -0
  17. alitycs-1.0.0/src/alitycs/context.py +85 -0
  18. alitycs-1.0.0/src/alitycs/persistence.py +317 -0
  19. alitycs-1.0.0/src/alitycs/session.py +81 -0
  20. alitycs-1.0.0/src/alitycs/transport.py +308 -0
  21. alitycs-1.0.0/src/alitycs/types.py +298 -0
  22. alitycs-1.0.0/src/alitycs/utils.py +161 -0
  23. alitycs-1.0.0/tests/conftest.py +144 -0
  24. alitycs-1.0.0/tests/integration/test_client_e2e.py +225 -0
  25. alitycs-1.0.0/tests/unit/test_batch.py +675 -0
  26. alitycs-1.0.0/tests/unit/test_client.py +585 -0
  27. alitycs-1.0.0/tests/unit/test_config.py +105 -0
  28. alitycs-1.0.0/tests/unit/test_context.py +127 -0
  29. alitycs-1.0.0/tests/unit/test_e2e_script.py +51 -0
  30. alitycs-1.0.0/tests/unit/test_session.py +98 -0
  31. alitycs-1.0.0/tests/unit/test_transport.py +494 -0
  32. alitycs-1.0.0/tests/unit/test_types.py +267 -0
  33. alitycs-1.0.0/tests/unit/test_utils.py +147 -0
@@ -0,0 +1,9 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ .venv/
4
+ .coverage
5
+ coverage.json
6
+ htmlcov/
7
+ dist/
8
+ build/
9
+ *.egg-info/
alitycs-1.0.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Alitycs Team
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.
alitycs-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,155 @@
1
+ Metadata-Version: 2.4
2
+ Name: alitycs
3
+ Version: 1.0.0
4
+ Summary: Official Alitycs analytics SDK for Python servers
5
+ Project-URL: Homepage, https://alitycs.com
6
+ Project-URL: Repository, https://github.com/alitycs/alitycs-sdk-python
7
+ Project-URL: Issues, https://github.com/alitycs/alitycs-sdk-python/issues
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: alitycs,analytics,events,telemetry
11
+ Classifier: Development Status :: 5 - Production/Stable
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: System :: Logging
21
+ Requires-Python: >=3.9
22
+ Provides-Extra: dev
23
+ Requires-Dist: build==1.3.0; extra == 'dev'
24
+ Requires-Dist: editables==0.5; extra == 'dev'
25
+ Requires-Dist: hatchling==1.27.0; extra == 'dev'
26
+ Requires-Dist: pytest-cov==6.3.0; extra == 'dev'
27
+ Requires-Dist: pytest==8.4.2; extra == 'dev'
28
+ Requires-Dist: ruff==0.12.3; extra == 'dev'
29
+ Description-Content-Type: text/markdown
30
+
31
+ # alitycs-python
32
+
33
+ Official Alitycs analytics SDK for Python servers. Zero runtime dependencies —
34
+ HTTP uses `urllib.request` from the standard library.
35
+
36
+ ## Install
37
+
38
+ ```bash
39
+ pip install alitycs
40
+ ```
41
+
42
+ ## Quickstart
43
+
44
+ ```python
45
+ from alitycs import Alitycs
46
+
47
+ client = Alitycs(api_key="pk_...")
48
+
49
+ client.track("checkout_completed", {"plan": "pro", "mrr": 49})
50
+ client.identify("user-123", {"email": "user@example.com"})
51
+ client.page("settings")
52
+
53
+ # Deliver everything queued before your process exits:
54
+ client.shutdown()
55
+ ```
56
+
57
+ For a client shared by concurrent server requests, scope identity to each event
58
+ instead of changing ambient state with `identify()`:
59
+
60
+ ```python
61
+ client.track("checkout_started", user_id=request.user_id)
62
+ client.capture_error("checkout_failed", {"code": "E_CARD"}, user_id=request.user_id)
63
+ ```
64
+
65
+ The same `user_id` keyword is accepted by `track_revenue()` and `page()` and
66
+ does not change the identity used by any other call.
67
+
68
+ With batching enabled (the default), events are queued and dispatched on a daemon flusher thread,
69
+ so `track` does not block on network I/O. With `batching=False`, each `track` call sends inline and
70
+ can block up to the configured request/retry limits. Batches flush when `flush_size` (default 20)
71
+ events are queued, every `flush_interval` seconds (default 2.0), or when you call `flush()` /
72
+ `shutdown()` explicitly. `shutdown()` waits up to 30 seconds by default; pass
73
+ `join_timeout=None` only when an unbounded drain is appropriate. On process exit a safety net
74
+ drains live instances; SIGTERM and SIGINT also trigger a best-effort drain before the default
75
+ termination disposition is restored (registered from the main thread only).
76
+
77
+ ## Configuration
78
+
79
+ ```python
80
+ Alitycs(
81
+ api_key="pk_...", # required
82
+ endpoint="https://api.alitycs.com/events",
83
+ flush_size=20, # events per batch
84
+ flush_interval=2.0, # seconds; None disables the timer
85
+ debug=False,
86
+ max_queue_size=1000,
87
+ max_retries=3, # exponential backoff from retry_backoff_base
88
+ session_timeout=1800.0,
89
+ batching=True, # False sends each event inline
90
+ request_timeout=10.0,
91
+ retry_backoff_base=1.0,
92
+ persistence_path=None, # optional exact in-flight batch WAL file
93
+ )
94
+ ```
95
+
96
+ ## Delivery guarantees
97
+
98
+ - **Honest results**: `flush()` returns `True` only when every event was delivered.
99
+ Transient failures re-queue survivors at the head without persistence; with
100
+ `persistence_path`, the exact serialized in-flight batch remains on disk for restart.
101
+ Permanent refusals are dropped loudly.
102
+ - **No silent loss**: delivery failures and local rejections are logged at warn level
103
+ (never hidden behind `debug`) and counted — see `pending`, `rejected_locally`,
104
+ plus `delivered_total` / `requeued_total` / `lost_total` on the batch manager.
105
+ - **Split-on-rejection**: an HTTP 400 can mean one event poisoned a whole batch, so the
106
+ SDK splits that response in half to isolate valid events, with a hard cap of 64 sends.
107
+ Authentication, authorization, redirect, and other permanent responses are never split.
108
+ - Retries reuse the exact batch body so `batchId` stays stable for server-side dedup.
109
+ - SDK-generated exponential backoff is capped at 10 seconds. A server `Retry-After`
110
+ replaces that generated delay and is capped at one hour to keep delivery bounded.
111
+ - A new process using the same `persistence_path` replays retained bodies on
112
+ `flush()` (or an unbounded shutdown) and honors any remaining persisted `Retry-After` deadline.
113
+ If a finite shutdown deadline expires first, queued events are appended to the WAL in FIFO order.
114
+ The WAL starts immediately before the first network attempt and is capped at `max_queue_size`
115
+ retained events. Each path is exclusively owned by one live client; a same-process registry and
116
+ a POSIX advisory lock reject overlapping owners. After a fork, the child drops its
117
+ copy of the parent-owned queue and detaches from the inherited WAL; create a fresh client with a
118
+ child-specific path when child delivery also needs durability.
119
+
120
+ ## Ingestion limits
121
+
122
+ Events violating these limits are rejected locally at build time — never queued,
123
+ never sent, never truncated (they would cause the server to reject the whole batch):
124
+
125
+ | Limit | Value |
126
+ | --- | --- |
127
+ | Properties per event | ≤ 50 |
128
+ | Property key length | ≤ 100 chars |
129
+ | Property value length | ≤ 1000 chars |
130
+ | Estimated event size | ≤ 64 KB |
131
+ | Required fields | non-blank name AND (`userId` or `anonymousId`) |
132
+ | Timestamp | epoch **milliseconds**, ≤ 7 days past, none future |
133
+
134
+ Revenue payloads (trusted ingestion, requires a key with `revenue:write`) validate
135
+ their per-kind fields strictly:
136
+
137
+ ```python
138
+ from alitycs import RevenuePayload
139
+
140
+ RevenuePayload.transaction(fact_id="inv-1", amount="19.99", currency="USD")
141
+ RevenuePayload.mrr_snapshot(
142
+ fact_id="snap-1", subscription_id="sub-1",
143
+ customer_id="cus-1", mrr_amount="250.00", currency="USD",
144
+ )
145
+ RevenuePayload.mrr_baseline_complete(fact_id="base-1", currency="USD", expected_active_subscriptions=120)
146
+
147
+ client.track_revenue(RevenuePayload.transaction(fact_id="inv-2", amount="9.99", currency="EUR"))
148
+ ```
149
+
150
+ ## Development
151
+
152
+ ```bash
153
+ pytest tests/unit # unit tests, no network
154
+ scripts/e2e_run.py # end-to-end against a local stack (Docker)
155
+ ```
@@ -0,0 +1,125 @@
1
+ # alitycs-python
2
+
3
+ Official Alitycs analytics SDK for Python servers. Zero runtime dependencies —
4
+ HTTP uses `urllib.request` from the standard library.
5
+
6
+ ## Install
7
+
8
+ ```bash
9
+ pip install alitycs
10
+ ```
11
+
12
+ ## Quickstart
13
+
14
+ ```python
15
+ from alitycs import Alitycs
16
+
17
+ client = Alitycs(api_key="pk_...")
18
+
19
+ client.track("checkout_completed", {"plan": "pro", "mrr": 49})
20
+ client.identify("user-123", {"email": "user@example.com"})
21
+ client.page("settings")
22
+
23
+ # Deliver everything queued before your process exits:
24
+ client.shutdown()
25
+ ```
26
+
27
+ For a client shared by concurrent server requests, scope identity to each event
28
+ instead of changing ambient state with `identify()`:
29
+
30
+ ```python
31
+ client.track("checkout_started", user_id=request.user_id)
32
+ client.capture_error("checkout_failed", {"code": "E_CARD"}, user_id=request.user_id)
33
+ ```
34
+
35
+ The same `user_id` keyword is accepted by `track_revenue()` and `page()` and
36
+ does not change the identity used by any other call.
37
+
38
+ With batching enabled (the default), events are queued and dispatched on a daemon flusher thread,
39
+ so `track` does not block on network I/O. With `batching=False`, each `track` call sends inline and
40
+ can block up to the configured request/retry limits. Batches flush when `flush_size` (default 20)
41
+ events are queued, every `flush_interval` seconds (default 2.0), or when you call `flush()` /
42
+ `shutdown()` explicitly. `shutdown()` waits up to 30 seconds by default; pass
43
+ `join_timeout=None` only when an unbounded drain is appropriate. On process exit a safety net
44
+ drains live instances; SIGTERM and SIGINT also trigger a best-effort drain before the default
45
+ termination disposition is restored (registered from the main thread only).
46
+
47
+ ## Configuration
48
+
49
+ ```python
50
+ Alitycs(
51
+ api_key="pk_...", # required
52
+ endpoint="https://api.alitycs.com/events",
53
+ flush_size=20, # events per batch
54
+ flush_interval=2.0, # seconds; None disables the timer
55
+ debug=False,
56
+ max_queue_size=1000,
57
+ max_retries=3, # exponential backoff from retry_backoff_base
58
+ session_timeout=1800.0,
59
+ batching=True, # False sends each event inline
60
+ request_timeout=10.0,
61
+ retry_backoff_base=1.0,
62
+ persistence_path=None, # optional exact in-flight batch WAL file
63
+ )
64
+ ```
65
+
66
+ ## Delivery guarantees
67
+
68
+ - **Honest results**: `flush()` returns `True` only when every event was delivered.
69
+ Transient failures re-queue survivors at the head without persistence; with
70
+ `persistence_path`, the exact serialized in-flight batch remains on disk for restart.
71
+ Permanent refusals are dropped loudly.
72
+ - **No silent loss**: delivery failures and local rejections are logged at warn level
73
+ (never hidden behind `debug`) and counted — see `pending`, `rejected_locally`,
74
+ plus `delivered_total` / `requeued_total` / `lost_total` on the batch manager.
75
+ - **Split-on-rejection**: an HTTP 400 can mean one event poisoned a whole batch, so the
76
+ SDK splits that response in half to isolate valid events, with a hard cap of 64 sends.
77
+ Authentication, authorization, redirect, and other permanent responses are never split.
78
+ - Retries reuse the exact batch body so `batchId` stays stable for server-side dedup.
79
+ - SDK-generated exponential backoff is capped at 10 seconds. A server `Retry-After`
80
+ replaces that generated delay and is capped at one hour to keep delivery bounded.
81
+ - A new process using the same `persistence_path` replays retained bodies on
82
+ `flush()` (or an unbounded shutdown) and honors any remaining persisted `Retry-After` deadline.
83
+ If a finite shutdown deadline expires first, queued events are appended to the WAL in FIFO order.
84
+ The WAL starts immediately before the first network attempt and is capped at `max_queue_size`
85
+ retained events. Each path is exclusively owned by one live client; a same-process registry and
86
+ a POSIX advisory lock reject overlapping owners. After a fork, the child drops its
87
+ copy of the parent-owned queue and detaches from the inherited WAL; create a fresh client with a
88
+ child-specific path when child delivery also needs durability.
89
+
90
+ ## Ingestion limits
91
+
92
+ Events violating these limits are rejected locally at build time — never queued,
93
+ never sent, never truncated (they would cause the server to reject the whole batch):
94
+
95
+ | Limit | Value |
96
+ | --- | --- |
97
+ | Properties per event | ≤ 50 |
98
+ | Property key length | ≤ 100 chars |
99
+ | Property value length | ≤ 1000 chars |
100
+ | Estimated event size | ≤ 64 KB |
101
+ | Required fields | non-blank name AND (`userId` or `anonymousId`) |
102
+ | Timestamp | epoch **milliseconds**, ≤ 7 days past, none future |
103
+
104
+ Revenue payloads (trusted ingestion, requires a key with `revenue:write`) validate
105
+ their per-kind fields strictly:
106
+
107
+ ```python
108
+ from alitycs import RevenuePayload
109
+
110
+ RevenuePayload.transaction(fact_id="inv-1", amount="19.99", currency="USD")
111
+ RevenuePayload.mrr_snapshot(
112
+ fact_id="snap-1", subscription_id="sub-1",
113
+ customer_id="cus-1", mrr_amount="250.00", currency="USD",
114
+ )
115
+ RevenuePayload.mrr_baseline_complete(fact_id="base-1", currency="USD", expected_active_subscriptions=120)
116
+
117
+ client.track_revenue(RevenuePayload.transaction(fact_id="inv-2", amount="9.99", currency="EUR"))
118
+ ```
119
+
120
+ ## Development
121
+
122
+ ```bash
123
+ pytest tests/unit # unit tests, no network
124
+ scripts/e2e_run.py # end-to-end against a local stack (Docker)
125
+ ```
@@ -0,0 +1,63 @@
1
+ [build-system]
2
+ requires = ["hatchling==1.27.0"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "alitycs"
7
+ version = "1.0.0"
8
+ description = "Official Alitycs analytics SDK for Python servers"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.9"
12
+ keywords = ["analytics", "alitycs", "events", "telemetry"]
13
+ classifiers = [
14
+ "Development Status :: 5 - Production/Stable",
15
+ "Intended Audience :: Developers",
16
+ "Operating System :: OS Independent",
17
+ "Programming Language :: Python :: 3",
18
+ "Programming Language :: Python :: 3.9",
19
+ "Programming Language :: Python :: 3.10",
20
+ "Programming Language :: Python :: 3.11",
21
+ "Programming Language :: Python :: 3.12",
22
+ "Programming Language :: Python :: 3.13",
23
+ "Topic :: System :: Logging",
24
+ ]
25
+ # Zero runtime dependencies by design: HTTP uses urllib.request from the stdlib,
26
+ # mirroring how alitycs-sdk-jvm uses java.net.http.
27
+ dependencies = []
28
+
29
+ [project.urls]
30
+ Homepage = "https://alitycs.com"
31
+ Repository = "https://github.com/alitycs/alitycs-sdk-python"
32
+ Issues = "https://github.com/alitycs/alitycs-sdk-python/issues"
33
+
34
+ [project.optional-dependencies]
35
+ dev = [
36
+ "build==1.3.0",
37
+ "editables==0.5",
38
+ "hatchling==1.27.0",
39
+ "pytest==8.4.2",
40
+ "pytest-cov==6.3.0",
41
+ "ruff==0.12.3",
42
+ ]
43
+
44
+ [tool.hatch.build.targets.wheel]
45
+ packages = ["src/alitycs"]
46
+
47
+ [tool.hatch.build.targets.sdist]
48
+ include = ["src", "tests", "scripts", "README.md", "pyproject.toml"]
49
+
50
+ [tool.pytest.ini_options]
51
+ testpaths = ["tests"]
52
+ pythonpath = ["src"]
53
+
54
+ [tool.coverage.run]
55
+ branch = true
56
+ source = ["alitycs"]
57
+
58
+ [tool.coverage.report]
59
+ # coverage.py has no function-coverage metric, so the contract's "functions >= 85%"
60
+ # gate is covered by branch coverage instead (see .agents/plans/sdk-python.md).
61
+ # scripts/coverage_gate.py additionally enforces the branch gate from coverage.json.
62
+ fail_under = 90
63
+ show_missing = true