From 70fa4aff7fd0256d099da60046f851b328c458a5 Mon Sep 17 00:00:00 2001 From: Todd Baert Date: Thu, 26 Oct 2023 13:14:04 -0400 Subject: [PATCH 01/10] chore: update readme based on latest template Signed-off-by: Todd Baert --- readme.md | 208 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 124 insertions(+), 84 deletions(-) diff --git a/readme.md b/readme.md index 13564bd0..f7c6ebbb 100644 --- a/readme.md +++ b/readme.md @@ -1,36 +1,59 @@ +

- - - OpenFeature Logo + + OpenFeature Logo

OpenFeature Python SDK

-[![PyPI version](https://badge.fury.io/py/openfeature-sdk.svg)](https://badge.fury.io/py/openfeature-sdk) -![Python 3.8+](https://img.shields.io/badge/python->=3.8-blue.svg) -[![Project Status: WIP – Initial development is in progress, but there has not yet been a stable, usable release suitable for the public.](https://www.repostatus.org/badges/latest/wip.svg)](https://www.repostatus.org/#wip) -[![Specification](https://img.shields.io/static/v1?label=Specification&message=v0.3.0&color=red)](https://github.com/open-feature/spec/tree/v0.3.0) -[![on-merge](https://github.com/open-feature/python-sdk/actions/workflows/merge.yml/badge.svg)](https://github.com/open-feature/python-sdk/actions/workflows/merge.yml) -[![codecov](https://codecov.io/gh/open-feature/python-sdk/branch/main/graph/badge.svg?token=FQ1I444HB3)](https://codecov.io/gh/open-feature/python-sdk) + + +

-> ⚠️ Development is in progress, but there's not a stable release available. ⚠️ + + Specification + -This is the Python implementation of [OpenFeature](https://openfeature.dev), a vendor-agnostic abstraction library for evaluating feature flags. + -We support multiple data types for flags (numbers, strings, booleans, objects) as well as hooks, which can alter the lifecycle of a flag evaluation. + + Codecov + -This library is intended to be used in server-side contexts and has not been evaluated for use in mobile devices. + +
+ + Codecov + -## 🔍 Requirements: + + Codecov + -- Python 3.8+ + + Codecov + + + + Codecov + +

+ + +[OpenFeature](https://openfeature.dev) is an open standard that provides a vendor-agnostic, community-driven API for feature flagging that works with your favorite feature flag management tool. -## 📦 Installation: + + +## 🚀 Quick start + +### Requirements + +- Python 3.8+ -### Add it to your build +### Install @@ -52,55 +75,53 @@ pip install requirements.txt -## 🌟 Features: +### Usage -- support for various backend [providers](https://openfeature.dev/docs/reference/concepts/provider) -- easy integration and extension via [hooks](https://openfeature.dev/docs/reference/concepts/hooks) -- bool, string, numeric, and object flag types -- [context-aware](https://openfeature.dev/docs/reference/concepts/evaluation-context) evaluation + -## 🚀 Usage: +### API Reference -### Configure it + -In order to use the sdk there is some minor configuration. Follow the script below: +## 🌟 Features -```python -from openfeature import api -from openfeature.provider.no_op_provider import NoOpProvider +| Status | Features | Description | +| ------ | ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | +| ✅ | [Providers](#providers) | Integrate with a commercial, open source, or in-house feature management tool. | +| ✅ | [Targeting](#targeting) | Contextually-aware flag evaluation using [evaluation context](https://openfeature.dev/docs/reference/concepts/evaluation-context). | +| ✅ | [Hooks](#hooks) | Add functionality to various stages of the flag evaluation life-cycle. | +| ❌ | [Logging](#logging) | Integrate with popular logging packages. | +| ❌ | [Named clients](#named-clients) | Utilize multiple providers in a single application. | +| ❌ | [Eventing](#eventing) | React to state changes in the provider or flag management system. | +| ✅ | [Shutdown](#shutdown) | Gracefully clean up a provider during application shutdown. | +| ✅ | [Extending](#extending) | Extend OpenFeature with custom providers and hooks. | -api.set_provider(NoOpProvider()) -open_feature_client = api.get_client() -``` +Implemented: ✅ | In-progress: ⚠️ | Not implemented yet: ❌ -### Basics: +### Providers -While Boolean provides the simplest introduction, we offer a variety of flag types. +[Providers](https://openfeature.dev/docs/reference/concepts/provider) are an abstraction between a flag management system and the OpenFeature SDK. +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. +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. -```python -# Depending on the flag type, use one of the methods below -flag_key = "PROVIDER_FLAG" -boolean_result = open_feature_client.get_boolean_value(key=flag_key,default_value=False) -integer_result = open_feature_client.get_integer_value(key=flag_key,default_value=-1) -float_result = open_feature_client.get_float_value(key=flag_key,default_value=-1) -string_result = open_feature_client.get_string_value(key=flag_key,default_value="") -object_result = open_feature_client.get_object_value(key=flag_key,default_value={}) -``` - -You can also bind a provider to a specific client by name instead of setting that provider globally: +Once you've added a provider as a dependency, it can be registered with OpenFeature like this: ```python +from openfeature import api +from openfeature.provider.no_op_provider import NoOpProvider api.set_provider(NoOpProvider()) +open_feature_client = api.get_client() ``` -Each provider class may have further setup required i.e. secret keys, environment variables etc +In some situations, it may be beneficial to register multiple providers in the same application. +This is possible using [named clients](#named-clients), which is covered in more detail below. -### Context-aware evaluation: +### Targeting -Sometimes the value of a flag must take into account some dynamic criteria about the application or user, such as the user location, IP, email address, or the location of the server. -In OpenFeature, we refer to this as [`targeting`](https://openfeature.dev/specification/glossary#targeting). -If the flag system you're using supports targeting, you can provide the input data using the `EvaluationContext`. +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. +In OpenFeature, we refer to this as [targeting](https://openfeature.dev/specification/glossary#targeting). +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). ```python from openfeature.api import ( @@ -127,19 +148,61 @@ client.get_string_value("email", None, request_context) ``` -### Events +### Hooks + +[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. +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. +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. -TBD (See Issue [#131](https://github.com/open-feature/python-sdk/issues/131)) +Once you've added a hook as a dependency, it can be registered at the global, client, or flag invocation level. -### Providers: +```python +# set global hooks at the API-level +from openfeature.api import add_hooks +add_hooks([MyHook()]) -To develop a provider, you need to create a new project and include the OpenFeature SDK as a dependency. 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. Finally, you’ll then need to write the provider itself. This can be accomplished by implementing the `Provider` interface exported by the OpenFeature SDK. +# or configure them in the client +client = OpenFeatureClient() +client.add_hooks([MyHook()]) +``` -See [here](https://openfeature.dev/ecosystem) for a catalog of available providers. + -### Hooks: +### Logging -A hook is a mechanism that allows for adding arbitrary behavior at well-defined points of the flag evaluation life-cycle. Use cases include validating the resolved flag value, modifying or adding data to the evaluation context, logging, telemetry, and tracking. +Logging customization is not yet available in the Python SDK. + +### Named clients + +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). + +### Eventing + +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). + +### Shutdown + +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). + +## Extending + +### Develop a provider + +To develop a provider, you need to create a new project and include the OpenFeature SDK as a dependency. +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. +You’ll then need to write the provider by implementing the `FeatureProvider` interface exported by the OpenFeature SDK. + + + +> 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! + +### Develop a hook + +To develop a hook, you need to create a new project and include the OpenFeature SDK as a dependency. +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. +Implement your own hook by conforming to the `Hook interface`. +To satisfy the interface, all methods (`Before`/`After`/`Finally`/`Error`) need to be defined. +To avoid defining empty functions, make use of the `UnimplementedHook` struct (which already implements all the empty functions). ```python from openfeature.hook import Hook @@ -148,21 +211,11 @@ class MyHook(Hook): def after(self, hook_context: HookContext, details: FlagEvaluationDetails, hints: dict): print("This runs after the flag has been evaluated") - -# set global hooks at the API-level -from openfeature.api import add_hooks -add_hooks([MyHook()]) - -# or configure them in the client -client = OpenFeatureClient() -client.add_hooks([MyHook()]) ``` -See [here](https://openfeature.dev/ecosystem) for a catalog of available hooks. +> 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! -### Logging: - -TBD + ## ⭐️ Support the project @@ -171,31 +224,18 @@ TBD - Twitter: [@openfeature](https://twitter.com/openfeature) - LinkedIn: [OpenFeature](https://www.linkedin.com/company/openfeature/) - Join us on [Slack](https://cloud-native.slack.com/archives/C0344AANLA1) -- For more check out our [community page](https://openfeature.dev/community/) +- For more, check out our [community page](https://openfeature.dev/community/) ## 🤝 Contributing Interested in contributing? Great, we'd love your help! To get started, take a look at the [CONTRIBUTING](CONTRIBUTING.md) guide. -### Thanks to everyone that has already contributed +### Thanks to everyone who has already contributed - Pictures of the folks who have contributed to the project Made with [contrib.rocks](https://contrib.rocks). -## Contacting us - -We hold regular meetings which you can see [here](https://github.com/open-feature/community/#meetings-and-events). - -We are also present on the `#openfeature` channel in the [CNCF slack](https://slack.cncf.io/). - -## 📜 License - -[Apache License 2.0](LICENSE) - - - -[openfeature-website]: https://openfeature.dev + From 87e91b1919fddfd42784817db1b305e2f7c5f45b Mon Sep 17 00:00:00 2001 From: Todd Baert Date: Thu, 26 Oct 2023 13:14:39 -0400 Subject: [PATCH 02/10] fixup: mv readme Signed-off-by: Todd Baert --- readme.md => README.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename readme.md => README.md (100%) diff --git a/readme.md b/README.md similarity index 100% rename from readme.md rename to README.md From ca14a1c389a79296e73b1b0fa0481dda668d2ece Mon Sep 17 00:00:00 2001 From: Todd Baert Date: Thu, 26 Oct 2023 14:34:04 -0400 Subject: [PATCH 03/10] fixup: badge alts Signed-off-by: Todd Baert --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index f7c6ebbb..62994e8a 100644 --- a/README.md +++ b/README.md @@ -20,13 +20,13 @@ - Codecov + Latest version
- Codecov + Build status @@ -34,11 +34,11 @@ - Codecov + Min python version - Codecov + Repo status

From cc577d7e7bf5e25dc89ddfd55bc57778e4d546b3 Mon Sep 17 00:00:00 2001 From: Todd Baert Date: Thu, 26 Oct 2023 14:45:24 -0400 Subject: [PATCH 04/10] Update README.md Co-authored-by: Michael Beemer Signed-off-by: Todd Baert --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 62994e8a..73a4f194 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@

- + Specification From 7a75ccec71093919385d988791b48e0af816d985 Mon Sep 17 00:00:00 2001 From: Todd Baert Date: Thu, 26 Oct 2023 14:45:35 -0400 Subject: [PATCH 05/10] Update README.md Co-authored-by: Michael Beemer Signed-off-by: Todd Baert --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 73a4f194..341f01df 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ - + Latest version From 2e8db7eb4bd22d62915cd21cd9434637b47a111d Mon Sep 17 00:00:00 2001 From: Todd Baert Date: Thu, 26 Oct 2023 14:45:41 -0400 Subject: [PATCH 06/10] Update README.md Co-authored-by: Michael Beemer Signed-off-by: Todd Baert --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 341f01df..9b78e9ce 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ -Pip install +#### Pip install ```bash pip install openfeature-sdk==0.3.1 From b1e50de7898f5e351c33b9521ae4a038b819098c Mon Sep 17 00:00:00 2001 From: Todd Baert Date: Thu, 26 Oct 2023 14:45:46 -0400 Subject: [PATCH 07/10] Update README.md Co-authored-by: Michael Beemer Signed-off-by: Todd Baert --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9b78e9ce..5da69f30 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ pip install openfeature-sdk==0.3.1 ``` -requirements.txt +#### requirements.txt ```bash openfeature-sdk==0.3.1 From a689f29e7b6a49fc189bdf77bbd3481f58353fb2 Mon Sep 17 00:00:00 2001 From: Todd Baert Date: Thu, 26 Oct 2023 15:41:30 -0400 Subject: [PATCH 08/10] Update README.md Co-authored-by: Federico Bond Signed-off-by: Todd Baert --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5da69f30..56fa3ac6 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ openfeature-sdk==0.3.1 ``` ```python -pip install requirements.txt +pip install -r requirements.txt ``` From f16df32162fd472ebee25482b5c53c584a2abe5a Mon Sep 17 00:00:00 2001 From: Todd Baert Date: Thu, 26 Oct 2023 15:41:35 -0400 Subject: [PATCH 09/10] Update README.md Co-authored-by: Federico Bond Signed-off-by: Todd Baert --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 56fa3ac6..f987e050 100644 --- a/README.md +++ b/README.md @@ -140,7 +140,7 @@ request_context = EvaluationContext( ) ## set global context -set_evaluation_context(first_context) +set_evaluation_context(global_context) # merge second context client = get_client(name="No-op Provider", version="0.5.2") From 6e0787ad636674c51ad045679071997667a5e75d Mon Sep 17 00:00:00 2001 From: Michael Beemer Date: Wed, 1 Nov 2023 10:23:54 -0400 Subject: [PATCH 10/10] updated readme based on feedback Signed-off-by: Michael Beemer --- README.md | 107 ++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 88 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index f987e050..728c3c22 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ Codecov - + Min python version @@ -43,7 +43,7 @@

-[OpenFeature](https://openfeature.dev) is an open standard that provides a vendor-agnostic, community-driven API for feature flagging that works with your favorite feature flag management tool. +[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. @@ -77,11 +77,25 @@ pip install -r requirements.txt ### Usage - +```python +from openfeature import api +from openfeature.provider.in_memory_provider import InMemoryFlag, InMemoryProvider -### API Reference +# flags defined in memory +my_flags = { + "v2_enabled": InMemoryFlag("on", {"on": True, "off": False}) +} - +# configure a provider +api.set_provider(InMemoryProvider(my_flags)) + +# create a client +client = api.get_client() + +# get a bool flag value +flag_value = client.get_boolean_value("v2_enabled", False) +print("Value: " + str(flag_value)) +``` ## 🌟 Features @@ -114,8 +128,8 @@ api.set_provider(NoOpProvider()) open_feature_client = api.get_client() ``` -In some situations, it may be beneficial to register multiple providers in the same application. -This is possible using [named clients](#named-clients), which is covered in more detail below. + ### Targeting @@ -127,7 +141,7 @@ If the flag management system you're using supports targeting, you can provide t from openfeature.api import ( get_client, get_provider, - set_provider + set_provider, get_evaluation_context, set_evaluation_context, ) @@ -143,9 +157,8 @@ request_context = EvaluationContext( set_evaluation_context(global_context) # merge second context -client = get_client(name="No-op Provider", version="0.5.2") -client.get_string_value("email", None, request_context) - +client = get_client(name="No-op Provider") +client.get_string_value("email", "fallback", request_context) ``` ### Hooks @@ -157,16 +170,20 @@ If the hook you're looking for hasn't been created yet, see the [develop a hook] Once you've added a hook as a dependency, it can be registered at the global, client, or flag invocation level. ```python -# set global hooks at the API-level from openfeature.api import add_hooks +from openfeature.flag_evaluation import FlagEvaluationOptions + +# set global hooks at the API-level add_hooks([MyHook()]) # or configure them in the client client = OpenFeatureClient() client.add_hooks([MyHook()]) -``` - +# or at the invocation-level +options = FlagEvaluationOptions(hooks=[MyHook()]) +client.get_boolean_flag("my-flag", False, flag_evaluation_options=options) +``` ### Logging @@ -190,9 +207,62 @@ A shutdown method is not yet available in the Python SDK. Progress on this featu To develop a provider, you need to create a new project and include the OpenFeature SDK as a dependency. 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. -You’ll then need to write the provider by implementing the `FeatureProvider` interface exported by the OpenFeature SDK. +You’ll then need to write the provider by implementing the `AbstractProvider` class exported by the OpenFeature SDK. - +```python +from typing import List, Optional + +from openfeature.evaluation_context import EvaluationContext +from openfeature.flag_evaluation import FlagResolutionDetails +from openfeature.provider.provider import AbstractProvider + +class MyProvider(AbstractProvider): + def get_metadata(self) -> Metadata: + ... + + def get_provider_hooks(self) -> List[Hook]: + return [] + + def resolve_boolean_details( + self, + flag_key: str, + default_value: bool, + evaluation_context: Optional[EvaluationContext] = None, + ) -> FlagResolutionDetails[bool]: + ... + + def resolve_string_details( + self, + flag_key: str, + default_value: str, + evaluation_context: Optional[EvaluationContext] = None, + ) -> FlagResolutionDetails[str]: + ... + + def resolve_integer_details( + self, + flag_key: str, + default_value: int, + evaluation_context: Optional[EvaluationContext] = None, + ) -> FlagResolutionDetails[int]: + ... + + def resolve_float_details( + self, + flag_key: str, + default_value: float, + evaluation_context: Optional[EvaluationContext] = None, + ) -> FlagResolutionDetails[float]: + ... + + def resolve_object_details( + self, + flag_key: str, + default_value: Union[dict, list], + evaluation_context: Optional[EvaluationContext] = None, + ) -> FlagResolutionDetails[Union[dict, list]]: + ... +``` > 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! @@ -200,9 +270,8 @@ You’ll then need to write the provider by implementing the `FeatureProvider` i To develop a hook, you need to create a new project and include the OpenFeature SDK as a dependency. 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. -Implement your own hook by conforming to the `Hook interface`. -To satisfy the interface, all methods (`Before`/`After`/`Finally`/`Error`) need to be defined. -To avoid defining empty functions, make use of the `UnimplementedHook` struct (which already implements all the empty functions). +Implement your own hook by creating a hook that inherits from the `Hook` class. +Any of the evaluation life-cycle stages (`before`/`after`/`error`/`finally_after`) can be override to add the desired business logic. ```python from openfeature.hook import Hook