zenmanage 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.
- zenmanage-1.0.0/.github/workflows/ci.yml +39 -0
- zenmanage-1.0.0/.github/workflows/publish.yml +39 -0
- zenmanage-1.0.0/.gitignore +14 -0
- zenmanage-1.0.0/CHANGELOG.md +11 -0
- zenmanage-1.0.0/LICENSE +21 -0
- zenmanage-1.0.0/PKG-INFO +282 -0
- zenmanage-1.0.0/README.md +246 -0
- zenmanage-1.0.0/docs/FRAMEWORK_INTEGRATIONS.md +99 -0
- zenmanage-1.0.0/examples/README.md +20 -0
- zenmanage-1.0.0/examples/ab_testing.py +25 -0
- zenmanage-1.0.0/examples/caching.py +17 -0
- zenmanage-1.0.0/examples/context_based_flags.py +18 -0
- zenmanage-1.0.0/examples/defaults.py +19 -0
- zenmanage-1.0.0/examples/django_integration.py +23 -0
- zenmanage-1.0.0/examples/fastapi_async.py +30 -0
- zenmanage-1.0.0/examples/flask_integration.py +22 -0
- zenmanage-1.0.0/examples/percentage_rollouts.py +15 -0
- zenmanage-1.0.0/examples/simple_flags.py +8 -0
- zenmanage-1.0.0/pyproject.toml +80 -0
- zenmanage-1.0.0/scripts/validate_release.py +99 -0
- zenmanage-1.0.0/src/zenmanage/__init__.py +46 -0
- zenmanage-1.0.0/src/zenmanage/api_client.py +163 -0
- zenmanage-1.0.0/src/zenmanage/async_api_client.py +165 -0
- zenmanage-1.0.0/src/zenmanage/async_flag_manager.py +163 -0
- zenmanage-1.0.0/src/zenmanage/async_zenmanage.py +52 -0
- zenmanage-1.0.0/src/zenmanage/cache/__init__.py +8 -0
- zenmanage-1.0.0/src/zenmanage/cache/base.py +17 -0
- zenmanage-1.0.0/src/zenmanage/cache/filesystem.py +65 -0
- zenmanage-1.0.0/src/zenmanage/cache/in_memory.py +35 -0
- zenmanage-1.0.0/src/zenmanage/cache/null.py +22 -0
- zenmanage-1.0.0/src/zenmanage/config.py +133 -0
- zenmanage-1.0.0/src/zenmanage/context.py +89 -0
- zenmanage-1.0.0/src/zenmanage/defaults_collection.py +43 -0
- zenmanage-1.0.0/src/zenmanage/errors.py +27 -0
- zenmanage-1.0.0/src/zenmanage/flag.py +108 -0
- zenmanage-1.0.0/src/zenmanage/flag_manager.py +163 -0
- zenmanage-1.0.0/src/zenmanage/py.typed +0 -0
- zenmanage-1.0.0/src/zenmanage/rollout.py +23 -0
- zenmanage-1.0.0/src/zenmanage/rule_engine.py +178 -0
- zenmanage-1.0.0/src/zenmanage/types.py +104 -0
- zenmanage-1.0.0/src/zenmanage/zenmanage.py +48 -0
- zenmanage-1.0.0/tests/conftest.py +7 -0
- zenmanage-1.0.0/tests/test_api_client.py +174 -0
- zenmanage-1.0.0/tests/test_async_api_client.py +182 -0
- zenmanage-1.0.0/tests/test_async_flag_manager.py +163 -0
- zenmanage-1.0.0/tests/test_async_zenmanage.py +60 -0
- zenmanage-1.0.0/tests/test_cache.py +69 -0
- zenmanage-1.0.0/tests/test_config.py +65 -0
- zenmanage-1.0.0/tests/test_context.py +24 -0
- zenmanage-1.0.0/tests/test_defaults_collection.py +16 -0
- zenmanage-1.0.0/tests/test_flag.py +170 -0
- zenmanage-1.0.0/tests/test_flag_manager.py +189 -0
- zenmanage-1.0.0/tests/test_rollout.py +26 -0
- zenmanage-1.0.0/tests/test_rule_engine.py +176 -0
- zenmanage-1.0.0/tests/test_zenmanage.py +62 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
strategy:
|
|
12
|
+
matrix:
|
|
13
|
+
python-version: ["3.10", "3.11", "3.12"]
|
|
14
|
+
|
|
15
|
+
steps:
|
|
16
|
+
- name: Checkout
|
|
17
|
+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
18
|
+
|
|
19
|
+
- name: Setup Python
|
|
20
|
+
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
|
21
|
+
with:
|
|
22
|
+
python-version: ${{ matrix.python-version }}
|
|
23
|
+
|
|
24
|
+
- name: Install dependencies
|
|
25
|
+
run: |
|
|
26
|
+
python -m pip install --upgrade pip
|
|
27
|
+
pip install -e ".[dev]"
|
|
28
|
+
|
|
29
|
+
- name: Lint
|
|
30
|
+
run: ruff check .
|
|
31
|
+
|
|
32
|
+
- name: Type check
|
|
33
|
+
run: mypy src
|
|
34
|
+
|
|
35
|
+
- name: Validate release metadata
|
|
36
|
+
run: python scripts/validate_release.py
|
|
37
|
+
|
|
38
|
+
- name: Test
|
|
39
|
+
run: pytest
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
name: Publish
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
publish:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
|
|
12
|
+
steps:
|
|
13
|
+
- name: Checkout
|
|
14
|
+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
15
|
+
|
|
16
|
+
- name: Setup Python
|
|
17
|
+
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
|
18
|
+
with:
|
|
19
|
+
python-version: "3.12"
|
|
20
|
+
|
|
21
|
+
- name: Install dependencies
|
|
22
|
+
run: |
|
|
23
|
+
python -m pip install --upgrade pip
|
|
24
|
+
pip install -e ".[dev]"
|
|
25
|
+
|
|
26
|
+
- name: Validate tag, version, and changelog
|
|
27
|
+
run: python scripts/validate_release.py --tag "${{ github.ref_name }}"
|
|
28
|
+
|
|
29
|
+
- name: Build
|
|
30
|
+
run: python -m build
|
|
31
|
+
|
|
32
|
+
- name: Verify package
|
|
33
|
+
run: python -m twine check dist/*
|
|
34
|
+
|
|
35
|
+
- name: Publish to PyPI
|
|
36
|
+
env:
|
|
37
|
+
TWINE_USERNAME: __token__
|
|
38
|
+
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
|
|
39
|
+
run: python -m twine upload dist/*
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented in this file.
|
|
4
|
+
|
|
5
|
+
## 1.0.0 - 2026-05-31
|
|
6
|
+
|
|
7
|
+
- Initial Python SDK implementation.
|
|
8
|
+
- Added ConfigBuilder, flag evaluation, context targeting, and deterministic percentage rollouts.
|
|
9
|
+
- Added cache backends: in-memory, filesystem, and null.
|
|
10
|
+
- Added comprehensive test suite with 90%+ coverage target.
|
|
11
|
+
- Added runnable examples and publishing guide.
|
zenmanage-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Zenmanage
|
|
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.
|
zenmanage-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: zenmanage
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Official Python SDK for Zenmanage feature flags with local evaluation
|
|
5
|
+
Project-URL: Homepage, https://github.com/zenmanage/zenmanage-python
|
|
6
|
+
Project-URL: Repository, https://github.com/zenmanage/zenmanage-python
|
|
7
|
+
Project-URL: Documentation, https://github.com/zenmanage/zenmanage-python#readme
|
|
8
|
+
Project-URL: Issues, https://github.com/zenmanage/zenmanage-python/issues
|
|
9
|
+
Author-email: Zenmanage <hello@zenmanage.com>
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: ab-testing,feature-flags,feature-toggles,remote-config,zenmanage
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
|
+
Requires-Python: >=3.9
|
|
23
|
+
Requires-Dist: httpx>=0.27.0
|
|
24
|
+
Requires-Dist: requests>=2.31.0
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: build>=1.2.1; extra == 'dev'
|
|
27
|
+
Requires-Dist: mypy>=1.11.1; extra == 'dev'
|
|
28
|
+
Requires-Dist: pytest-asyncio>=0.23.7; extra == 'dev'
|
|
29
|
+
Requires-Dist: pytest-cov>=5.0.0; extra == 'dev'
|
|
30
|
+
Requires-Dist: pytest>=8.2.0; extra == 'dev'
|
|
31
|
+
Requires-Dist: requests-mock>=1.12.1; extra == 'dev'
|
|
32
|
+
Requires-Dist: ruff>=0.6.0; extra == 'dev'
|
|
33
|
+
Requires-Dist: twine>=5.1.1; extra == 'dev'
|
|
34
|
+
Requires-Dist: types-requests>=2.32.0.20250602; extra == 'dev'
|
|
35
|
+
Description-Content-Type: text/markdown
|
|
36
|
+
|
|
37
|
+
# Zenmanage Python SDK
|
|
38
|
+
|
|
39
|
+
[](https://pypi.org/project/zenmanage/)
|
|
40
|
+
[](https://github.com/zenmanage/zenmanage-python/actions/workflows/ci.yml)
|
|
41
|
+
[](https://app.codacy.com/gh/zenmanage/zenmanage-python/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)
|
|
42
|
+
[](#development)
|
|
43
|
+
|
|
44
|
+
Add feature flags to your Python application in minutes. Control feature rollouts, A/B test, and manage configurations without deploying code.
|
|
45
|
+
|
|
46
|
+
## Why Zenmanage?
|
|
47
|
+
|
|
48
|
+
- Fast: rules cached locally for low-latency evaluation
|
|
49
|
+
- Targeted: roll out by user, organization, or custom attributes
|
|
50
|
+
- Safe: graceful defaults and typed accessors
|
|
51
|
+
- Insightful: optional usage reporting
|
|
52
|
+
- Testable: deterministic rollout logic and isolated rule engine
|
|
53
|
+
|
|
54
|
+
## Installation
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pip install zenmanage
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Requirements: Python 3.9+
|
|
61
|
+
|
|
62
|
+
## Key Compatibility
|
|
63
|
+
|
|
64
|
+
- Supported in Python SDK: case-sensitive server keys prefixed with `srv_`
|
|
65
|
+
- Not supported in Python SDK: client keys (`cli_`) and mobile keys (`mob_`) (initialization fails fast)
|
|
66
|
+
|
|
67
|
+
## Get Started in 60 Seconds
|
|
68
|
+
|
|
69
|
+
1. Get your server key (`srv_...`) from [zenmanage.com](https://zenmanage.com)
|
|
70
|
+
2. Initialize the SDK:
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
from zenmanage import ConfigBuilder, Zenmanage
|
|
74
|
+
|
|
75
|
+
zenmanage = Zenmanage(
|
|
76
|
+
ConfigBuilder.create()
|
|
77
|
+
.with_environment_token("srv_your_server_key_here")
|
|
78
|
+
.build()
|
|
79
|
+
)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
3. Check a feature flag:
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
flag = zenmanage.flags().single("new-dashboard", False)
|
|
86
|
+
|
|
87
|
+
if flag.is_enabled():
|
|
88
|
+
show_new_dashboard()
|
|
89
|
+
else:
|
|
90
|
+
show_old_dashboard()
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Common Use Cases
|
|
94
|
+
|
|
95
|
+
### Async Frameworks (FastAPI, Starlette, etc.)
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
from zenmanage import AsyncZenmanage, ConfigBuilder, Context
|
|
99
|
+
|
|
100
|
+
zenmanage = AsyncZenmanage(
|
|
101
|
+
ConfigBuilder.from_environment().build()
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
async def is_enabled_for_user(user_id: str) -> bool:
|
|
105
|
+
context = Context.single("user", user_id)
|
|
106
|
+
flag = await zenmanage.flags().with_context(context).single("new-dashboard", False)
|
|
107
|
+
return flag.is_enabled()
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Remember to close the async client on shutdown:
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
await zenmanage.aclose()
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Roll Out a New Feature Gradually
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
from zenmanage import Context
|
|
120
|
+
|
|
121
|
+
context = Context.single("user", user_id, user_name)
|
|
122
|
+
|
|
123
|
+
beta_access = (
|
|
124
|
+
zenmanage.flags()
|
|
125
|
+
.with_context(context)
|
|
126
|
+
.single("beta-program", False)
|
|
127
|
+
.is_enabled()
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
if beta_access:
|
|
131
|
+
enable_beta_features()
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### A/B Testing
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
from zenmanage import Attribute, Context
|
|
138
|
+
|
|
139
|
+
context = Context.single("user", user.id, user.name)
|
|
140
|
+
context.add_attribute(Attribute.from_strings("country", [user.country]))
|
|
141
|
+
context.add_attribute(Attribute.from_strings("plan", [user.subscription_plan]))
|
|
142
|
+
|
|
143
|
+
variant = (
|
|
144
|
+
zenmanage.flags()
|
|
145
|
+
.with_context(context)
|
|
146
|
+
.single("checkout-flow", "multi-page")
|
|
147
|
+
.as_string()
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
if variant == "one-page":
|
|
151
|
+
render_one_page_checkout()
|
|
152
|
+
else:
|
|
153
|
+
render_multi_page_checkout()
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Percentage Rollouts
|
|
157
|
+
|
|
158
|
+
```python
|
|
159
|
+
from zenmanage import Context
|
|
160
|
+
|
|
161
|
+
context = Context.single("user", user_id)
|
|
162
|
+
|
|
163
|
+
flag = (
|
|
164
|
+
zenmanage.flags()
|
|
165
|
+
.with_context(context)
|
|
166
|
+
.single("new-checkout-flow", False)
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
if flag.is_enabled():
|
|
170
|
+
render_new_checkout()
|
|
171
|
+
else:
|
|
172
|
+
render_classic_checkout()
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
How it works:
|
|
176
|
+
|
|
177
|
+
- Configure rollout percentage (0-100) and salt in Zenmanage
|
|
178
|
+
- SDK computes CRC32B bucket from `salt:contextIdentifier`
|
|
179
|
+
- Same user always lands in same bucket
|
|
180
|
+
- Increasing percentage only adds users, never removes included users
|
|
181
|
+
|
|
182
|
+
### Use Defaults Across Many Flags
|
|
183
|
+
|
|
184
|
+
```python
|
|
185
|
+
from zenmanage import DefaultsCollection
|
|
186
|
+
|
|
187
|
+
defaults = DefaultsCollection.from_dict(
|
|
188
|
+
{
|
|
189
|
+
"new-ui": True,
|
|
190
|
+
"api-version": "v2",
|
|
191
|
+
"max-items": 100,
|
|
192
|
+
}
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
flag_manager = zenmanage.flags().with_defaults(defaults)
|
|
196
|
+
new_ui = flag_manager.single("new-ui").as_bool()
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Fetch All Flags
|
|
200
|
+
|
|
201
|
+
```python
|
|
202
|
+
for flag in zenmanage.flags().all():
|
|
203
|
+
print(flag.key, flag.get_value())
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## Configuration
|
|
207
|
+
|
|
208
|
+
```python
|
|
209
|
+
import logging
|
|
210
|
+
|
|
211
|
+
config = (
|
|
212
|
+
ConfigBuilder.create()
|
|
213
|
+
.with_environment_token("srv_your_server_key_here")
|
|
214
|
+
.with_cache_ttl(3600)
|
|
215
|
+
.with_cache_backend("memory") # memory | filesystem | null
|
|
216
|
+
.with_cache_directory(".cache/zenmanage") # required for filesystem
|
|
217
|
+
.with_usage_reporting(True)
|
|
218
|
+
.with_api_endpoint("https://api.zenmanage.com")
|
|
219
|
+
.with_logger(logging.getLogger("my-app"))
|
|
220
|
+
.build()
|
|
221
|
+
)
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
You can also load from environment variables:
|
|
225
|
+
|
|
226
|
+
- `ZENMANAGE_ENVIRONMENT_TOKEN`
|
|
227
|
+
- `ZENMANAGE_CACHE_TTL`
|
|
228
|
+
- `ZENMANAGE_CACHE_BACKEND`
|
|
229
|
+
- `ZENMANAGE_CACHE_DIR`
|
|
230
|
+
- `ZENMANAGE_ENABLE_USAGE_REPORTING`
|
|
231
|
+
- `ZENMANAGE_API_ENDPOINT`
|
|
232
|
+
|
|
233
|
+
```python
|
|
234
|
+
config = ConfigBuilder.from_environment().build()
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## Cache Backends
|
|
238
|
+
|
|
239
|
+
- `memory`: default, fastest, process-local
|
|
240
|
+
- `filesystem`: durable between process restarts
|
|
241
|
+
- `null`: disables caching
|
|
242
|
+
|
|
243
|
+
Custom cache objects are supported with `with_cache(...)` as long as they implement `get`, `set`, `has`, `delete`, and `clear`.
|
|
244
|
+
|
|
245
|
+
## Examples
|
|
246
|
+
|
|
247
|
+
See [examples/README.md](examples/README.md) for runnable examples:
|
|
248
|
+
|
|
249
|
+
- simple-flags
|
|
250
|
+
- ab-testing
|
|
251
|
+
- caching
|
|
252
|
+
- context-based-flags
|
|
253
|
+
- defaults
|
|
254
|
+
- percentage-rollouts
|
|
255
|
+
- django-integration
|
|
256
|
+
- flask-integration
|
|
257
|
+
- fastapi-async
|
|
258
|
+
|
|
259
|
+
Framework integrations: [docs/FRAMEWORK_INTEGRATIONS.md](docs/FRAMEWORK_INTEGRATIONS.md)
|
|
260
|
+
|
|
261
|
+
## Development
|
|
262
|
+
|
|
263
|
+
```bash
|
|
264
|
+
python -m venv .venv
|
|
265
|
+
source .venv/bin/activate
|
|
266
|
+
pip install -e .[dev]
|
|
267
|
+
pytest
|
|
268
|
+
ruff check .
|
|
269
|
+
mypy src
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
## Publishing
|
|
273
|
+
|
|
274
|
+
Validate tag/version/changelog consistency locally:
|
|
275
|
+
|
|
276
|
+
```bash
|
|
277
|
+
python scripts/validate_release.py --tag v1.0.0
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
## License
|
|
281
|
+
|
|
282
|
+
MIT
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
# Zenmanage Python SDK
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/zenmanage/)
|
|
4
|
+
[](https://github.com/zenmanage/zenmanage-python/actions/workflows/ci.yml)
|
|
5
|
+
[](https://app.codacy.com/gh/zenmanage/zenmanage-python/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)
|
|
6
|
+
[](#development)
|
|
7
|
+
|
|
8
|
+
Add feature flags to your Python application in minutes. Control feature rollouts, A/B test, and manage configurations without deploying code.
|
|
9
|
+
|
|
10
|
+
## Why Zenmanage?
|
|
11
|
+
|
|
12
|
+
- Fast: rules cached locally for low-latency evaluation
|
|
13
|
+
- Targeted: roll out by user, organization, or custom attributes
|
|
14
|
+
- Safe: graceful defaults and typed accessors
|
|
15
|
+
- Insightful: optional usage reporting
|
|
16
|
+
- Testable: deterministic rollout logic and isolated rule engine
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pip install zenmanage
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Requirements: Python 3.9+
|
|
25
|
+
|
|
26
|
+
## Key Compatibility
|
|
27
|
+
|
|
28
|
+
- Supported in Python SDK: case-sensitive server keys prefixed with `srv_`
|
|
29
|
+
- Not supported in Python SDK: client keys (`cli_`) and mobile keys (`mob_`) (initialization fails fast)
|
|
30
|
+
|
|
31
|
+
## Get Started in 60 Seconds
|
|
32
|
+
|
|
33
|
+
1. Get your server key (`srv_...`) from [zenmanage.com](https://zenmanage.com)
|
|
34
|
+
2. Initialize the SDK:
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
from zenmanage import ConfigBuilder, Zenmanage
|
|
38
|
+
|
|
39
|
+
zenmanage = Zenmanage(
|
|
40
|
+
ConfigBuilder.create()
|
|
41
|
+
.with_environment_token("srv_your_server_key_here")
|
|
42
|
+
.build()
|
|
43
|
+
)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
3. Check a feature flag:
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
flag = zenmanage.flags().single("new-dashboard", False)
|
|
50
|
+
|
|
51
|
+
if flag.is_enabled():
|
|
52
|
+
show_new_dashboard()
|
|
53
|
+
else:
|
|
54
|
+
show_old_dashboard()
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Common Use Cases
|
|
58
|
+
|
|
59
|
+
### Async Frameworks (FastAPI, Starlette, etc.)
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
from zenmanage import AsyncZenmanage, ConfigBuilder, Context
|
|
63
|
+
|
|
64
|
+
zenmanage = AsyncZenmanage(
|
|
65
|
+
ConfigBuilder.from_environment().build()
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
async def is_enabled_for_user(user_id: str) -> bool:
|
|
69
|
+
context = Context.single("user", user_id)
|
|
70
|
+
flag = await zenmanage.flags().with_context(context).single("new-dashboard", False)
|
|
71
|
+
return flag.is_enabled()
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Remember to close the async client on shutdown:
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
await zenmanage.aclose()
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Roll Out a New Feature Gradually
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
from zenmanage import Context
|
|
84
|
+
|
|
85
|
+
context = Context.single("user", user_id, user_name)
|
|
86
|
+
|
|
87
|
+
beta_access = (
|
|
88
|
+
zenmanage.flags()
|
|
89
|
+
.with_context(context)
|
|
90
|
+
.single("beta-program", False)
|
|
91
|
+
.is_enabled()
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
if beta_access:
|
|
95
|
+
enable_beta_features()
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### A/B Testing
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
from zenmanage import Attribute, Context
|
|
102
|
+
|
|
103
|
+
context = Context.single("user", user.id, user.name)
|
|
104
|
+
context.add_attribute(Attribute.from_strings("country", [user.country]))
|
|
105
|
+
context.add_attribute(Attribute.from_strings("plan", [user.subscription_plan]))
|
|
106
|
+
|
|
107
|
+
variant = (
|
|
108
|
+
zenmanage.flags()
|
|
109
|
+
.with_context(context)
|
|
110
|
+
.single("checkout-flow", "multi-page")
|
|
111
|
+
.as_string()
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
if variant == "one-page":
|
|
115
|
+
render_one_page_checkout()
|
|
116
|
+
else:
|
|
117
|
+
render_multi_page_checkout()
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Percentage Rollouts
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
from zenmanage import Context
|
|
124
|
+
|
|
125
|
+
context = Context.single("user", user_id)
|
|
126
|
+
|
|
127
|
+
flag = (
|
|
128
|
+
zenmanage.flags()
|
|
129
|
+
.with_context(context)
|
|
130
|
+
.single("new-checkout-flow", False)
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
if flag.is_enabled():
|
|
134
|
+
render_new_checkout()
|
|
135
|
+
else:
|
|
136
|
+
render_classic_checkout()
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
How it works:
|
|
140
|
+
|
|
141
|
+
- Configure rollout percentage (0-100) and salt in Zenmanage
|
|
142
|
+
- SDK computes CRC32B bucket from `salt:contextIdentifier`
|
|
143
|
+
- Same user always lands in same bucket
|
|
144
|
+
- Increasing percentage only adds users, never removes included users
|
|
145
|
+
|
|
146
|
+
### Use Defaults Across Many Flags
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
from zenmanage import DefaultsCollection
|
|
150
|
+
|
|
151
|
+
defaults = DefaultsCollection.from_dict(
|
|
152
|
+
{
|
|
153
|
+
"new-ui": True,
|
|
154
|
+
"api-version": "v2",
|
|
155
|
+
"max-items": 100,
|
|
156
|
+
}
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
flag_manager = zenmanage.flags().with_defaults(defaults)
|
|
160
|
+
new_ui = flag_manager.single("new-ui").as_bool()
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Fetch All Flags
|
|
164
|
+
|
|
165
|
+
```python
|
|
166
|
+
for flag in zenmanage.flags().all():
|
|
167
|
+
print(flag.key, flag.get_value())
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Configuration
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
import logging
|
|
174
|
+
|
|
175
|
+
config = (
|
|
176
|
+
ConfigBuilder.create()
|
|
177
|
+
.with_environment_token("srv_your_server_key_here")
|
|
178
|
+
.with_cache_ttl(3600)
|
|
179
|
+
.with_cache_backend("memory") # memory | filesystem | null
|
|
180
|
+
.with_cache_directory(".cache/zenmanage") # required for filesystem
|
|
181
|
+
.with_usage_reporting(True)
|
|
182
|
+
.with_api_endpoint("https://api.zenmanage.com")
|
|
183
|
+
.with_logger(logging.getLogger("my-app"))
|
|
184
|
+
.build()
|
|
185
|
+
)
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
You can also load from environment variables:
|
|
189
|
+
|
|
190
|
+
- `ZENMANAGE_ENVIRONMENT_TOKEN`
|
|
191
|
+
- `ZENMANAGE_CACHE_TTL`
|
|
192
|
+
- `ZENMANAGE_CACHE_BACKEND`
|
|
193
|
+
- `ZENMANAGE_CACHE_DIR`
|
|
194
|
+
- `ZENMANAGE_ENABLE_USAGE_REPORTING`
|
|
195
|
+
- `ZENMANAGE_API_ENDPOINT`
|
|
196
|
+
|
|
197
|
+
```python
|
|
198
|
+
config = ConfigBuilder.from_environment().build()
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## Cache Backends
|
|
202
|
+
|
|
203
|
+
- `memory`: default, fastest, process-local
|
|
204
|
+
- `filesystem`: durable between process restarts
|
|
205
|
+
- `null`: disables caching
|
|
206
|
+
|
|
207
|
+
Custom cache objects are supported with `with_cache(...)` as long as they implement `get`, `set`, `has`, `delete`, and `clear`.
|
|
208
|
+
|
|
209
|
+
## Examples
|
|
210
|
+
|
|
211
|
+
See [examples/README.md](examples/README.md) for runnable examples:
|
|
212
|
+
|
|
213
|
+
- simple-flags
|
|
214
|
+
- ab-testing
|
|
215
|
+
- caching
|
|
216
|
+
- context-based-flags
|
|
217
|
+
- defaults
|
|
218
|
+
- percentage-rollouts
|
|
219
|
+
- django-integration
|
|
220
|
+
- flask-integration
|
|
221
|
+
- fastapi-async
|
|
222
|
+
|
|
223
|
+
Framework integrations: [docs/FRAMEWORK_INTEGRATIONS.md](docs/FRAMEWORK_INTEGRATIONS.md)
|
|
224
|
+
|
|
225
|
+
## Development
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
python -m venv .venv
|
|
229
|
+
source .venv/bin/activate
|
|
230
|
+
pip install -e .[dev]
|
|
231
|
+
pytest
|
|
232
|
+
ruff check .
|
|
233
|
+
mypy src
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
## Publishing
|
|
237
|
+
|
|
238
|
+
Validate tag/version/changelog consistency locally:
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
python scripts/validate_release.py --tag v1.0.0
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
## License
|
|
245
|
+
|
|
246
|
+
MIT
|