Skip to content

Commit 0c98968

Browse files
authored
Support feature flags in Python SDK (growthbook#258)
1 parent 6b3ad64 commit 0c98968

File tree

6 files changed

+264
-187
lines changed

6 files changed

+264
-187
lines changed

packages/docs/pages/app/api.mdx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,5 +68,6 @@ We offer official SDKs that work with these data structures in a few popular lan
6868
- [React](/lib/react)
6969
- [Go](/lib/go)
7070
- [PHP](/lib/php)
71+
- [Python](/lib/python)
7172
- [Kotlin (Android)](/lib/kotlin)
7273
- [Build your own](/lib/build-your-own)

packages/docs/pages/lib/build-your-own.mdx

Lines changed: 22 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ This converts and experiment's coverage and variation weights into an array of b
270270
for (w in weights) {
271271
start = cumulative;
272272
cumulative += w;
273-
ranges.push(start, start + coverage * w);
273+
ranges.push([start, start + coverage * w]);
274274
}
275275

276276
return ranges;
@@ -543,13 +543,13 @@ The method is pretty simple:
543543
};
544544
```
545545

546-
### public feature(key: string): FeatureResult
546+
### public evalFeature(key: string): FeatureResult
547547

548-
The `feature` method takes a single string argument, which is the unique identifier for the feature and returns a `FeatureResult` object.
548+
The `evalFeature` method takes a single string argument, which is the unique identifier for the feature and returns a `FeatureResult` object.
549549

550550
```js
551551
growthbook = new GrowthBook(context);
552-
myFeature = growthbook.feature("my-feature");
552+
myFeature = growthbook.evalFeature("my-feature");
553553
```
554554

555555
There are a few ordered steps to evaluate a feature
@@ -660,71 +660,40 @@ There are a bunch of ordered steps to run an experiment:
660660
14. Fire `context.trackingCallback` if set and the combination of hashAttribute, hashValue, experiment.key, and variationId has not been tracked before
661661
15. Return `result`
662662

663-
## Developer Experience
663+
### Feature helper methods
664664

665-
Having a good developer experience is super important.
666-
667-
### Basic Usage
668-
669-
It should be extremely easy and natural to use features, both on/off flags and full remote config features.
665+
There are 3 tiny helper methods that wrap `evalFeature` for a better developer experience:
670666

671667
```js
672-
// On/off flag
673-
if (growthbook.feature("my-feature").on) {
674-
// ...
668+
isOn(key) {
669+
return this.evalFeature(key).on
675670
}
676671

677-
// Remote config
678-
color = growthbook.feature("signup-button-color").value || "blue";
679-
```
680-
681-
The developer doesn't need to care how the value of a feature was determined, whether it came from a default, an experiment, or through complex targeting rules.
682-
683-
In most cases, this is all the developer will ever need to do. If they do need to run an inline experiment, it should be very simple to run a basic A/B test:
684-
685-
```js
686-
result = growthbook.run({
687-
key: "my-experiment",
688-
variations: ["A", "B"],
689-
});
672+
isOff(key) {
673+
return this.evalFeature(key).off
674+
}
690675

691-
print(result.value); // "A" or "B"
676+
getFeatureValue(key, fallback) {
677+
value = this.evalFeature(key).value
678+
return value === null ? fallback : value
679+
}
692680
```
693681

694-
And it should feel natural to scale up to more complex use cases:
695-
696-
```js
697-
// 50% of beta testers, 80/20 split between variations
698-
result = growthbook.run({
699-
key: "complex-experiment",
700-
variations: [
701-
{ color: "blue", size: "small" },
702-
{ color: "green", size: "large" },
703-
],
704-
weights: [0.8, 0.2],
705-
coverage: 0.5,
706-
condition: {
707-
beta: true,
708-
},
709-
});
710-
711-
print(result.value.color, result.value.size);
712-
// "blue,small" OR "green,large"
713-
```
682+
For strongly typed languages, you can use generics (if supported) for `getFeatureValue` and coerce the return value to always match the data type of fallback. If generics are not supported, you can use type-specific versions of the function such as `getFeatureValueAsString`.
714683

715-
### Type Hinting
684+
## Type Hinting
716685

717686
Most languages have some sort of strong typing support, whether in the language itself or via annotations. This helps to reduce errors and is highly encouraged for SDKs.
718687

719688
If possible, use generics to type the return value. If `experiment.variations` is type `T[]`, then `result.value` should be type `T`.
720689

721690
If your type system supports specifying a minimum array length, it's best to type `experiment.variations` as requiring at least 2 elements.
722691

723-
### Handling Errors
692+
## Handling Errors
724693

725694
The general rule is to be strict in development and lenient in production.
726695

727-
You can throw exceptions in development, but someone's production app should never crash because of a call to `growthbook.feature` or `growthbook.run`.
696+
You can throw exceptions in development, but someone's production app should never crash because of a call to `growthbook.evalFeature` or `growthbook.run`.
728697

729698
For the below edge cases in production, just act as if the problematic property didn't exist and ignore errors:
730699

@@ -741,7 +710,7 @@ For the below edge cases in production, the experiment should be disabled (every
741710
- `context.forcedVariations` specifies an invalid variation index
742711
- `experiment.hashAttribute` is an empty string
743712

744-
### Subscriptions
713+
## Subscriptions
745714

746715
Sometimes it's useful to be able to "subscribe" to a GrowthBook instance and be alerted every time `growthbook.run` is called. This is different from the tracking callback since it also fires when a user is _not_ included in an experiment.
747716

@@ -762,7 +731,7 @@ unsubscriber()
762731

763732
In addition to subscriptions you may also want to expose a `growthbook.getAllResults` method that returns a map of the latest results indexed by experiment key.
764733

765-
### Memory Management
734+
## Memory Management
766735

767736
Subscriptions and tracking calls require storing references to many objects and functions. If it makes sense for your language, libraries should provide a `growthbook.destroy` method to remove all of these references and release their memory.
768737

@@ -805,7 +774,7 @@ The cases.json file is an object. The keys are the function being tested, and th
805774
- condition
806775
- attributes
807776
- expected return value (boolean)
808-
- **feature**
777+
- **feature** (evalFeature)
809778
- name of the test case (string)
810779
- context passed into GrowthBook constructor
811780
- name of the feature (string)

0 commit comments

Comments
 (0)