Skip to content

Commit e2d4bb4

Browse files
authored
Merge pull request #1 from feature-flags-co/feature/1.0-final
feature/1.0-final: python sdk 1.0.0
2 parents 26c2f39 + d3d2338 commit e2d4bb4

20 files changed

+466
-131
lines changed

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@
186186
same "printed page" as the copyright notice for easier
187187
identification within third-party archives.
188188

189-
Copyright 2022 feature-flag.co
189+
Copyright 2022 featureflag.co
190190

191191
Licensed under the Apache License, Version 2.0 (the "License");
192192
you may not use this file except in compliance with the License.

MANIFEST.in

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
include requirements.txt
2+
include README.md
3+
include dev-requirements.txt

README.md

Lines changed: 127 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,127 @@
1-
# ffc-server-python-sdk
1+
# ffc-server-python-sdk
2+
3+
## Introduction
4+
5+
This is the Python Server SDK for the feature management platform [featureflag.co](https://featureflag.co/). It is
6+
intended for use in a multiple-users python server applications.
7+
8+
This SDK has two main purposes:
9+
10+
- Store the available feature flags and evaluate the feature flags by given user in the server side SDK
11+
- Sends feature flags usage, and custom events for the insights and A/B/n testing.
12+
13+
## Data synchonization
14+
15+
We use websocket to make the local data synchronized with the server, and then store them in the memory by default.
16+
Whenever there is any changes to a feature flag or his related data, the changes would be pushed to the SDK, the average
17+
synchronization time is less than **100** ms. Be aware the websocket connection can be interrupted by any error or
18+
internet interruption, but it would be restored automatically right after the problem is gone.
19+
20+
## Offline mode support
21+
22+
In the offline mode, SDK DOES not exchange any data with [featureflag.co](https://featureflag.co/), this mode is only use for internal test for instance.
23+
24+
To open the offline mode:
25+
```python
26+
config = Config(env_secret, offline=True)
27+
```
28+
29+
## Evaluation of a feature flag
30+
31+
SDK will initialize all the related data(feature flags, segments etc.) in the bootstrapping and receive the data updates
32+
in real time, as mentioned in the above
33+
34+
After initialization, the SDK has all the feature flags in the memory and all evaluation is done locally and synchronously, the average evaluation time is < **10** ms.
35+
36+
## Installation
37+
install the sdk in using pip, this version of the SDK is compatible with Python 3.6 through 3.9.
38+
39+
```
40+
pip install ffc-server-python-sdk
41+
```
42+
43+
## SDK
44+
45+
Applications SHOULD instantiate a single instance for the lifetime of the application. In the case where an application
46+
needs to evaluate feature flags from different environments, you may create multiple clients, but they should still be
47+
retained for the lifetime of the application rather than created per request or per thread.
48+
49+
### Bootstrapping
50+
51+
The bootstrapping is in fact the call of constructor of `FFCClient`, in which the SDK will be initialized, using
52+
streaming from [featureflag.co](https://featureflag.co/).
53+
54+
The constructor will return when it successfully connects, or when the timeout(default: 15 seconds) expires, whichever comes first. If it has not succeeded in connecting when the timeout elapses, you will receive the client in an uninitialized state where feature flags will return default values; it will still continue trying to connect in the background unless there has been a network error or you close the client(using `stop()`). You can detect whether initialization has succeeded by calling `initialize()`.
55+
56+
The best way to use the SDK as a singleton, first make sure you have called `ffcclient.set_config()` at startup time. Then `ffcclient.get()` will return the same shared `ffcclient.client.FFCClient` instance each time. The client will be initialized if it runs first time.
57+
```python
58+
from ffcclient.config import Config
59+
from ffcclient import get, set_config
60+
61+
set_config(Config(env_secret))
62+
client = get()
63+
64+
if client.initialize:
65+
# your code
66+
67+
```
68+
You can also manage your `ffcclient.client.FFCClient`, the SDK will be initialized if you call `ffcclient.client.FFCClient` constructor.
69+
```python
70+
from ffcclient.config import Config
71+
from ffcclient.client import FFCClient
72+
73+
client = FFCClient(Config(env_secret), start_wait=15)
74+
75+
if client.initialize:
76+
# your code
77+
78+
```
79+
If you prefer to have the constructor return immediately, and then wait for initialization to finish at some other
80+
point, you can use `ffcclient.client.FFCClient.update_status_provider` object, which provides an asynchronous way, as follows:
81+
82+
``` python
83+
from ffcclient.config import Config
84+
from ffcclient.client import FFCClient
85+
86+
client = FFCClient(Config(env_secret), start_wait=0)
87+
if client._update_status_provider.wait_for_OKState():
88+
# your code
89+
90+
```
91+
92+
93+
### Evaluation
94+
95+
SDK calculates the value of a feature flag for a given user, and returns a flag vlaue/an object that describes the way
96+
that the value was determined.
97+
98+
`User`: A dictionary of attributes that can affect flag evaluation, usually corresponding to a user of your application.
99+
This object contains built-in properties(`key`, `name`, `email` and `country`). The only mandatory property is the key,
100+
which must uniquely identify each user; this could be a username or email address for authenticated users, or a ID for anonymous users.
101+
102+
All other built-in properties are optional, it's strongly recommended to set **name** in order to search your user quickly
103+
You may also define custom properties with arbitrary names and values.
104+
105+
```python
106+
if client.initialize:
107+
user = {'key': user_key, 'name': user_name}
108+
flag_value = client.variation(flag_key, user)
109+
# your if/else code according to flag value
110+
111+
```
112+
If evaluation called before SDK client initialized or you set the wrong flag key or user for the evaluation, SDK will return
113+
the default value you set. The `FlagState` will explain the details of the last evaluation including error raison.
114+
115+
SDK support the String, Boolean, and Number as the return type of flag values, see pydoc for more details.
116+
117+
### Experiments (A/B/n Testing)
118+
We support automatic experiments for pageviews and clicks, you just need to set your experiment on our SaaS platform, then you should be able to see the result in near real time after the experiment is started.
119+
120+
In case you need more control over the experiment data sent to our server, we offer a method to send custom event.
121+
```python
122+
client.track_metric(user, event_name, numeric_value);
123+
```
124+
**numeric_value** is not mandatory, the default value is **1**.
125+
126+
Make sure `track_metric` is called after the related feature flag is evaluated by simply calling `variation` or `variation_detail`
127+
otherwise, the custom event won't be included into the experiment result.

dev-requirements.txt

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
certifi
2-
urllib3
3-
schedule
4-
websocket-client
1+
certifi>=2018.4.16
2+
urllib3>=1.22.0
3+
schedule >= 1.0.0
4+
websocket-client>=1.0.0
55
flake8
66
pytest
7-
autopep8
7+
autopep8
8+
build
9+
twine

ffcclient/__init__.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,15 @@
1212

1313

1414
def get() -> FFCClient:
15+
"""Returns the singleton Python SDK client instance, using the current configuration.
16+
17+
To use the SDK as a singleton, first make sure you have called :func:`ffcclient.set_config()`
18+
at startup time. Then ``get()`` will return the same shared :class:`ffcclient.client.FFCClient`
19+
instance each time. The client will be initialized if it runs first time.
20+
21+
If you need to create multiple client instances with different environments, instead of this
22+
singleton approach you can call directly the :class:`ffcclient.client.FFCClient` constructor.
23+
"""
1524
global __config
1625
global __client
1726
global __lock
@@ -28,14 +37,22 @@ def get() -> FFCClient:
2837
try:
2938
__lock.write_lock()
3039
if not __client:
31-
log.info("FFC Python Client is initializing...")
40+
log.info("FFC Python SDK: FFC Python Client is initializing...")
3241
__client = FFCClient(__config, start_wait)
3342
return __client
3443
finally:
3544
__lock.release_write_lock()
3645

3746

3847
def set_config(config: Config):
48+
"""Sets the configuration for the shared SDK client instance.
49+
50+
If this is called prior to :func:`ffcclient.get()`, it stores the configuration that will be used when the
51+
client is initialized. If it is called after the client has already been initialized, the client will be
52+
re-initialized with the new configuration.
53+
54+
:param config: the client configuration
55+
"""
3956
global __config
4057
global __client
4158
global __lock
@@ -44,7 +61,7 @@ def set_config(config: Config):
4461
__lock.write_lock()
4562
if __client:
4663
__client.stop()
47-
log.info('FFC Python Client is reinitializing...')
64+
log.info('FFC Python SDK: FFC Python Client is reinitializing...')
4865
__client = FFCClient(config, start_wait)
4966
finally:
5067
__config = config

0 commit comments

Comments
 (0)