FeatureManagement 1.0.0b1__tar.gz → 2.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.
- featuremanagement-2.0.0/CHANGELOG.md +3 -0
- featuremanagement-2.0.0/FeatureManagement.egg-info/PKG-INFO +87 -0
- {featuremanagement-1.0.0b1 → featuremanagement-2.0.0}/FeatureManagement.egg-info/SOURCES.txt +13 -0
- featuremanagement-2.0.0/FeatureManagement.egg-info/requires.txt +3 -0
- featuremanagement-2.0.0/PKG-INFO +87 -0
- featuremanagement-2.0.0/README.md +41 -0
- {featuremanagement-1.0.0b1 → featuremanagement-2.0.0}/featuremanagement/__init__.py +5 -1
- {featuremanagement-1.0.0b1 → featuremanagement-2.0.0}/featuremanagement/_defaultfilters.py +14 -10
- {featuremanagement-1.0.0b1 → featuremanagement-2.0.0}/featuremanagement/_featurefilters.py +9 -6
- featuremanagement-2.0.0/featuremanagement/_featuremanager.py +155 -0
- featuremanagement-2.0.0/featuremanagement/_featuremanagerbase.py +294 -0
- {featuremanagement-1.0.0b1 → featuremanagement-2.0.0}/featuremanagement/_models/__init__.py +12 -2
- featuremanagement-2.0.0/featuremanagement/_models/_allocation.py +207 -0
- {featuremanagement-1.0.0b1 → featuremanagement-2.0.0}/featuremanagement/_models/_constants.py +15 -0
- {featuremanagement-1.0.0b1 → featuremanagement-2.0.0}/featuremanagement/_models/_evaluation_event.py +8 -4
- {featuremanagement-1.0.0b1 → featuremanagement-2.0.0}/featuremanagement/_models/_feature_conditions.py +7 -6
- {featuremanagement-1.0.0b1 → featuremanagement-2.0.0}/featuremanagement/_models/_feature_flag.py +60 -14
- featuremanagement-2.0.0/featuremanagement/_models/_telemetry.py +17 -0
- featuremanagement-2.0.0/featuremanagement/_models/_variant.py +35 -0
- featuremanagement-2.0.0/featuremanagement/_models/_variant_assignment_reason.py +19 -0
- featuremanagement-2.0.0/featuremanagement/_models/_variant_reference.py +65 -0
- {featuremanagement-1.0.0b1 → featuremanagement-2.0.0}/featuremanagement/_version.py +1 -1
- {featuremanagement-1.0.0b1 → featuremanagement-2.0.0}/featuremanagement/aio/_defaultfilters.py +5 -5
- {featuremanagement-1.0.0b1 → featuremanagement-2.0.0}/featuremanagement/aio/_featurefilters.py +9 -6
- featuremanagement-2.0.0/featuremanagement/aio/_featuremanager.py +162 -0
- featuremanagement-2.0.0/featuremanagement/azuremonitor/__init__.py +12 -0
- featuremanagement-2.0.0/featuremanagement/azuremonitor/_send_telemetry.py +83 -0
- {featuremanagement-1.0.0b1 → featuremanagement-2.0.0}/pyproject.toml +3 -3
- featuremanagement-2.0.0/samples/feature_variant_sample.py +25 -0
- featuremanagement-2.0.0/samples/feature_variant_sample_with_telemetry.py +36 -0
- {featuremanagement-1.0.0b1 → featuremanagement-2.0.0}/samples/formatted_feature_flags.json +0 -1
- {featuremanagement-1.0.0b1 → featuremanagement-2.0.0}/setup.py +1 -1
- {featuremanagement-1.0.0b1 → featuremanagement-2.0.0}/tests/test_default_feature_flags.py +1 -1
- {featuremanagement-1.0.0b1 → featuremanagement-2.0.0}/tests/test_default_feature_flags_async.py +1 -1
- {featuremanagement-1.0.0b1 → featuremanagement-2.0.0}/tests/test_feature_manager.py +32 -1
- {featuremanagement-1.0.0b1 → featuremanagement-2.0.0}/tests/test_feature_manager_async.py +54 -1
- {featuremanagement-1.0.0b1 → featuremanagement-2.0.0}/tests/test_feature_manager_refresh.py +1 -1
- featuremanagement-2.0.0/tests/test_feature_variants.py +328 -0
- featuremanagement-2.0.0/tests/test_feature_variants_async.py +324 -0
- featuremanagement-2.0.0/tests/test_send_telemetry_appinsights.py +198 -0
- featuremanagement-1.0.0b1/CHANGELOG.md +0 -13
- featuremanagement-1.0.0b1/FeatureManagement.egg-info/PKG-INFO +0 -340
- featuremanagement-1.0.0b1/FeatureManagement.egg-info/requires.txt +0 -4
- featuremanagement-1.0.0b1/PKG-INFO +0 -340
- featuremanagement-1.0.0b1/README.md +0 -293
- featuremanagement-1.0.0b1/featuremanagement/_featuremanager.py +0 -190
- featuremanagement-1.0.0b1/featuremanagement/aio/_featuremanager.py +0 -142
- {featuremanagement-1.0.0b1 → featuremanagement-2.0.0}/CODE_OF_CONDUCT.md +0 -0
- {featuremanagement-1.0.0b1 → featuremanagement-2.0.0}/FeatureManagement.egg-info/dependency_links.txt +0 -0
- {featuremanagement-1.0.0b1 → featuremanagement-2.0.0}/FeatureManagement.egg-info/not-zip-safe +0 -0
- {featuremanagement-1.0.0b1 → featuremanagement-2.0.0}/FeatureManagement.egg-info/top_level.txt +0 -0
- {featuremanagement-1.0.0b1 → featuremanagement-2.0.0}/LICENSE +0 -0
- {featuremanagement-1.0.0b1 → featuremanagement-2.0.0}/MANIFEST.in +0 -0
- {featuremanagement-1.0.0b1 → featuremanagement-2.0.0}/SECURITY.md +0 -0
- {featuremanagement-1.0.0b1 → featuremanagement-2.0.0}/SUPPORT.md +0 -0
- {featuremanagement-1.0.0b1 → featuremanagement-2.0.0}/featuremanagement/_models/_targeting_context.py +0 -0
- {featuremanagement-1.0.0b1 → featuremanagement-2.0.0}/featuremanagement/aio/__init__.py +0 -0
- {featuremanagement-1.0.0b1 → featuremanagement-2.0.0}/featuremanagement/py.typed +0 -0
- {featuremanagement-1.0.0b1 → featuremanagement-2.0.0}/samples/feature_flag_sample.py +0 -0
- {featuremanagement-1.0.0b1 → featuremanagement-2.0.0}/samples/feature_flag_with_azure_app_configuration_sample.py +0 -0
- {featuremanagement-1.0.0b1 → featuremanagement-2.0.0}/samples/random_filter.py +0 -0
- {featuremanagement-1.0.0b1 → featuremanagement-2.0.0}/setup.cfg +0 -0
- {featuremanagement-1.0.0b1 → featuremanagement-2.0.0}/tests/validation_tests/test_json_validations.py +0 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: FeatureManagement
|
|
3
|
+
Version: 2.0.0
|
|
4
|
+
Summary: A library for enabling/disabling features at runtime.
|
|
5
|
+
Home-page: https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/appconfiguration/feature-management
|
|
6
|
+
Author: Microsoft Corporation
|
|
7
|
+
Author-email: Microsoft Corporation <appconfig@microsoft.com>
|
|
8
|
+
License: MIT License
|
|
9
|
+
|
|
10
|
+
Copyright (c) Microsoft Corporation.
|
|
11
|
+
|
|
12
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
13
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
14
|
+
in the Software without restriction, including without limitation the rights
|
|
15
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
16
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
17
|
+
furnished to do so, subject to the following conditions:
|
|
18
|
+
|
|
19
|
+
The above copyright notice and this permission notice shall be included in all
|
|
20
|
+
copies or substantial portions of the Software.
|
|
21
|
+
|
|
22
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
23
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
24
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
25
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
26
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
27
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
28
|
+
SOFTWARE
|
|
29
|
+
|
|
30
|
+
Project-URL: Homepage, https://github.com/microsoft/FeatureManagement-Python
|
|
31
|
+
Project-URL: Issues, https://github.com/microsoft/FeatureManagement-Python/issues
|
|
32
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
33
|
+
Classifier: Programming Language :: Python
|
|
34
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
35
|
+
Classifier: Programming Language :: Python :: 3
|
|
36
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
40
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
41
|
+
Requires-Python: >=3.6
|
|
42
|
+
Description-Content-Type: text/markdown
|
|
43
|
+
License-File: LICENSE
|
|
44
|
+
Provides-Extra: azuremonitor
|
|
45
|
+
Requires-Dist: azure-monitor-events-extension<2.0.0; extra == "azuremonitor"
|
|
46
|
+
|
|
47
|
+
# Microsoft Feature Management for Python
|
|
48
|
+
|
|
49
|
+
[](https://pypi.org/project/FeatureManagement/)
|
|
50
|
+
|
|
51
|
+
Feature management provides a way to develop and expose application functionality based on features. Many applications have special requirements when a new feature is developed such as when the feature should be enabled and under what conditions. This library provides a way to define these relationships, and also integrates into common Python code patterns to make exposing these features possible.
|
|
52
|
+
|
|
53
|
+
## Get Started
|
|
54
|
+
|
|
55
|
+
[Quickstart](https://learn.microsoft.com/azure/azure-app-configuration/quickstart-feature-flag-python): A quickstart guide is available to learn how to integrate feature flags from Azure App Configuration into your Python applications.
|
|
56
|
+
|
|
57
|
+
[API Reference](https://microsoft.github.io/FeatureManagement-Python/): This API reference details the API surface of the libraries contained within this repository.
|
|
58
|
+
|
|
59
|
+
## Examples
|
|
60
|
+
|
|
61
|
+
* [Python Application](https://github.com/microsoft/FeatureManagement-Python/blob/main/samples/feature_flag_sample.py)
|
|
62
|
+
* [Python Application with Feature Variants](https://github.com/microsoft/FeatureManagement-Python/blob/main/samples/feature_variant_sample.py)
|
|
63
|
+
* [Python Application with Azure App Configuration](https://github.com/microsoft/FeatureManagement-Python/blob/main/samples/feature_flag_with_azure_app_configuration_sample.py)
|
|
64
|
+
* [Django Application](https://github.com/Azure/AppConfiguration/tree/main/examples/Python/python-django-webapp-sample)
|
|
65
|
+
* [Flask Application](https://github.com/Azure/AppConfiguration/tree/main/examples/Python/python-flask-webapp-sample)
|
|
66
|
+
|
|
67
|
+
## Contributing
|
|
68
|
+
|
|
69
|
+
This project welcomes contributions and suggestions. Most contributions require you to agree to a
|
|
70
|
+
Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
|
|
71
|
+
the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com.
|
|
72
|
+
|
|
73
|
+
When you submit a pull request, a CLA bot will automatically determine whether you need to provide
|
|
74
|
+
a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions
|
|
75
|
+
provided by the bot. You will only need to do this once across all repos using our CLA.
|
|
76
|
+
|
|
77
|
+
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
|
|
78
|
+
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
|
|
79
|
+
contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
|
|
80
|
+
|
|
81
|
+
## Trademarks
|
|
82
|
+
|
|
83
|
+
This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft
|
|
84
|
+
trademarks or logos is subject to and must follow
|
|
85
|
+
[Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general).
|
|
86
|
+
Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship.
|
|
87
|
+
Any use of third-party trademarks or logos are subject to those third-party's policies.
|
{featuremanagement-1.0.0b1 → featuremanagement-2.0.0}/FeatureManagement.egg-info/SOURCES.txt
RENAMED
|
@@ -17,20 +17,30 @@ featuremanagement/__init__.py
|
|
|
17
17
|
featuremanagement/_defaultfilters.py
|
|
18
18
|
featuremanagement/_featurefilters.py
|
|
19
19
|
featuremanagement/_featuremanager.py
|
|
20
|
+
featuremanagement/_featuremanagerbase.py
|
|
20
21
|
featuremanagement/_version.py
|
|
21
22
|
featuremanagement/py.typed
|
|
22
23
|
featuremanagement/_models/__init__.py
|
|
24
|
+
featuremanagement/_models/_allocation.py
|
|
23
25
|
featuremanagement/_models/_constants.py
|
|
24
26
|
featuremanagement/_models/_evaluation_event.py
|
|
25
27
|
featuremanagement/_models/_feature_conditions.py
|
|
26
28
|
featuremanagement/_models/_feature_flag.py
|
|
27
29
|
featuremanagement/_models/_targeting_context.py
|
|
30
|
+
featuremanagement/_models/_telemetry.py
|
|
31
|
+
featuremanagement/_models/_variant.py
|
|
32
|
+
featuremanagement/_models/_variant_assignment_reason.py
|
|
33
|
+
featuremanagement/_models/_variant_reference.py
|
|
28
34
|
featuremanagement/aio/__init__.py
|
|
29
35
|
featuremanagement/aio/_defaultfilters.py
|
|
30
36
|
featuremanagement/aio/_featurefilters.py
|
|
31
37
|
featuremanagement/aio/_featuremanager.py
|
|
38
|
+
featuremanagement/azuremonitor/__init__.py
|
|
39
|
+
featuremanagement/azuremonitor/_send_telemetry.py
|
|
32
40
|
samples/feature_flag_sample.py
|
|
33
41
|
samples/feature_flag_with_azure_app_configuration_sample.py
|
|
42
|
+
samples/feature_variant_sample.py
|
|
43
|
+
samples/feature_variant_sample_with_telemetry.py
|
|
34
44
|
samples/formatted_feature_flags.json
|
|
35
45
|
samples/random_filter.py
|
|
36
46
|
tests/test_default_feature_flags.py
|
|
@@ -38,4 +48,7 @@ tests/test_default_feature_flags_async.py
|
|
|
38
48
|
tests/test_feature_manager.py
|
|
39
49
|
tests/test_feature_manager_async.py
|
|
40
50
|
tests/test_feature_manager_refresh.py
|
|
51
|
+
tests/test_feature_variants.py
|
|
52
|
+
tests/test_feature_variants_async.py
|
|
53
|
+
tests/test_send_telemetry_appinsights.py
|
|
41
54
|
tests/validation_tests/test_json_validations.py
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: FeatureManagement
|
|
3
|
+
Version: 2.0.0
|
|
4
|
+
Summary: A library for enabling/disabling features at runtime.
|
|
5
|
+
Home-page: https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/appconfiguration/feature-management
|
|
6
|
+
Author: Microsoft Corporation
|
|
7
|
+
Author-email: Microsoft Corporation <appconfig@microsoft.com>
|
|
8
|
+
License: MIT License
|
|
9
|
+
|
|
10
|
+
Copyright (c) Microsoft Corporation.
|
|
11
|
+
|
|
12
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
13
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
14
|
+
in the Software without restriction, including without limitation the rights
|
|
15
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
16
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
17
|
+
furnished to do so, subject to the following conditions:
|
|
18
|
+
|
|
19
|
+
The above copyright notice and this permission notice shall be included in all
|
|
20
|
+
copies or substantial portions of the Software.
|
|
21
|
+
|
|
22
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
23
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
24
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
25
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
26
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
27
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
28
|
+
SOFTWARE
|
|
29
|
+
|
|
30
|
+
Project-URL: Homepage, https://github.com/microsoft/FeatureManagement-Python
|
|
31
|
+
Project-URL: Issues, https://github.com/microsoft/FeatureManagement-Python/issues
|
|
32
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
33
|
+
Classifier: Programming Language :: Python
|
|
34
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
35
|
+
Classifier: Programming Language :: Python :: 3
|
|
36
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
40
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
41
|
+
Requires-Python: >=3.6
|
|
42
|
+
Description-Content-Type: text/markdown
|
|
43
|
+
License-File: LICENSE
|
|
44
|
+
Provides-Extra: azuremonitor
|
|
45
|
+
Requires-Dist: azure-monitor-events-extension<2.0.0; extra == "azuremonitor"
|
|
46
|
+
|
|
47
|
+
# Microsoft Feature Management for Python
|
|
48
|
+
|
|
49
|
+
[](https://pypi.org/project/FeatureManagement/)
|
|
50
|
+
|
|
51
|
+
Feature management provides a way to develop and expose application functionality based on features. Many applications have special requirements when a new feature is developed such as when the feature should be enabled and under what conditions. This library provides a way to define these relationships, and also integrates into common Python code patterns to make exposing these features possible.
|
|
52
|
+
|
|
53
|
+
## Get Started
|
|
54
|
+
|
|
55
|
+
[Quickstart](https://learn.microsoft.com/azure/azure-app-configuration/quickstart-feature-flag-python): A quickstart guide is available to learn how to integrate feature flags from Azure App Configuration into your Python applications.
|
|
56
|
+
|
|
57
|
+
[API Reference](https://microsoft.github.io/FeatureManagement-Python/): This API reference details the API surface of the libraries contained within this repository.
|
|
58
|
+
|
|
59
|
+
## Examples
|
|
60
|
+
|
|
61
|
+
* [Python Application](https://github.com/microsoft/FeatureManagement-Python/blob/main/samples/feature_flag_sample.py)
|
|
62
|
+
* [Python Application with Feature Variants](https://github.com/microsoft/FeatureManagement-Python/blob/main/samples/feature_variant_sample.py)
|
|
63
|
+
* [Python Application with Azure App Configuration](https://github.com/microsoft/FeatureManagement-Python/blob/main/samples/feature_flag_with_azure_app_configuration_sample.py)
|
|
64
|
+
* [Django Application](https://github.com/Azure/AppConfiguration/tree/main/examples/Python/python-django-webapp-sample)
|
|
65
|
+
* [Flask Application](https://github.com/Azure/AppConfiguration/tree/main/examples/Python/python-flask-webapp-sample)
|
|
66
|
+
|
|
67
|
+
## Contributing
|
|
68
|
+
|
|
69
|
+
This project welcomes contributions and suggestions. Most contributions require you to agree to a
|
|
70
|
+
Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
|
|
71
|
+
the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com.
|
|
72
|
+
|
|
73
|
+
When you submit a pull request, a CLA bot will automatically determine whether you need to provide
|
|
74
|
+
a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions
|
|
75
|
+
provided by the bot. You will only need to do this once across all repos using our CLA.
|
|
76
|
+
|
|
77
|
+
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
|
|
78
|
+
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
|
|
79
|
+
contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
|
|
80
|
+
|
|
81
|
+
## Trademarks
|
|
82
|
+
|
|
83
|
+
This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft
|
|
84
|
+
trademarks or logos is subject to and must follow
|
|
85
|
+
[Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general).
|
|
86
|
+
Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship.
|
|
87
|
+
Any use of third-party trademarks or logos are subject to those third-party's policies.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Microsoft Feature Management for Python
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/FeatureManagement/)
|
|
4
|
+
|
|
5
|
+
Feature management provides a way to develop and expose application functionality based on features. Many applications have special requirements when a new feature is developed such as when the feature should be enabled and under what conditions. This library provides a way to define these relationships, and also integrates into common Python code patterns to make exposing these features possible.
|
|
6
|
+
|
|
7
|
+
## Get Started
|
|
8
|
+
|
|
9
|
+
[Quickstart](https://learn.microsoft.com/azure/azure-app-configuration/quickstart-feature-flag-python): A quickstart guide is available to learn how to integrate feature flags from Azure App Configuration into your Python applications.
|
|
10
|
+
|
|
11
|
+
[API Reference](https://microsoft.github.io/FeatureManagement-Python/): This API reference details the API surface of the libraries contained within this repository.
|
|
12
|
+
|
|
13
|
+
## Examples
|
|
14
|
+
|
|
15
|
+
* [Python Application](https://github.com/microsoft/FeatureManagement-Python/blob/main/samples/feature_flag_sample.py)
|
|
16
|
+
* [Python Application with Feature Variants](https://github.com/microsoft/FeatureManagement-Python/blob/main/samples/feature_variant_sample.py)
|
|
17
|
+
* [Python Application with Azure App Configuration](https://github.com/microsoft/FeatureManagement-Python/blob/main/samples/feature_flag_with_azure_app_configuration_sample.py)
|
|
18
|
+
* [Django Application](https://github.com/Azure/AppConfiguration/tree/main/examples/Python/python-django-webapp-sample)
|
|
19
|
+
* [Flask Application](https://github.com/Azure/AppConfiguration/tree/main/examples/Python/python-flask-webapp-sample)
|
|
20
|
+
|
|
21
|
+
## Contributing
|
|
22
|
+
|
|
23
|
+
This project welcomes contributions and suggestions. Most contributions require you to agree to a
|
|
24
|
+
Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
|
|
25
|
+
the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com.
|
|
26
|
+
|
|
27
|
+
When you submit a pull request, a CLA bot will automatically determine whether you need to provide
|
|
28
|
+
a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions
|
|
29
|
+
provided by the bot. You will only need to do this once across all repos using our CLA.
|
|
30
|
+
|
|
31
|
+
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
|
|
32
|
+
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
|
|
33
|
+
contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
|
|
34
|
+
|
|
35
|
+
## Trademarks
|
|
36
|
+
|
|
37
|
+
This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft
|
|
38
|
+
trademarks or logos is subject to and must follow
|
|
39
|
+
[Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general).
|
|
40
|
+
Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship.
|
|
41
|
+
Any use of third-party trademarks or logos are subject to those third-party's policies.
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
from ._featuremanager import FeatureManager
|
|
7
7
|
from ._featurefilters import FeatureFilter
|
|
8
8
|
from ._defaultfilters import TimeWindowFilter, TargetingFilter
|
|
9
|
-
from ._models import TargetingContext
|
|
9
|
+
from ._models import FeatureFlag, Variant, EvaluationEvent, VariantAssignmentReason, TargetingContext
|
|
10
10
|
|
|
11
11
|
from ._version import VERSION
|
|
12
12
|
|
|
@@ -16,5 +16,9 @@ __all__ = [
|
|
|
16
16
|
"TimeWindowFilter",
|
|
17
17
|
"TargetingFilter",
|
|
18
18
|
"FeatureFilter",
|
|
19
|
+
"FeatureFlag",
|
|
20
|
+
"Variant",
|
|
21
|
+
"EvaluationEvent",
|
|
22
|
+
"VariantAssignmentReason",
|
|
19
23
|
"TargetingContext",
|
|
20
24
|
]
|
|
@@ -5,10 +5,9 @@
|
|
|
5
5
|
# -------------------------------------------------------------------------
|
|
6
6
|
import logging
|
|
7
7
|
import hashlib
|
|
8
|
-
|
|
9
8
|
from datetime import datetime, timezone
|
|
10
9
|
from email.utils import parsedate_to_datetime
|
|
11
|
-
|
|
10
|
+
from typing import cast, List, Mapping, Optional, Dict, Any
|
|
12
11
|
from ._featurefilters import FeatureFilter
|
|
13
12
|
|
|
14
13
|
FEATURE_FLAG_NAME_KEY = "feature_name"
|
|
@@ -45,7 +44,7 @@ class TimeWindowFilter(FeatureFilter):
|
|
|
45
44
|
Feature Filter that determines if the current time is within the time window.
|
|
46
45
|
"""
|
|
47
46
|
|
|
48
|
-
def evaluate(self, context, **kwargs):
|
|
47
|
+
def evaluate(self, context: Mapping[Any, Any], **kwargs: Any) -> bool:
|
|
49
48
|
"""
|
|
50
49
|
Determine if the feature flag is enabled for the given context.
|
|
51
50
|
|
|
@@ -75,7 +74,7 @@ class TargetingFilter(FeatureFilter):
|
|
|
75
74
|
"""
|
|
76
75
|
|
|
77
76
|
@staticmethod
|
|
78
|
-
def _is_targeted(context_id, rollout_percentage):
|
|
77
|
+
def _is_targeted(context_id: str, rollout_percentage: int) -> bool:
|
|
79
78
|
"""Determine if the user is targeted for the given context"""
|
|
80
79
|
# Always return true if rollout percentage is 100
|
|
81
80
|
if rollout_percentage == 100:
|
|
@@ -87,7 +86,9 @@ class TargetingFilter(FeatureFilter):
|
|
|
87
86
|
percentage = (context_marker / (2**32 - 1)) * 100
|
|
88
87
|
return percentage < rollout_percentage
|
|
89
88
|
|
|
90
|
-
def _target_group(
|
|
89
|
+
def _target_group(
|
|
90
|
+
self, target_user: Optional[str], target_group: str, group: Mapping[str, Any], feature_flag_name: str
|
|
91
|
+
) -> bool:
|
|
91
92
|
group_rollout_percentage = group.get(ROLLOUT_PERCENTAGE_KEY, 0)
|
|
92
93
|
if not target_user:
|
|
93
94
|
target_user = ""
|
|
@@ -95,7 +96,7 @@ class TargetingFilter(FeatureFilter):
|
|
|
95
96
|
|
|
96
97
|
return self._is_targeted(audience_context_id, group_rollout_percentage)
|
|
97
98
|
|
|
98
|
-
def evaluate(self, context, **kwargs):
|
|
99
|
+
def evaluate(self, context: Mapping[Any, Any], **kwargs: Any) -> bool:
|
|
99
100
|
"""
|
|
100
101
|
Determine if the feature flag is enabled for the given context.
|
|
101
102
|
|
|
@@ -103,8 +104,11 @@ class TargetingFilter(FeatureFilter):
|
|
|
103
104
|
:return: True if the user is targeted for the feature flag.
|
|
104
105
|
:rtype: bool
|
|
105
106
|
"""
|
|
106
|
-
target_user =
|
|
107
|
-
|
|
107
|
+
target_user: Optional[str] = cast(
|
|
108
|
+
str,
|
|
109
|
+
kwargs.get(TARGETED_USER_KEY, None),
|
|
110
|
+
)
|
|
111
|
+
target_groups: List[str] = cast(List[str], kwargs.get(TARGETED_GROUPS_KEY, []))
|
|
108
112
|
|
|
109
113
|
if not target_user and not (target_groups and len(target_groups) > 0):
|
|
110
114
|
logging.warning("%s: Name or Groups are required parameters", TargetingFilter.__name__)
|
|
@@ -152,11 +156,11 @@ class TargetingFilter(FeatureFilter):
|
|
|
152
156
|
return self._is_targeted(context_id, default_rollout_percentage)
|
|
153
157
|
|
|
154
158
|
@staticmethod
|
|
155
|
-
def _validate(groups, default_rollout_percentage):
|
|
159
|
+
def _validate(groups: List[Dict[str, Any]], default_rollout_percentage: int) -> None:
|
|
156
160
|
# Validate the audience settings
|
|
157
161
|
if default_rollout_percentage < 0 or default_rollout_percentage > 100:
|
|
158
162
|
raise TargetingException("DefaultRolloutPercentage must be between 0 and 100")
|
|
159
163
|
|
|
160
164
|
for group in groups:
|
|
161
|
-
if group.get(ROLLOUT_PERCENTAGE_KEY) < 0 or group.get(ROLLOUT_PERCENTAGE_KEY) > 100:
|
|
165
|
+
if group.get(ROLLOUT_PERCENTAGE_KEY, 0) < 0 or group.get(ROLLOUT_PERCENTAGE_KEY, 100) > 100:
|
|
162
166
|
raise TargetingException("RolloutPercentage must be between 0 and 100")
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
# license information.
|
|
5
5
|
# -------------------------------------------------------------------------
|
|
6
6
|
from abc import ABC, abstractmethod
|
|
7
|
+
from typing import Mapping, Callable, Any, Optional
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
class FeatureFilter(ABC):
|
|
@@ -11,8 +12,10 @@ class FeatureFilter(ABC):
|
|
|
11
12
|
Parent class for all feature filters.
|
|
12
13
|
"""
|
|
13
14
|
|
|
15
|
+
_alias: Optional[str] = None
|
|
16
|
+
|
|
14
17
|
@abstractmethod
|
|
15
|
-
def evaluate(self, context, **kwargs):
|
|
18
|
+
def evaluate(self, context: Mapping[Any, Any], **kwargs: Any) -> bool:
|
|
16
19
|
"""
|
|
17
20
|
Determine if the feature flag is enabled for the given context.
|
|
18
21
|
|
|
@@ -20,28 +23,28 @@ class FeatureFilter(ABC):
|
|
|
20
23
|
"""
|
|
21
24
|
|
|
22
25
|
@property
|
|
23
|
-
def name(self):
|
|
26
|
+
def name(self) -> str:
|
|
24
27
|
"""
|
|
25
28
|
Get the name of the filter.
|
|
26
29
|
|
|
27
30
|
:return: Name of the filter, or alias if it exists.
|
|
28
31
|
:rtype: str
|
|
29
32
|
"""
|
|
30
|
-
if hasattr(self, "_alias"):
|
|
33
|
+
if hasattr(self, "_alias") and self._alias:
|
|
31
34
|
return self._alias
|
|
32
35
|
return self.__class__.__name__
|
|
33
36
|
|
|
34
37
|
@staticmethod
|
|
35
|
-
def alias(alias):
|
|
38
|
+
def alias(alias: str) -> Callable[..., Any]:
|
|
36
39
|
"""
|
|
37
40
|
Decorator to set the alias for the filter.
|
|
38
41
|
|
|
39
42
|
:param str alias: Alias for the filter.
|
|
40
43
|
:return: Decorator.
|
|
41
|
-
:rtype:
|
|
44
|
+
:rtype: Callable
|
|
42
45
|
"""
|
|
43
46
|
|
|
44
|
-
def wrapper(cls):
|
|
47
|
+
def wrapper(cls: "FeatureFilter") -> Any:
|
|
45
48
|
cls._alias = alias # pylint: disable=protected-access
|
|
46
49
|
return cls
|
|
47
50
|
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# ------------------------------------------------------------------------
|
|
2
|
+
# Copyright (c) Microsoft Corporation. All rights reserved.
|
|
3
|
+
# Licensed under the MIT License. See License.txt in the project root for
|
|
4
|
+
# license information.
|
|
5
|
+
# -------------------------------------------------------------------------
|
|
6
|
+
from typing import cast, overload, Any, Optional, Dict, Mapping, List
|
|
7
|
+
from ._defaultfilters import TimeWindowFilter, TargetingFilter
|
|
8
|
+
from ._featurefilters import FeatureFilter
|
|
9
|
+
from ._models import EvaluationEvent, Variant, TargetingContext
|
|
10
|
+
from ._featuremanagerbase import (
|
|
11
|
+
FeatureManagerBase,
|
|
12
|
+
PROVIDED_FEATURE_FILTERS,
|
|
13
|
+
REQUIREMENT_TYPE_ALL,
|
|
14
|
+
FEATURE_FILTER_NAME,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class FeatureManager(FeatureManagerBase):
|
|
19
|
+
"""
|
|
20
|
+
Feature Manager that determines if a feature flag is enabled for the given context.
|
|
21
|
+
|
|
22
|
+
:param Mapping configuration: Configuration object.
|
|
23
|
+
:keyword list[FeatureFilter] feature_filters: Custom filters to be used for evaluating feature flags.
|
|
24
|
+
:keyword Callable[EvaluationEvent] on_feature_evaluated: Callback function to be called when a feature flag is
|
|
25
|
+
evaluated.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(self, configuration: Mapping[str, Any], **kwargs: Any):
|
|
29
|
+
super().__init__(configuration, **kwargs)
|
|
30
|
+
self._filters: Dict[str, FeatureFilter] = {}
|
|
31
|
+
filters = [TimeWindowFilter(), TargetingFilter()] + cast(
|
|
32
|
+
List[FeatureFilter], kwargs.pop(PROVIDED_FEATURE_FILTERS, [])
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
for feature_filter in filters:
|
|
36
|
+
if not isinstance(feature_filter, FeatureFilter):
|
|
37
|
+
raise ValueError("Custom filter must be a subclass of FeatureFilter")
|
|
38
|
+
self._filters[feature_filter.name] = feature_filter
|
|
39
|
+
|
|
40
|
+
@overload # type: ignore
|
|
41
|
+
def is_enabled(self, feature_flag_id: str, user_id: str, **kwargs: Any) -> bool:
|
|
42
|
+
"""
|
|
43
|
+
Determine if the feature flag is enabled for the given context.
|
|
44
|
+
|
|
45
|
+
:param str feature_flag_id: Name of the feature flag.
|
|
46
|
+
:param str user_id: User identifier.
|
|
47
|
+
:return: True if the feature flag is enabled for the given context.
|
|
48
|
+
:rtype: bool
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
def is_enabled(self, feature_flag_id: str, *args: Any, **kwargs: Any) -> bool:
|
|
52
|
+
"""
|
|
53
|
+
Determine if the feature flag is enabled for the given context.
|
|
54
|
+
|
|
55
|
+
:param str feature_flag_id: Name of the feature flag.
|
|
56
|
+
:return: True if the feature flag is enabled for the given context.
|
|
57
|
+
:rtype: bool
|
|
58
|
+
"""
|
|
59
|
+
targeting_context = self._build_targeting_context(args)
|
|
60
|
+
|
|
61
|
+
result = self._check_feature(feature_flag_id, targeting_context, **kwargs)
|
|
62
|
+
if (
|
|
63
|
+
self._on_feature_evaluated
|
|
64
|
+
and result.feature
|
|
65
|
+
and result.feature.telemetry.enabled
|
|
66
|
+
and callable(self._on_feature_evaluated)
|
|
67
|
+
):
|
|
68
|
+
result.user = targeting_context.user_id
|
|
69
|
+
self._on_feature_evaluated(result)
|
|
70
|
+
return result.enabled
|
|
71
|
+
|
|
72
|
+
@overload # type: ignore
|
|
73
|
+
def get_variant(self, feature_flag_id: str, user_id: str, **kwargs: Any) -> Optional[Variant]:
|
|
74
|
+
"""
|
|
75
|
+
Determine the variant for the given context.
|
|
76
|
+
|
|
77
|
+
:param str feature_flag_id: Name of the feature flag.
|
|
78
|
+
:param str user_id: User identifier.
|
|
79
|
+
:return: return: Variant instance.
|
|
80
|
+
:rtype: Variant
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
def get_variant(self, feature_flag_id: str, *args: Any, **kwargs: Any) -> Optional[Variant]:
|
|
84
|
+
"""
|
|
85
|
+
Determine the variant for the given context.
|
|
86
|
+
|
|
87
|
+
:param str feature_flag_id: Name of the feature flag
|
|
88
|
+
:keyword TargetingContext targeting_context: Targeting context.
|
|
89
|
+
:return: Variant instance.
|
|
90
|
+
:rtype: Variant
|
|
91
|
+
"""
|
|
92
|
+
targeting_context = self._build_targeting_context(args)
|
|
93
|
+
|
|
94
|
+
result = self._check_feature(feature_flag_id, targeting_context, **kwargs)
|
|
95
|
+
if (
|
|
96
|
+
self._on_feature_evaluated
|
|
97
|
+
and result.feature
|
|
98
|
+
and result.feature.telemetry.enabled
|
|
99
|
+
and callable(self._on_feature_evaluated)
|
|
100
|
+
):
|
|
101
|
+
result.user = targeting_context.user_id
|
|
102
|
+
self._on_feature_evaluated(result)
|
|
103
|
+
return result.variant
|
|
104
|
+
|
|
105
|
+
def _check_feature_filters(
|
|
106
|
+
self, evaluation_event: EvaluationEvent, targeting_context: TargetingContext, **kwargs: Any
|
|
107
|
+
) -> None:
|
|
108
|
+
feature_flag = evaluation_event.feature
|
|
109
|
+
if not feature_flag:
|
|
110
|
+
return
|
|
111
|
+
feature_conditions = feature_flag.conditions
|
|
112
|
+
feature_filters = feature_conditions.client_filters
|
|
113
|
+
|
|
114
|
+
if len(feature_filters) == 0:
|
|
115
|
+
# Feature flags without any filters return evaluate
|
|
116
|
+
evaluation_event.enabled = True
|
|
117
|
+
else:
|
|
118
|
+
# The assumed value is no filters is based on the requirement type.
|
|
119
|
+
# Requirement type Any assumes false until proven true, All assumes true until proven false
|
|
120
|
+
evaluation_event.enabled = feature_conditions.requirement_type == REQUIREMENT_TYPE_ALL
|
|
121
|
+
|
|
122
|
+
for feature_filter in feature_filters:
|
|
123
|
+
filter_name = feature_filter[FEATURE_FILTER_NAME]
|
|
124
|
+
kwargs["user"] = targeting_context.user_id
|
|
125
|
+
kwargs["groups"] = targeting_context.groups
|
|
126
|
+
if filter_name not in self._filters:
|
|
127
|
+
raise ValueError(f"Feature flag {feature_flag.name} has unknown filter {filter_name}")
|
|
128
|
+
if feature_conditions.requirement_type == REQUIREMENT_TYPE_ALL:
|
|
129
|
+
if not self._filters[filter_name].evaluate(feature_filter, **kwargs):
|
|
130
|
+
evaluation_event.enabled = False
|
|
131
|
+
break
|
|
132
|
+
elif self._filters[filter_name].evaluate(feature_filter, **kwargs):
|
|
133
|
+
evaluation_event.enabled = True
|
|
134
|
+
break
|
|
135
|
+
|
|
136
|
+
def _check_feature(
|
|
137
|
+
self, feature_flag_id: str, targeting_context: TargetingContext, **kwargs: Any
|
|
138
|
+
) -> EvaluationEvent:
|
|
139
|
+
"""
|
|
140
|
+
Determine if the feature flag is enabled for the given context.
|
|
141
|
+
|
|
142
|
+
:param str feature_flag_id: Name of the feature flag.
|
|
143
|
+
:param TargetingContext targeting_context: Targeting context.
|
|
144
|
+
:return: EvaluationEvent for the given context.
|
|
145
|
+
:rtype: EvaluationEvent
|
|
146
|
+
"""
|
|
147
|
+
evaluation_event, done = super()._check_feature_base(feature_flag_id)
|
|
148
|
+
|
|
149
|
+
if done:
|
|
150
|
+
return evaluation_event
|
|
151
|
+
|
|
152
|
+
self._check_feature_filters(evaluation_event, targeting_context, **kwargs)
|
|
153
|
+
|
|
154
|
+
self._assign_allocation(evaluation_event, targeting_context)
|
|
155
|
+
return evaluation_event
|