prodantix-sdk 0.0.1__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.
@@ -0,0 +1,172 @@
1
+ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2
+
3
+ # git filter-branch / rewrite temp
4
+ .git-rewrite/
5
+
6
+ #environment files
7
+ /env
8
+ .env
9
+ .env.local
10
+ cypress.env.json
11
+ settings.local.json
12
+ .claude/settings.local.json
13
+
14
+ # Cross-tool agent-CLI mirrors — redundant with .claude/; do not commit
15
+ .agents/
16
+ .codex/
17
+ # Flutter web env files are generated at build time from environment variables
18
+ # (scripts/vercel/gen-env.sh) — never commit them. Use .env.example as the
19
+ # committed source of keys + dev defaults.
20
+ .env.development
21
+ .env.test
22
+ .env.production
23
+
24
+ **/snippets/
25
+
26
+ # dependencies
27
+ node_modules
28
+ .pnp
29
+ .pnp.js
30
+
31
+ # testing
32
+ coverage
33
+ screenshots
34
+
35
+ # Re-include intentional marketing screenshots that live under web apps' public dirs
36
+ # Pattern matches both pre-restructure (products/<app>/public/...) and post-restructure
37
+ # (products/<product>/<app>/public/...) layouts.
38
+ !products/*/public/images/screenshots/
39
+ !products/*/public/images/screenshots/**
40
+ !products/*/*/public/images/screenshots/
41
+ !products/*/*/public/images/screenshots/**
42
+
43
+ # Re-include fastlane screenshot driver scripts (Dart source, not generated image files).
44
+ !**/fastlane/screenshots/
45
+ !**/fastlane/screenshots/*.dart
46
+ !**/fastlane/screenshots/*.txt
47
+
48
+ # Fastlane supply / deliver – generated screenshots (regenerate with screengrab / your pipeline)
49
+ **/fastlane/metadata/**/images/
50
+
51
+ # next.js
52
+ .next/
53
+ .next.stale*/
54
+ out/
55
+ build
56
+ dist
57
+
58
+ # dart
59
+ .dart_tool/
60
+ .flutter-plugins
61
+ .flutter-plugins-dependencies
62
+ .packages
63
+ .pub-cache/
64
+ .pub/
65
+ /build/
66
+ **/android/local.properties
67
+
68
+ # python — 48 of these were committed under the prodantix Python SDK and every
69
+ # test run re-dirtied them. .venv and .pytest_cache need no rule: both write a
70
+ # self-ignoring .gitignore into their own directory.
71
+ __pycache__/
72
+ *.pyc
73
+
74
+ # misc
75
+ .DS_Store
76
+ *.pem
77
+
78
+ # JetBrains IDE
79
+ .idea/
80
+
81
+ # superpowers session-scoped working state
82
+ .superpowers/
83
+
84
+ # debug
85
+ npm-debug.log*
86
+ yarn-debug.log*
87
+ yarn-error.log*
88
+
89
+ # local env files
90
+ .env.local
91
+ .env.development.local
92
+ .env.test.local
93
+ .env.production.local
94
+
95
+ #storybook
96
+ storybook-static
97
+
98
+ # Flutter
99
+ *.ipa
100
+ *.dSYM.zip
101
+
102
+ # typescript
103
+ *.tsbuildinfo
104
+
105
+ # turbo
106
+ .turbo
107
+
108
+ # Melos
109
+ .melos_tool
110
+ .melos_cache
111
+
112
+ # AuthKey
113
+ AuthKey_*.p8
114
+
115
+ .vercel
116
+ .gstack/
117
+
118
+ # GraphQL auto-generated schemas
119
+ products/clepit-server/products/*/src/schema.gql
120
+
121
+ # Railway CLI link state (project IDs are kept in project.json instead)
122
+ infra/railway/*/.railway/
123
+
124
+ # SOPS — never commit unencrypted age private keys
125
+ *.age.key
126
+ *.age
127
+ !*.age.pub
128
+
129
+ # Tofu — workspaces use GCS state (gs://bomdisoft-tofu-state/<workspace>/).
130
+ # Only tofu-state-bootstrap keeps state local (chicken-and-egg — it creates
131
+ # the bucket). All other workspaces have backend "gcs" in versions.tf so their
132
+ # tfstate never lands on disk.
133
+ #
134
+ # .terraform.lock.hcl is deliberately NOT ignored. CI runs a bare `tofu init`,
135
+ # so without a committed lockfile every apply re-resolves the version
136
+ # constraints against the registry and can pick up a new provider release
137
+ # mid-flight, unreviewed, against live infrastructure. The lockfiles carry
138
+ # hashes for every published platform (CI is linux_amd64, laptops are darwin),
139
+ # so regenerate with `tofu providers lock -platform=...` for ALL platforms.
140
+ # A bare `tofu init -upgrade` records only the current one and breaks CI.
141
+ infra/iac/projects/tofu-state-bootstrap/terraform.tfstate
142
+ infra/iac/projects/tofu-state-bootstrap/terraform.tfstate.backup
143
+ infra/iac/projects/*/.terraform/
144
+ infra/iac/projects/*/*.tfplan
145
+ infra/iac/projects/*/terraform.tfvars
146
+
147
+ # Local dev stacks (infra/iac/local/*) deliberately keep LOCAL state — they
148
+ # manage per-machine throwaway containers, so state never leaves the laptop.
149
+ infra/iac/local/*/.terraform/
150
+ infra/iac/local/*/terraform.tfstate
151
+ infra/iac/local/*/terraform.tfstate.backup
152
+ infra/iac/local/*/*.tfplan
153
+ products/prodantix/.env.local
154
+
155
+ CLAUDE.local.md
156
+
157
+ /target
158
+
159
+ # Tor hidden-service key material. Tor GENERATES these into HiddenServiceDir on
160
+ # first start; they are per-deployment secrets and must never be committed —
161
+ # anyone holding hs_ed25519_secret_key can impersonate the .onion address.
162
+ # (Three were committed in f392aa205 on 2026-08-03 and have been rotated.)
163
+ **/tor/hidden_service/
164
+ **/dark-web/tor/
165
+
166
+ # Shell artifact: an unexpanded $LOG once captured a turbo lint run and was
167
+ # committed (74a65bc6c, removed same week). Literal $ names are never source.
168
+ /$LOG
169
+ .claude/context/
170
+
171
+ # Lane addresses are machine state, not repository state (scripts/lane.ts).
172
+ .claude/lane-registry.json
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Bomdisoft
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,106 @@
1
+ Metadata-Version: 2.4
2
+ Name: prodantix-sdk
3
+ Version: 0.0.1
4
+ Summary: Prodantix server SDK: send events, evaluate feature flags, read the in-app inbox from a Python backend.
5
+ Project-URL: Homepage, https://prodantix.com
6
+ Author: Bomdisoft
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Keywords: analytics,events,feature-flags,prodantix
10
+ Requires-Python: >=3.9
11
+ Description-Content-Type: text/markdown
12
+
13
+ # Prodantix Python SDK
14
+
15
+ The server-side SDK for [Prodantix](https://prodantix.com): send analytics events,
16
+ evaluate feature flags (remote or locally), and read the in-app inbox from a Python
17
+ backend. Zero runtime dependencies: pure standard library.
18
+
19
+ It speaks the same wire contract as the JS/TS SDK and the server, and its feature-flag
20
+ bucketing is **byte-exact** across all three (proven by the shared `flag-golden.json`
21
+ vectors), so a user lands in the same rollout everywhere.
22
+
23
+ ## Docs
24
+
25
+ - [Quickstart](https://prodantix.com/en/docs/quickstart): install and send a first event
26
+ - [Get an API key](https://prodantix.com/en/docs/keys): signup, project creation, where the key lives
27
+ - [API reference](https://prodantix.com/en/docs/api): every REST operation, rendered
28
+
29
+ ## Install
30
+
31
+ ```
32
+ pip install prodantix-sdk
33
+ ```
34
+
35
+ Requires Python 3.9+.
36
+
37
+ ## Quickstart
38
+
39
+ ```python
40
+ import os
41
+ from prodantix import Client
42
+
43
+ client = Client(
44
+ api_key=os.environ["PRODANTIX_API_KEY"], # a public pdx_pub_ project key
45
+ host="https://eu.api.prodantix.com",
46
+ )
47
+
48
+ # Send events (distinct_id is explicit on every call, since a server serves many users).
49
+ client.capture("user-123", "order.completed", properties={"total": 42, "currency": "USD"})
50
+ client.identify("user-123", set_props={"plan": "pro"})
51
+ client.group("user-123", "company", "acme", properties={"seats": 10})
52
+
53
+ # Feature flags: remote (authoritative) …
54
+ if client.is_feature_enabled("user-123", "new-checkout"):
55
+ ...
56
+
57
+ # … or local (evaluated in-process from a cached snapshot, no per-call round-trip).
58
+ if client.is_feature_enabled_local("user-123", "new-checkout", properties={"plan": "pro"}):
59
+ ...
60
+
61
+ # In-app inbox
62
+ for message in client.get_inbox("user-123"):
63
+ ...
64
+ client.mark_message_read("user-123", "message-id")
65
+
66
+ # Flush any buffered events and stop the background flusher.
67
+ client.shutdown()
68
+ ```
69
+
70
+ `Client` is also a context manager, which shuts down (and flushes) on exit:
71
+
72
+ ```python
73
+ with Client(api_key=..., host=...) as client:
74
+ client.capture("user-123", "page.viewed")
75
+ ```
76
+
77
+ ## How events are delivered
78
+
79
+ Events are buffered in a thread-safe queue and delivered in batches by a background
80
+ flusher. A batch is sent when the queue reaches `flush_at` events (default 20) or every
81
+ `flush_interval_s` seconds (default 10), whichever comes first. Delivery retries transient
82
+ failures with exponential backoff and fails fast on a permanent 4xx. Always call
83
+ `shutdown()` (or use the context manager) before your process exits so the final batch
84
+ is flushed.
85
+
86
+ ## Configuration
87
+
88
+ | Argument | Default | Purpose |
89
+ | --- | --- | --- |
90
+ | `api_key` | required | Public project key (`pdx_pub_…`). Required. |
91
+ | `host` | required | Ingest base URL. Required. |
92
+ | `flags_host` | `host` | Base URL for the flags + inbox endpoints. |
93
+ | `flush_at` | `20` | Queue size that triggers an immediate flush. |
94
+ | `flush_interval_s` | `10.0` | Background flush cadence (`<= 0` disables the timer). |
95
+ | `max_queue_size` | `1000` | Cap; oldest events are dropped on overflow. |
96
+ | `max_retries` | `3` | Delivery retry attempts. |
97
+ | `request_timeout_s` | `10.0` | Per-request timeout. |
98
+ | `default_properties` | `None` | A dict or a callable merged into every event. |
99
+ | `os`, `locale` | `None` | Added to each event's `context`. |
100
+ | `on_error` | no-op | Called with any background/capture error. |
101
+
102
+ ## Development
103
+
104
+ ```
105
+ python -m unittest discover -s tests
106
+ ```
@@ -0,0 +1,94 @@
1
+ # Prodantix Python SDK
2
+
3
+ The server-side SDK for [Prodantix](https://prodantix.com): send analytics events,
4
+ evaluate feature flags (remote or locally), and read the in-app inbox from a Python
5
+ backend. Zero runtime dependencies: pure standard library.
6
+
7
+ It speaks the same wire contract as the JS/TS SDK and the server, and its feature-flag
8
+ bucketing is **byte-exact** across all three (proven by the shared `flag-golden.json`
9
+ vectors), so a user lands in the same rollout everywhere.
10
+
11
+ ## Docs
12
+
13
+ - [Quickstart](https://prodantix.com/en/docs/quickstart): install and send a first event
14
+ - [Get an API key](https://prodantix.com/en/docs/keys): signup, project creation, where the key lives
15
+ - [API reference](https://prodantix.com/en/docs/api): every REST operation, rendered
16
+
17
+ ## Install
18
+
19
+ ```
20
+ pip install prodantix-sdk
21
+ ```
22
+
23
+ Requires Python 3.9+.
24
+
25
+ ## Quickstart
26
+
27
+ ```python
28
+ import os
29
+ from prodantix import Client
30
+
31
+ client = Client(
32
+ api_key=os.environ["PRODANTIX_API_KEY"], # a public pdx_pub_ project key
33
+ host="https://eu.api.prodantix.com",
34
+ )
35
+
36
+ # Send events (distinct_id is explicit on every call, since a server serves many users).
37
+ client.capture("user-123", "order.completed", properties={"total": 42, "currency": "USD"})
38
+ client.identify("user-123", set_props={"plan": "pro"})
39
+ client.group("user-123", "company", "acme", properties={"seats": 10})
40
+
41
+ # Feature flags: remote (authoritative) …
42
+ if client.is_feature_enabled("user-123", "new-checkout"):
43
+ ...
44
+
45
+ # … or local (evaluated in-process from a cached snapshot, no per-call round-trip).
46
+ if client.is_feature_enabled_local("user-123", "new-checkout", properties={"plan": "pro"}):
47
+ ...
48
+
49
+ # In-app inbox
50
+ for message in client.get_inbox("user-123"):
51
+ ...
52
+ client.mark_message_read("user-123", "message-id")
53
+
54
+ # Flush any buffered events and stop the background flusher.
55
+ client.shutdown()
56
+ ```
57
+
58
+ `Client` is also a context manager, which shuts down (and flushes) on exit:
59
+
60
+ ```python
61
+ with Client(api_key=..., host=...) as client:
62
+ client.capture("user-123", "page.viewed")
63
+ ```
64
+
65
+ ## How events are delivered
66
+
67
+ Events are buffered in a thread-safe queue and delivered in batches by a background
68
+ flusher. A batch is sent when the queue reaches `flush_at` events (default 20) or every
69
+ `flush_interval_s` seconds (default 10), whichever comes first. Delivery retries transient
70
+ failures with exponential backoff and fails fast on a permanent 4xx. Always call
71
+ `shutdown()` (or use the context manager) before your process exits so the final batch
72
+ is flushed.
73
+
74
+ ## Configuration
75
+
76
+ | Argument | Default | Purpose |
77
+ | --- | --- | --- |
78
+ | `api_key` | required | Public project key (`pdx_pub_…`). Required. |
79
+ | `host` | required | Ingest base URL. Required. |
80
+ | `flags_host` | `host` | Base URL for the flags + inbox endpoints. |
81
+ | `flush_at` | `20` | Queue size that triggers an immediate flush. |
82
+ | `flush_interval_s` | `10.0` | Background flush cadence (`<= 0` disables the timer). |
83
+ | `max_queue_size` | `1000` | Cap; oldest events are dropped on overflow. |
84
+ | `max_retries` | `3` | Delivery retry attempts. |
85
+ | `request_timeout_s` | `10.0` | Per-request timeout. |
86
+ | `default_properties` | `None` | A dict or a callable merged into every event. |
87
+ | `os`, `locale` | `None` | Added to each event's `context`. |
88
+ | `on_error` | no-op | Called with any background/capture error. |
89
+
90
+ ## Development
91
+
92
+ ```
93
+ python -m unittest discover -s tests
94
+ ```
@@ -0,0 +1,46 @@
1
+ """Prodantix server SDK.
2
+
3
+ import os
4
+ from prodantix import Client
5
+
6
+ client = Client(api_key=os.environ["PRODANTIX_API_KEY"], host="https://eu.api.prodantix.com")
7
+ client.capture("user-123", "order.completed", properties={"total": 42})
8
+ if client.is_feature_enabled("user-123", "new-checkout"):
9
+ ...
10
+ client.shutdown()
11
+ """
12
+
13
+ from prodantix.client import Client, PersonTraits
14
+ from prodantix.errors import (
15
+ ConfigError,
16
+ ProdantixError,
17
+ QueueOverflowError,
18
+ TransportError,
19
+ ValidationError,
20
+ )
21
+ from prodantix.flag_eval import FlagDecision, assign_variant, bucket, evaluate_all, evaluate_flag, get_variant
22
+ from prodantix.predicate import FlagEvalContext, matches
23
+ from prodantix.transport import Transport, UrllibTransport
24
+ from prodantix.version import SDK_NAME, SDK_VERSION
25
+
26
+ __all__ = [
27
+ "Client",
28
+ "PersonTraits",
29
+ "ProdantixError",
30
+ "ConfigError",
31
+ "ValidationError",
32
+ "TransportError",
33
+ "QueueOverflowError",
34
+ "FlagDecision",
35
+ "FlagEvalContext",
36
+ "bucket",
37
+ "evaluate_flag",
38
+ "evaluate_all",
39
+ "assign_variant",
40
+ "get_variant",
41
+ "matches",
42
+ "Transport",
43
+ "UrllibTransport",
44
+ "SDK_NAME",
45
+ "SDK_VERSION",
46
+ ]
@@ -0,0 +1,37 @@
1
+ """Minimal request helper for the single-shot read endpoints (flags, inbox). Unlike
2
+ the event transport these do not retry (matching the JS SDK). The ``opener`` seam
3
+ lets a test substitute the network."""
4
+
5
+ from __future__ import annotations
6
+
7
+ import json
8
+ import urllib.error
9
+ import urllib.request
10
+ from typing import Any, Callable, Dict, Optional, Tuple
11
+
12
+
13
+ def trim_trailing_slash(value: str) -> str:
14
+ return value[:-1] if value.endswith("/") else value
15
+
16
+
17
+ def request(
18
+ method: str,
19
+ url: str,
20
+ project_key: str,
21
+ timeout_s: float,
22
+ *,
23
+ body: Optional[Dict[str, Any]] = None,
24
+ opener: Callable[..., Any] = urllib.request.urlopen,
25
+ ) -> Tuple[int, str]:
26
+ headers = {"authorization": f"Bearer {project_key}"}
27
+ data = None
28
+ if body is not None:
29
+ data = json.dumps(body).encode("utf-8")
30
+ headers["content-type"] = "application/json"
31
+ req = urllib.request.Request(url, data=data, method=method, headers=headers)
32
+ try:
33
+ with opener(req, timeout=timeout_s) as response:
34
+ status = getattr(response, "status", None) or response.getcode()
35
+ return status, response.read().decode("utf-8")
36
+ except urllib.error.HTTPError as error:
37
+ return error.code, error.read().decode("utf-8", "replace")