diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc new file mode 100644 index 0000000..1afb83b --- /dev/null +++ b/CHANGELOG.adoc @@ -0,0 +1,7 @@ += CHANGELOG + +==== 1.1.0 + +- updated to support feature properties +- updated to rev the dependencies. Minimum support of Python 3.7 means +upgrading the urllib to 2.x and SSE client library diff --git a/development.adoc b/development.adoc index 74fe341..0efb7d0 100644 --- a/development.adoc +++ b/development.adoc @@ -10,4 +10,4 @@ This SDK provides two edge clients (a Streaming one based on urllib3's `sseclien == Repo issues - needs reporting on how much test coverage we have -- needs beta testing! + diff --git a/example/build.sh b/example/build.sh index c4b66c7..15dea53 100755 --- a/example/build.sh +++ b/example/build.sh @@ -1,2 +1,3 @@ #!/bin/sh -docker build --no-cache -t featurehub/python-example:1.0 . +#docker build --no-cache -t featurehub/python-example:1.0 . +docker build -t featurehub/python-example:1.0 . diff --git a/example/config.py b/example/config.py index 112007b..5d7e6c4 100644 --- a/example/config.py +++ b/example/config.py @@ -1,2 +1,2 @@ -edge_url = 'https://zjbisc.demo.featurehub.io' +edge_url = 'http://zjbisc.demo.featurehub.io' client_eval_key = 'default/9b71f803-da79-4c04-8081-e5c0176dda87/CtVlmUHirgPd9Qz92Y0IQauUMUv3Wb*4dacoo47oYp6hSFFjVkG' diff --git a/example/load.sh b/example/load.sh new file mode 100755 index 0000000..47c4f72 --- /dev/null +++ b/example/load.sh @@ -0,0 +1,3 @@ +#!/bin/sh +kind load docker-image featurehub/python-example:1.0 --name featurehub-cluster +#featurehub-cluster \ No newline at end of file diff --git a/example/main.py b/example/main.py index 7c2d910..67e5db9 100755 --- a/example/main.py +++ b/example/main.py @@ -55,7 +55,7 @@ def create_app(config=None): fh_config = FeatureHubConfig(edge_url, [client_eval_key]) # to use polling - fh_config.use_polling_edge_service() + fh_config.use_polling_edge_service(1) # it takes a parameter uses the environment variable FEATUREHUB_POLL_INTERVAL if set print("starting featurehub") diff --git a/example/requirements.txt b/example/requirements.txt index f67a748..44dfbcb 100644 --- a/example/requirements.txt +++ b/example/requirements.txt @@ -1,5 +1,5 @@ Flask[async]==2.0.3 flask-cors==3.0.10 -#git+https://github.com/featurehub-io/featurehub-python-sdk@main#egg=featurehub-sdk +git+https://github.com/featurehub-io/featurehub-python-sdk@feature/sdk-extensions#egg=featurehub-sdk diff --git a/featurehub_sdk/client_context.py b/featurehub_sdk/client_context.py index cf73833..afdec32 100644 --- a/featurehub_sdk/client_context.py +++ b/featurehub_sdk/client_context.py @@ -29,6 +29,10 @@ def locked(self): def id(self): return "" + @property + def feature_properties(self) -> dict[str,str]: + return {} + @property def get_version(self) -> int: return -1 diff --git a/featurehub_sdk/featurehub_repository.py b/featurehub_sdk/featurehub_repository.py index fd5517d..e2e5837 100644 --- a/featurehub_sdk/featurehub_repository.py +++ b/featurehub_sdk/featurehub_repository.py @@ -61,8 +61,6 @@ def __update_feature_state(self, feature_state): # if feature is in the dictionary, check if version has changed elif feature_state.get('version') < holder.get_version: return - elif feature_state.get('version') == holder.get_version and feature_state.get('value') == holder.get_value: - return holder.set_feature_state(feature_state) diff --git a/featurehub_sdk/fh_state_base_holder.py b/featurehub_sdk/fh_state_base_holder.py index ac6e95a..763a7fa 100644 --- a/featurehub_sdk/fh_state_base_holder.py +++ b/featurehub_sdk/fh_state_base_holder.py @@ -81,6 +81,15 @@ def __get_value(self, feature_type: Optional[str]) -> Union[None, bool, str, flo return state.get('value') + @property + def feature_properties(self) -> dict[str, str]: + state = self._top_feature_state_holder()._feature_state() + + if state is None or state.get('fp') is None: + return {} + + return state.get('fp') + def with_context(self, ctx: ClientContext) -> FeatureState: return FeatureStateHolder(self._key, self._repo, None, self, ctx) diff --git a/featurehub_sdk/strategy_matchers.py b/featurehub_sdk/strategy_matchers.py index f114462..204c603 100644 --- a/featurehub_sdk/strategy_matchers.py +++ b/featurehub_sdk/strategy_matchers.py @@ -2,7 +2,7 @@ from typing import Optional, List, Dict import re -from semver import cmp +from nodesemver import cmp import math from murmurhash2 import murmurhash3 from ipaddress import ip_address, ip_network diff --git a/featurehub_sdk/test/test_apply_feature.py b/featurehub_sdk/test/test_apply_feature.py index 2d05f61..bf71405 100644 --- a/featurehub_sdk/test/test_apply_feature.py +++ b/featurehub_sdk/test/test_apply_feature.py @@ -61,7 +61,7 @@ def test_should_be_false_if_no_strategies_match_context(self): self.assertIsNone(found.value) ctx.get_attr.assert_called_once() - def test_should_not_match_percentage_and_should_match_field(self): + def test_should_match_conditional(self): ctx = MagicMock() ctx.default_percentage_key = 'userkey-value' ctx.get_attr.return_value = 'ponsonby' diff --git a/featurehub_sdk/test/test_feature_properties.py b/featurehub_sdk/test/test_feature_properties.py new file mode 100644 index 0000000..02e0e01 --- /dev/null +++ b/featurehub_sdk/test/test_feature_properties.py @@ -0,0 +1,51 @@ +import json +from unittest import TestCase + +from unittest.mock import MagicMock + +from featurehub_sdk.fh_state_base_holder import FeatureStateHolder + +from featurehub_sdk.client_context import InternalFeatureRepository + + +class FeaturePropertiesTest(TestCase): + repo: InternalFeatureRepository + + def setUp(self) -> None: + self.repo = MagicMock() + + def test_feature_returns_empty_with_no_feature_state(self): + fs = FeatureStateHolder('key', self.repo) + + self.assertEqual(fs.feature_properties, {}) + + def test_feature_returns_empty_dict_with_no_value_in_feature_state(self): + fs = FeatureStateHolder('key', self.repo) + data = '''{ + "id": "227dc2e8-59e8-424a-b510-328ef52010f7", + "key": "key", + "l": true, + "version": 28, + "type": "STRING", + "value": "orange" + }''' + json_data = json.loads(data) + fs.set_feature_state(json_data) + + self.assertEqual(fs.feature_properties, {}) + + def test_feature_returns_dict_when_properties_are_present(self): + fs = FeatureStateHolder('key', self.repo) + data = '''{ + "id": "227dc2e8-59e8-424a-b510-328ef52010f7", + "key": "key", + "l": true, + "version": 28, + "type": "STRING", + "value": "orange", + "fp": {"category": "shoes", "appName": "conga", "portfolio": "fish"} + }''' + json_data = json.loads(data) + fs.set_feature_state(json_data) + + self.assertEqual(fs.feature_properties, {"category": "shoes", "appName": "conga", "portfolio": "fish"}) diff --git a/featurehub_sdk/version.py b/featurehub_sdk/version.py index 09c61f8..ffc155f 100644 --- a/featurehub_sdk/version.py +++ b/featurehub_sdk/version.py @@ -1 +1 @@ -sdk_version="1.0.0" +sdk_version="1.1.0" diff --git a/requirements.txt b/requirements.txt index 907a46d..b574f73 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,4 @@ -urllib3==1.26.* -sseclient-py==1.7.* +urllib3==2.2.* +sseclient-py==1.8.* murmurhash2==0.2.* -node_semver==0.8.* - +node_semver==0.9.* diff --git a/setup.py b/setup.py index f1acfea..e6af055 100644 --- a/setup.py +++ b/setup.py @@ -18,8 +18,8 @@ packages=['featurehub_sdk'], long_description=long_description, long_description_content_type='text/markdown', - install_requires=['urllib3==1.26.*', - 'sseclient-py==1.7.*', + install_requires=['urllib3==2.2.*', + 'sseclient-py==1.8.*', 'murmurhash2==0.2.*', - 'node_semver==0.8.*'], + 'node_semver==0.9.*'], )