|
| 1 | +<!-- markdownlint-disable MD033 --> |
| 2 | +<!-- x-hide-in-docs-start --> |
| 3 | +<p align="center"> |
| 4 | + <picture> |
| 5 | + <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/open-feature/community/0e23508c163a6a1ac8c0ced3e4bd78faafe627c7/assets/logo/horizontal/white/openfeature-horizontal-white.svg" /> |
| 6 | + <img align="center" alt="OpenFeature Logo" src="https://raw.githubusercontent.com/open-feature/community/0e23508c163a6a1ac8c0ced3e4bd78faafe627c7/assets/logo/horizontal/black/openfeature-horizontal-black.svg" /> |
| 7 | + </picture> |
| 8 | +</p> |
| 9 | + |
| 10 | +<h2 align="center">OpenFeature Python SDK</h2> |
| 11 | + |
| 12 | +<!-- x-hide-in-docs-end --> |
| 13 | +<!-- The 'github-badges' class is used in the docs --> |
| 14 | +<p align="center" class="github-badges"> |
| 15 | + |
| 16 | + <a href="https://github.com/open-feature/spec/releases/tag/v0.3.0"> |
| 17 | + <img alt="Specification" src="https://img.shields.io/static/v1?label=Specification&message=v0.3.0&color=red&style=for-the-badge" /> |
| 18 | + </a> |
| 19 | + |
| 20 | + <!-- x-release-please-start-version --> |
| 21 | + |
| 22 | + <a href="https://github.com/open-feature/python-sdk/releases/tag/v0.3.1"> |
| 23 | + <img alt="Latest version" src="https://img.shields.io/static/v1?label=release&message=v0.3.1&color=blue&style=for-the-badge" /> |
| 24 | + </a> |
| 25 | + |
| 26 | + <!-- x-release-please-end --> |
| 27 | + <br/> |
| 28 | + <a href="https://github.com/open-feature/python-sdk/actions/workflows/merge.yml"> |
| 29 | + <img alt="Build status" src="https://github.com/open-feature/python-sdk/actions/workflows/build.yml/badge.svg" /> |
| 30 | + </a> |
| 31 | + |
| 32 | + <a href="https://codecov.io/gh/open-feature/python-sdk"> |
| 33 | + <img alt="Codecov" src="https://codecov.io/gh/open-feature/python-sdk/branch/main/graph/badge.svg?token=FQ1I444HB3" /> |
| 34 | + </a> |
| 35 | + |
| 36 | + <a href="https://www.python.org/downloads/"> |
| 37 | + <img alt="Min python version" src="https://img.shields.io/badge/python->=3.8-blue.svg" /> |
| 38 | + </a> |
| 39 | + |
| 40 | + <a href="https://www.repostatus.org/#wip"> |
| 41 | + <img alt="Repo status" src="https://www.repostatus.org/badges/latest/wip.svg" /> |
| 42 | + </a> |
| 43 | +</p> |
| 44 | +<!-- x-hide-in-docs-start --> |
| 45 | + |
| 46 | +[OpenFeature](https://openfeature.dev) is an open specification that provides a vendor-agnostic, community-driven API for feature flagging that works with your favorite feature flag management tool. |
| 47 | + |
| 48 | +<!-- x-hide-in-docs-end --> |
| 49 | + |
| 50 | +## 🚀 Quick start |
| 51 | + |
| 52 | +### Requirements |
| 53 | + |
| 54 | +- Python 3.8+ |
| 55 | + |
| 56 | +### Install |
| 57 | + |
| 58 | +<!---x-release-please-start-version--> |
| 59 | + |
| 60 | +#### Pip install |
| 61 | + |
| 62 | +```bash |
| 63 | +pip install openfeature-sdk==0.3.1 |
| 64 | +``` |
| 65 | + |
| 66 | +#### requirements.txt |
| 67 | + |
| 68 | +```bash |
| 69 | +openfeature-sdk==0.3.1 |
| 70 | +``` |
| 71 | + |
| 72 | +```python |
| 73 | +pip install -r requirements.txt |
| 74 | +``` |
| 75 | + |
| 76 | +<!---x-release-please-end--> |
| 77 | + |
| 78 | +### Usage |
| 79 | + |
| 80 | +```python |
| 81 | +from openfeature import api |
| 82 | +from openfeature.provider.in_memory_provider import InMemoryFlag, InMemoryProvider |
| 83 | + |
| 84 | +# flags defined in memory |
| 85 | +my_flags = { |
| 86 | + "v2_enabled": InMemoryFlag("on", {"on": True, "off": False}) |
| 87 | +} |
| 88 | + |
| 89 | +# configure a provider |
| 90 | +api.set_provider(InMemoryProvider(my_flags)) |
| 91 | + |
| 92 | +# create a client |
| 93 | +client = api.get_client() |
| 94 | + |
| 95 | +# get a bool flag value |
| 96 | +flag_value = client.get_boolean_value("v2_enabled", False) |
| 97 | +print("Value: " + str(flag_value)) |
| 98 | +``` |
| 99 | + |
| 100 | +## 🌟 Features |
| 101 | + |
| 102 | +| Status | Features | Description | |
| 103 | +| ------ | ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | |
| 104 | +| ✅ | [Providers](#providers) | Integrate with a commercial, open source, or in-house feature management tool. | |
| 105 | +| ✅ | [Targeting](#targeting) | Contextually-aware flag evaluation using [evaluation context](https://openfeature.dev/docs/reference/concepts/evaluation-context). | |
| 106 | +| ✅ | [Hooks](#hooks) | Add functionality to various stages of the flag evaluation life-cycle. | |
| 107 | +| ❌ | [Logging](#logging) | Integrate with popular logging packages. | |
| 108 | +| ❌ | [Named clients](#named-clients) | Utilize multiple providers in a single application. | |
| 109 | +| ❌ | [Eventing](#eventing) | React to state changes in the provider or flag management system. | |
| 110 | +| ✅ | [Shutdown](#shutdown) | Gracefully clean up a provider during application shutdown. | |
| 111 | +| ✅ | [Extending](#extending) | Extend OpenFeature with custom providers and hooks. | |
| 112 | + |
| 113 | +<sub>Implemented: ✅ | In-progress: ⚠️ | Not implemented yet: ❌</sub> |
| 114 | + |
| 115 | +### Providers |
| 116 | + |
| 117 | +[Providers](https://openfeature.dev/docs/reference/concepts/provider) are an abstraction between a flag management system and the OpenFeature SDK. |
| 118 | +Look [here](https://openfeature.dev/ecosystem?instant_search%5BrefinementList%5D%5Btype%5D%5B0%5D=Provider&instant_search%5BrefinementList%5D%5Btechnology%5D%5B0%5D=python) for a complete list of available providers. |
| 119 | +If the provider you're looking for hasn't been created yet, see the [develop a provider](#develop-a-provider) section to learn how to build it yourself. |
| 120 | + |
| 121 | +Once you've added a provider as a dependency, it can be registered with OpenFeature like this: |
| 122 | + |
| 123 | +```python |
| 124 | +from openfeature import api |
| 125 | +from openfeature.provider.no_op_provider import NoOpProvider |
| 126 | + |
| 127 | +api.set_provider(NoOpProvider()) |
| 128 | +open_feature_client = api.get_client() |
| 129 | +``` |
| 130 | + |
| 131 | +<!-- In some situations, it may be beneficial to register multiple providers in the same application. |
| 132 | +This is possible using [named clients](#named-clients), which is covered in more detail below. --> |
| 133 | + |
| 134 | +### Targeting |
| 135 | + |
| 136 | +Sometimes, the value of a flag must consider some dynamic criteria about the application or user, such as the user's location, IP, email address, or the server's location. |
| 137 | +In OpenFeature, we refer to this as [targeting](https://openfeature.dev/specification/glossary#targeting). |
| 138 | +If the flag management system you're using supports targeting, you can provide the input data using the [evaluation context](https://openfeature.dev/docs/reference/concepts/evaluation-context). |
| 139 | + |
| 140 | +```python |
| 141 | +from openfeature.api import ( |
| 142 | + get_client, |
| 143 | + get_provider, |
| 144 | + set_provider, |
| 145 | + get_evaluation_context, |
| 146 | + set_evaluation_context, |
| 147 | +) |
| 148 | + |
| 149 | +global_context = EvaluationContext( |
| 150 | + targeting_key="targeting_key1", attributes={"application": "value1"} |
| 151 | +) |
| 152 | +request_context = EvaluationContext( |
| 153 | + targeting_key="targeting_key2", attributes={"email": request.form['email']} |
| 154 | +) |
| 155 | + |
| 156 | +## set global context |
| 157 | +set_evaluation_context(global_context) |
| 158 | + |
| 159 | +# merge second context |
| 160 | +client = get_client(name="No-op Provider") |
| 161 | +client.get_string_value("email", "fallback", request_context) |
| 162 | +``` |
| 163 | + |
| 164 | +### Hooks |
| 165 | + |
| 166 | +[Hooks](https://openfeature.dev/docs/reference/concepts/hooks) allow for custom logic to be added at well-defined points of the flag evaluation life-cycle. |
| 167 | +Look [here](https://openfeature.dev/ecosystem/?instant_search%5BrefinementList%5D%5Btype%5D%5B0%5D=Hook&instant_search%5BrefinementList%5D%5Btechnology%5D%5B0%5D=python) for a complete list of available hooks. |
| 168 | +If the hook you're looking for hasn't been created yet, see the [develop a hook](#develop-a-hook) section to learn how to build it yourself. |
| 169 | + |
| 170 | +Once you've added a hook as a dependency, it can be registered at the global, client, or flag invocation level. |
| 171 | + |
| 172 | +```python |
| 173 | +from openfeature.api import add_hooks |
| 174 | +from openfeature.flag_evaluation import FlagEvaluationOptions |
| 175 | + |
| 176 | +# set global hooks at the API-level |
| 177 | +add_hooks([MyHook()]) |
| 178 | + |
| 179 | +# or configure them in the client |
| 180 | +client = OpenFeatureClient() |
| 181 | +client.add_hooks([MyHook()]) |
| 182 | + |
| 183 | +# or at the invocation-level |
| 184 | +options = FlagEvaluationOptions(hooks=[MyHook()]) |
| 185 | +client.get_boolean_flag("my-flag", False, flag_evaluation_options=options) |
| 186 | +``` |
| 187 | + |
| 188 | +### Logging |
| 189 | + |
| 190 | +Logging customization is not yet available in the Python SDK. |
| 191 | + |
| 192 | +### Named clients |
| 193 | + |
| 194 | +Named clients are not yet available in the Python SDK. Progress on this feature can be tracked [here](https://github.com/open-feature/python-sdk/issues/125). |
| 195 | + |
| 196 | +### Eventing |
| 197 | + |
| 198 | +Events are not yet available in the Python SDK. Progress on this feature can be tracked [here](https://github.com/open-feature/python-sdk/issues/125). |
| 199 | + |
| 200 | +### Shutdown |
| 201 | + |
| 202 | +A shutdown method is not yet available in the Python SDK. Progress on this feature can be tracked [here](https://github.com/open-feature/python-sdk/issues/125). |
| 203 | + |
| 204 | +## Extending |
| 205 | + |
| 206 | +### Develop a provider |
| 207 | + |
| 208 | +To develop a provider, you need to create a new project and include the OpenFeature SDK as a dependency. |
| 209 | +This can be a new repository or included in [the existing contrib repository](https://github.com/open-feature/python-sdk-contrib) available under the OpenFeature organization. |
| 210 | +You’ll then need to write the provider by implementing the `AbstractProvider` class exported by the OpenFeature SDK. |
| 211 | + |
| 212 | +```python |
| 213 | +from typing import List, Optional |
| 214 | + |
| 215 | +from openfeature.evaluation_context import EvaluationContext |
| 216 | +from openfeature.flag_evaluation import FlagResolutionDetails |
| 217 | +from openfeature.provider.provider import AbstractProvider |
| 218 | + |
| 219 | +class MyProvider(AbstractProvider): |
| 220 | + def get_metadata(self) -> Metadata: |
| 221 | + ... |
| 222 | + |
| 223 | + def get_provider_hooks(self) -> List[Hook]: |
| 224 | + return [] |
| 225 | + |
| 226 | + def resolve_boolean_details( |
| 227 | + self, |
| 228 | + flag_key: str, |
| 229 | + default_value: bool, |
| 230 | + evaluation_context: Optional[EvaluationContext] = None, |
| 231 | + ) -> FlagResolutionDetails[bool]: |
| 232 | + ... |
| 233 | + |
| 234 | + def resolve_string_details( |
| 235 | + self, |
| 236 | + flag_key: str, |
| 237 | + default_value: str, |
| 238 | + evaluation_context: Optional[EvaluationContext] = None, |
| 239 | + ) -> FlagResolutionDetails[str]: |
| 240 | + ... |
| 241 | + |
| 242 | + def resolve_integer_details( |
| 243 | + self, |
| 244 | + flag_key: str, |
| 245 | + default_value: int, |
| 246 | + evaluation_context: Optional[EvaluationContext] = None, |
| 247 | + ) -> FlagResolutionDetails[int]: |
| 248 | + ... |
| 249 | + |
| 250 | + def resolve_float_details( |
| 251 | + self, |
| 252 | + flag_key: str, |
| 253 | + default_value: float, |
| 254 | + evaluation_context: Optional[EvaluationContext] = None, |
| 255 | + ) -> FlagResolutionDetails[float]: |
| 256 | + ... |
| 257 | + |
| 258 | + def resolve_object_details( |
| 259 | + self, |
| 260 | + flag_key: str, |
| 261 | + default_value: Union[dict, list], |
| 262 | + evaluation_context: Optional[EvaluationContext] = None, |
| 263 | + ) -> FlagResolutionDetails[Union[dict, list]]: |
| 264 | + ... |
| 265 | +``` |
| 266 | + |
| 267 | +> Built a new provider? [Let us know](https://github.com/open-feature/openfeature.dev/issues/new?assignees=&labels=provider&projects=&template=document-provider.yaml&title=%5BProvider%5D%3A+) so we can add it to the docs! |
| 268 | +
|
| 269 | +### Develop a hook |
| 270 | + |
| 271 | +To develop a hook, you need to create a new project and include the OpenFeature SDK as a dependency. |
| 272 | +This can be a new repository or included in [the existing contrib repository](https://github.com/open-feature/python-sdk-contrib) available under the OpenFeature organization. |
| 273 | +Implement your own hook by creating a hook that inherits from the `Hook` class. |
| 274 | +Any of the evaluation life-cycle stages (`before`/`after`/`error`/`finally_after`) can be override to add the desired business logic. |
| 275 | + |
| 276 | +```python |
| 277 | +from openfeature.hook import Hook |
| 278 | + |
| 279 | +class MyHook(Hook): |
| 280 | + def after(self, hook_context: HookContext, details: FlagEvaluationDetails, hints: dict): |
| 281 | + print("This runs after the flag has been evaluated") |
| 282 | + |
| 283 | +``` |
| 284 | + |
| 285 | +> Built a new hook? [Let us know](https://github.com/open-feature/openfeature.dev/issues/new?assignees=&labels=hook&projects=&template=document-hook.yaml&title=%5BHook%5D%3A+) so we can add it to the docs! |
| 286 | +
|
| 287 | +<!-- x-hide-in-docs-start --> |
| 288 | + |
| 289 | +## ⭐️ Support the project |
| 290 | + |
| 291 | +- Give this repo a ⭐️! |
| 292 | +- Follow us on social media: |
| 293 | + - Twitter: [@openfeature](https://twitter.com/openfeature) |
| 294 | + - LinkedIn: [OpenFeature](https://www.linkedin.com/company/openfeature/) |
| 295 | +- Join us on [Slack](https://cloud-native.slack.com/archives/C0344AANLA1) |
| 296 | +- For more, check out our [community page](https://openfeature.dev/community/) |
| 297 | + |
| 298 | +## 🤝 Contributing |
| 299 | + |
| 300 | +Interested in contributing? Great, we'd love your help! To get started, take a look at the [CONTRIBUTING](CONTRIBUTING.md) guide. |
| 301 | + |
| 302 | +### Thanks to everyone who has already contributed |
| 303 | + |
| 304 | +<a href="https://github.com/open-feature/python-sdk/graphs/contributors"> |
| 305 | + <img src="https://contrib.rocks/image?repo=open-feature/python-sdk" alt="Pictures of the folks who have contributed to the project" /> |
| 306 | +</a> |
| 307 | + |
| 308 | +Made with [contrib.rocks](https://contrib.rocks). |
| 309 | + |
| 310 | +<!-- x-hide-in-docs-end --> |
0 commit comments