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.
- prodantix_sdk-0.0.1/.gitignore +172 -0
- prodantix_sdk-0.0.1/LICENSE +21 -0
- prodantix_sdk-0.0.1/PKG-INFO +106 -0
- prodantix_sdk-0.0.1/README.md +94 -0
- prodantix_sdk-0.0.1/prodantix/__init__.py +46 -0
- prodantix_sdk-0.0.1/prodantix/_http.py +37 -0
- prodantix_sdk-0.0.1/prodantix/client.py +335 -0
- prodantix_sdk-0.0.1/prodantix/clock.py +17 -0
- prodantix_sdk-0.0.1/prodantix/config.py +99 -0
- prodantix_sdk-0.0.1/prodantix/errors.py +45 -0
- prodantix_sdk-0.0.1/prodantix/events.py +45 -0
- prodantix_sdk-0.0.1/prodantix/flag_eval.py +111 -0
- prodantix_sdk-0.0.1/prodantix/flags_client.py +48 -0
- prodantix_sdk-0.0.1/prodantix/messages_client.py +49 -0
- prodantix_sdk-0.0.1/prodantix/predicate.py +163 -0
- prodantix_sdk-0.0.1/prodantix/queue.py +69 -0
- prodantix_sdk-0.0.1/prodantix/transport.py +90 -0
- prodantix_sdk-0.0.1/prodantix/validation.py +54 -0
- prodantix_sdk-0.0.1/prodantix/version.py +4 -0
- prodantix_sdk-0.0.1/pyproject.toml +24 -0
|
@@ -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")
|