From 0521bd01a2bd78116f3c188730aefda64dbd89b5 Mon Sep 17 00:00:00 2001 From: "christian.lutnik" Date: Tue, 18 Mar 2025 11:15:13 +0100 Subject: [PATCH 01/30] add support for metadata in flagd Signed-off-by: christian.lutnik --- .../provider/flagd/resolvers/in_process.py | 25 ++++- .../process/connector/file_watcher.py | 9 +- .../provider/flagd/resolvers/process/flags.py | 33 +++++- .../tests/e2e/step/flag_step.py | 15 +++ .../tests/e2e/step/provider_steps.py | 17 ++- .../flags/basic-flag-combined-metadata.json | 29 +++++ .../tests/flags/basic-flag-metadata.json | 19 ++++ .../tests/flags/basic-flag-set-metadata.json | 19 ++++ .../flags/invalid-flag-metadata-list.json | 14 +++ .../tests/flags/invalid-flag-metadata.json | 24 +++++ .../flags/invalid-flag-set-metadata-list.json | 14 +++ .../flags/invalid-flag-set-metadata.json | 21 ++++ .../tests/test_file_store.py | 33 ++++++ .../tests/test_metadata.py | 101 ++++++++++++++++++ 14 files changed, 360 insertions(+), 13 deletions(-) create mode 100644 providers/openfeature-provider-flagd/tests/flags/basic-flag-combined-metadata.json create mode 100644 providers/openfeature-provider-flagd/tests/flags/basic-flag-metadata.json create mode 100644 providers/openfeature-provider-flagd/tests/flags/basic-flag-set-metadata.json create mode 100644 providers/openfeature-provider-flagd/tests/flags/invalid-flag-metadata-list.json create mode 100644 providers/openfeature-provider-flagd/tests/flags/invalid-flag-metadata.json create mode 100644 providers/openfeature-provider-flagd/tests/flags/invalid-flag-set-metadata-list.json create mode 100644 providers/openfeature-provider-flagd/tests/flags/invalid-flag-set-metadata.json create mode 100644 providers/openfeature-provider-flagd/tests/test_metadata.py diff --git a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/in_process.py b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/in_process.py index a555896d..a2d4a40b 100644 --- a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/in_process.py +++ b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/in_process.py @@ -17,6 +17,22 @@ T = typing.TypeVar("T") +def _merge_metadata( + flag_metadata: typing.Mapping[str, typing.Union[float, int, str, bool]], + flag_set_metadata: typing.Mapping[str, typing.Union[float, int, str, bool]] +) -> typing.Mapping[str, typing.Union[float, int, str, bool]]: + metadata = {} + if flag_set_metadata is not None: + for key, value in flag_set_metadata.items(): + metadata[key] = value + + if flag_metadata is not None: + for key, value in flag_metadata.items(): + metadata[key] = value + + return metadata + + class InProcessResolver: def __init__( self, @@ -103,18 +119,20 @@ def _resolve( if not flag: raise FlagNotFoundError(f"Flag with key {key} not present in flag store.") + metadata = _merge_metadata(flag.metadata, self.flag_store.flag_set_metadata) + if flag.state == "DISABLED": - return FlagResolutionDetails(default_value, reason=Reason.DISABLED) + return FlagResolutionDetails(default_value, flag_metadata=metadata, reason=Reason.DISABLED) if not flag.targeting: variant, value = flag.default - return FlagResolutionDetails(value, variant=variant, reason=Reason.STATIC) + return FlagResolutionDetails(value, variant=variant, flag_metadata=metadata, reason=Reason.STATIC) variant = targeting(flag.key, flag.targeting, evaluation_context) if variant is None: variant, value = flag.default - return FlagResolutionDetails(value, variant=variant, reason=Reason.DEFAULT) + return FlagResolutionDetails(value, variant=variant, flag_metadata=metadata, reason=Reason.DEFAULT) if not isinstance(variant, (str, bool)): raise ParseError( "Parsed JSONLogic targeting did not return a string or bool" @@ -128,4 +146,5 @@ def _resolve( value, variant=variant, reason=Reason.TARGETING_MATCH, + flag_metadata=metadata ) diff --git a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/connector/file_watcher.py b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/connector/file_watcher.py index eaf2b6d3..0cd99c11 100644 --- a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/connector/file_watcher.py +++ b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/connector/file_watcher.py @@ -14,7 +14,7 @@ from openfeature.contrib.provider.flagd.resolvers.process.flags import FlagStore from openfeature.evaluation_context import EvaluationContext from openfeature.event import ProviderEventDetails -from openfeature.exception import ParseError, ProviderNotReadyError +from openfeature.exception import ParseError, ProviderNotReadyError, ErrorCode logger = logging.getLogger("openfeature.contrib") @@ -76,8 +76,9 @@ def safe_load_data(self) -> None: self.handle_error("Could not parse JSON flag data from file") except yaml.error.YAMLError: self.handle_error("Could not parse YAML flag data from file") - except ParseError: - self.handle_error("Could not parse flag data using flagd syntax") + except ParseError as e: + self.handle_error("Could not parse flag data using flagd syntax: " + ( + "no error message provided" if e is None or e.error_message is None else e.error_message)) except Exception: self.handle_error("Could not read flags from file") @@ -104,4 +105,4 @@ def _load_data(self, modified_time: typing.Optional[float] = None) -> None: def handle_error(self, error_message: str) -> None: logger.exception(error_message) self.should_emit_ready_on_success = True - self.emit_provider_error(ProviderEventDetails(message=error_message)) + self.emit_provider_error(ProviderEventDetails(message=error_message, error_code=ErrorCode.PARSE_ERROR)) diff --git a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/flags.py b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/flags.py index d8f93b36..b5a34c41 100644 --- a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/flags.py +++ b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/flags.py @@ -7,6 +7,19 @@ from openfeature.exception import ParseError +def _validate_metadata(key, value): + if key is None: + raise ParseError("Metadata key must be set") + elif not isinstance(key, str): + raise ParseError("Metadata key " + str(key) + " must be of type str, but is " + str(type(key))) + if value is None: + raise ParseError("Metadata value for key " + str(key) + " must be set") + elif not isinstance(value, typing.Union[float, int, str, bool]): + raise ParseError("Metadata value " + str(value) + + " for key " + str(key) + + " must be of type float, int, str or bool, but is " + str(type(value))) + + class FlagStore: def __init__( self, @@ -16,12 +29,14 @@ def __init__( ): self.emit_provider_configuration_changed = emit_provider_configuration_changed self.flags: typing.Mapping[str, Flag] = {} + self.flag_set_metadata: typing.Mapping[str, typing.Union[float, int, str, bool]] = {} def get_flag(self, key: str) -> typing.Optional["Flag"]: return self.flags.get(key) def update(self, flags_data: dict) -> None: flags = flags_data.get("flags", {}) + metadata = flags_data.get("metadata", {}) evaluators: typing.Optional[dict] = flags_data.get("$evaluators") if evaluators: transposed = json.dumps(flags) @@ -33,10 +48,16 @@ def update(self, flags_data: dict) -> None: if not isinstance(flags, dict): raise ParseError("`flags` key of configuration must be a dictionary") + if not isinstance(metadata, dict): + raise ParseError("`metadata` key of configuration must be a dictionary") + for key, value in metadata.items(): + _validate_metadata(key, value) + self.flags = {key: Flag.from_dict(key, data) for key, data in flags.items()} + self.flag_set_metadata = metadata self.emit_provider_configuration_changed( - ProviderEventDetails(flags_changed=list(self.flags.keys())) + ProviderEventDetails(flags_changed=list(self.flags.keys()), metadata=metadata) ) @@ -47,6 +68,7 @@ class Flag: variants: typing.Mapping[str, typing.Any] default_variant: typing.Union[bool, str] targeting: typing.Optional[dict] = None + metadata: typing.Optional[typing.Mapping[str, typing.Union[float, int, str, bool]]] = None def __post_init__(self) -> None: if not self.state or not isinstance(self.state, str): @@ -66,6 +88,13 @@ def __post_init__(self) -> None: if self.default_variant not in self.variants: raise ParseError("Default variant does not match set of variants") + if self.metadata: + if not isinstance(self.metadata, dict): + raise ParseError("Flag metadata is not a valid json object") + for key, value in self.metadata.items(): + _validate_metadata(key, value) + + @classmethod def from_dict(cls, key: str, data: dict) -> "Flag": if "defaultVariant" in data: @@ -79,6 +108,8 @@ def from_dict(cls, key: str, data: dict) -> "Flag": try: flag = cls(key=key, **data) return flag + except ParseError as parseError: + raise parseError except Exception as err: raise ParseError from err diff --git a/providers/openfeature-provider-flagd/tests/e2e/step/flag_step.py b/providers/openfeature-provider-flagd/tests/e2e/step/flag_step.py index cc4386e4..db5e8b38 100644 --- a/providers/openfeature-provider-flagd/tests/e2e/step/flag_step.py +++ b/providers/openfeature-provider-flagd/tests/e2e/step/flag_step.py @@ -94,3 +94,18 @@ def resolve_details_reason( reason: str, ): assert_equal(details.reason, Reason(reason)) + + +@then( + parsers.cfparse("the resolved metadata should contain") +) +def step_impl(details: FlagEvaluationDetails[JsonPrimitive], datatable): + assert_equal(len(details.flag_metadata), len(datatable) - 1) # skip table header + for i in range(1, len(datatable)): + key, metadata_type, expected = datatable[i] + assert_equal(details.flag_metadata[key], type_cast[metadata_type](expected)) + + +@then("the resolved metadata is empty") +def step_impl(details: FlagEvaluationDetails[JsonPrimitive]): + assert_equal(len(details.flag_metadata), 0) \ No newline at end of file diff --git a/providers/openfeature-provider-flagd/tests/e2e/step/provider_steps.py b/providers/openfeature-provider-flagd/tests/e2e/step/provider_steps.py index 76d739d9..f3a9c008 100644 --- a/providers/openfeature-provider-flagd/tests/e2e/step/provider_steps.py +++ b/providers/openfeature-provider-flagd/tests/e2e/step/provider_steps.py @@ -41,7 +41,7 @@ def setup_provider_old( def get_default_options_for_provider( - provider_type: str, resolver_type: ResolverType, container + provider_type: str, resolver_type: ResolverType, container, option_values: dict ) -> tuple[dict, bool]: launchpad = "default" t = TestProviderType(provider_type) @@ -68,9 +68,16 @@ def get_default_options_for_provider( return options, True if resolver_type == ResolverType.FILE: - options["offline_flag_source_path"] = os.path.join( - container.flagDir.name, "allFlags.json" - ) + if "selector" in option_values: + path = option_values["selector"] + path = path.replace("rawflags/", "") + options["offline_flag_source_path"] = os.path.join( + "..", "openfeature", "test-harness", "flags", path + ) + else: + options["offline_flag_source_path"] = os.path.join( + container.flagDir.name, "allFlags.json" + ) requests.post( f"{container.get_launchpad_url()}/start?config={launchpad}", timeout=1 @@ -89,7 +96,7 @@ def setup_provider( option_values: dict, ) -> OpenFeatureClient: default_options, wait = get_default_options_for_provider( - provider_type, resolver_type, container + provider_type, resolver_type, container, option_values ) combined_options = {**default_options, **option_values} diff --git a/providers/openfeature-provider-flagd/tests/flags/basic-flag-combined-metadata.json b/providers/openfeature-provider-flagd/tests/flags/basic-flag-combined-metadata.json new file mode 100644 index 00000000..75016630 --- /dev/null +++ b/providers/openfeature-provider-flagd/tests/flags/basic-flag-combined-metadata.json @@ -0,0 +1,29 @@ +{ + "flags": { + "basic-flag": { + "state": "ENABLED", + "variants": { + "true": true, + "false": false + }, + "defaultVariant": "false", + "targeting": {}, + "metadata": { + "string": "a", + "integer": 1, + "float": 1.2, + "bool": true + } + } + }, + "metadata": { + "string": "b", + "integer": 2, + "float": 2.2, + "bool": false, + "flag-set-string": "c", + "flag-set-integer": 3, + "flag-set-float": 3.2, + "flag-set-bool": false + } +} \ No newline at end of file diff --git a/providers/openfeature-provider-flagd/tests/flags/basic-flag-metadata.json b/providers/openfeature-provider-flagd/tests/flags/basic-flag-metadata.json new file mode 100644 index 00000000..208bd18c --- /dev/null +++ b/providers/openfeature-provider-flagd/tests/flags/basic-flag-metadata.json @@ -0,0 +1,19 @@ +{ + "flags": { + "basic-flag": { + "state": "ENABLED", + "variants": { + "true": true, + "false": false + }, + "defaultVariant": "false", + "targeting": {}, + "metadata": { + "string": "a", + "integer": 1, + "float": 1.2, + "bool": true + } + } + } +} \ No newline at end of file diff --git a/providers/openfeature-provider-flagd/tests/flags/basic-flag-set-metadata.json b/providers/openfeature-provider-flagd/tests/flags/basic-flag-set-metadata.json new file mode 100644 index 00000000..ed111e0a --- /dev/null +++ b/providers/openfeature-provider-flagd/tests/flags/basic-flag-set-metadata.json @@ -0,0 +1,19 @@ +{ + "flags": { + "basic-flag": { + "state": "ENABLED", + "variants": { + "true": true, + "false": false + }, + "defaultVariant": "false", + "targeting": {} + } + }, + "metadata": { + "string": "a", + "integer": 1, + "float": 1.2, + "bool": true + } +} \ No newline at end of file diff --git a/providers/openfeature-provider-flagd/tests/flags/invalid-flag-metadata-list.json b/providers/openfeature-provider-flagd/tests/flags/invalid-flag-metadata-list.json new file mode 100644 index 00000000..89176889 --- /dev/null +++ b/providers/openfeature-provider-flagd/tests/flags/invalid-flag-metadata-list.json @@ -0,0 +1,14 @@ +{ + "flags": { + "basic-flag": { + "state": "ENABLED", + "variants": { + "true": true, + "false": false + }, + "defaultVariant": "false", + "targeting": {}, + "metadata": ["a"] + } + } +} \ No newline at end of file diff --git a/providers/openfeature-provider-flagd/tests/flags/invalid-flag-metadata.json b/providers/openfeature-provider-flagd/tests/flags/invalid-flag-metadata.json new file mode 100644 index 00000000..bbbd6144 --- /dev/null +++ b/providers/openfeature-provider-flagd/tests/flags/invalid-flag-metadata.json @@ -0,0 +1,24 @@ +{ + "flags": { + "basic-flag": { + "state": "ENABLED", + "variants": { + "true": true, + "false": false + }, + "defaultVariant": "false", + "targeting": {}, + "metadata": { + "string": { + "a": "a" + }, + "integer": 1, + "float": 1.2, + "bool": true + } + } + }, + "metadata": { + "bool": true + } +} \ No newline at end of file diff --git a/providers/openfeature-provider-flagd/tests/flags/invalid-flag-set-metadata-list.json b/providers/openfeature-provider-flagd/tests/flags/invalid-flag-set-metadata-list.json new file mode 100644 index 00000000..61950ce7 --- /dev/null +++ b/providers/openfeature-provider-flagd/tests/flags/invalid-flag-set-metadata-list.json @@ -0,0 +1,14 @@ +{ + "flags": { + "basic-flag": { + "state": "ENABLED", + "variants": { + "true": true, + "false": false + }, + "defaultVariant": "false", + "targeting": {} + } + }, + "metadata": ["a"] +} \ No newline at end of file diff --git a/providers/openfeature-provider-flagd/tests/flags/invalid-flag-set-metadata.json b/providers/openfeature-provider-flagd/tests/flags/invalid-flag-set-metadata.json new file mode 100644 index 00000000..800733a8 --- /dev/null +++ b/providers/openfeature-provider-flagd/tests/flags/invalid-flag-set-metadata.json @@ -0,0 +1,21 @@ +{ + "flags": { + "basic-flag": { + "state": "ENABLED", + "variants": { + "true": true, + "false": false + }, + "defaultVariant": "false", + "targeting": {} + } + }, + "metadata": { + "string": { + "a": "a" + }, + "integer": 1, + "float": 1.2, + "bool": true + } +} \ No newline at end of file diff --git a/providers/openfeature-provider-flagd/tests/test_file_store.py b/providers/openfeature-provider-flagd/tests/test_file_store.py index dea890ec..4d88cbdf 100644 --- a/providers/openfeature-provider-flagd/tests/test_file_store.py +++ b/providers/openfeature-provider-flagd/tests/test_file_store.py @@ -44,3 +44,36 @@ def test_file_load(file_name: str): assert flag is not None assert isinstance(flag, Flag) + + flag_set_metadata = flag_store.flag_set_metadata + + assert flag_set_metadata is not None + assert isinstance(flag_set_metadata, dict) + assert len(flag_set_metadata) == 0 + + +def test_file_load_metadata(): + emit_provider_configuration_changed = Mock() + emit_provider_ready = Mock() + emit_provider_error = Mock() + flag_store = FlagStore(emit_provider_configuration_changed) + path = os.path.abspath(os.path.join(os.path.dirname(__file__), "./flags/")) + file_watcher = FileWatcher( + Config( + offline_flag_source_path=f"{path}/basic-flag-set-metadata.json", + ), + flag_store, + emit_provider_ready, + emit_provider_error, + ) + file_watcher.initialize(None) + + flag_set_metadata = flag_store.flag_set_metadata + + assert flag_set_metadata is not None + assert isinstance(flag_set_metadata, dict) + assert len(flag_set_metadata) == 4 + assert flag_set_metadata["string"] == "a" + assert flag_set_metadata["integer"] == 1 + assert flag_set_metadata["float"] == 1.2 + assert flag_set_metadata["bool"] == True \ No newline at end of file diff --git a/providers/openfeature-provider-flagd/tests/test_metadata.py b/providers/openfeature-provider-flagd/tests/test_metadata.py new file mode 100644 index 00000000..ea567840 --- /dev/null +++ b/providers/openfeature-provider-flagd/tests/test_metadata.py @@ -0,0 +1,101 @@ +import os +import time +from time import sleep + +import pytest +from openfeature.event import EventDetails, ProviderEvent +from openfeature.exception import ErrorCode + +from openfeature import api +from openfeature.contrib.provider.flagd import FlagdProvider +from openfeature.contrib.provider.flagd.config import ResolverType + + +def create_client(file_name): + path = os.path.abspath(os.path.join(os.path.dirname(__file__), "./flags/")) + provider = FlagdProvider( + resolver_type=ResolverType.FILE, + offline_flag_source_path=f"{path}/{file_name}", + ) + + api.set_provider(provider) + return api.get_client() + + +def test_should_load_flag_set_metadata(): + client = create_client("basic-flag-set-metadata.json") + res = client.get_boolean_details("basic-flag", False) + + assert res.flag_metadata is not None + assert isinstance(res.flag_metadata, dict) + assert len(res.flag_metadata) == 4 + assert res.flag_metadata["string"] == "a" + assert res.flag_metadata["integer"] == 1 + assert res.flag_metadata["float"] == 1.2 + assert res.flag_metadata["bool"] == True + + +def test_should_load_flag_metadata(): + client = create_client("basic-flag-metadata.json") + res = client.get_boolean_details("basic-flag", False) + + assert res.flag_metadata is not None + assert isinstance(res.flag_metadata, dict) + assert len(res.flag_metadata) == 4 + assert res.flag_metadata["string"] == "a" + assert res.flag_metadata["integer"] == 1 + assert res.flag_metadata["float"] == 1.2 + assert res.flag_metadata["bool"] == True + + +def test_should_load_flag_combined_metadata(): + client = create_client("basic-flag-combined-metadata.json") + res = client.get_boolean_details("basic-flag", False) + + assert res.flag_metadata is not None + assert isinstance(res.flag_metadata, dict) + assert len(res.flag_metadata) == 8 + assert res.flag_metadata["string"] == "a" + assert res.flag_metadata["integer"] == 1 + assert res.flag_metadata["float"] == 1.2 + assert res.flag_metadata["bool"] == True + assert res.flag_metadata["flag-set-string"] == "c" + assert res.flag_metadata["flag-set-integer"] == 3 + assert res.flag_metadata["flag-set-float"] == 3.2 + assert res.flag_metadata["flag-set-bool"] == False + +class Channel: + parse_error_received = False + +def create_error_handler(): + channel = Channel() + def error_handler(details: EventDetails): + nonlocal channel + if details.error_code == ErrorCode.PARSE_ERROR: + channel.parse_error_received = True + + return error_handler, channel + +@pytest.mark.parametrize( + "file_name", + [ + "invalid-flag-set-metadata.json", + "invalid-flag-set-metadata-list.json", + "invalid-flag-metadata.json", + "invalid-flag-metadata-list.json", + ], +) +def test_invalid_flag_set_metadata(file_name): + error_handler, channel = create_error_handler() + + client = create_client(file_name) + client.add_handler(ProviderEvent.PROVIDER_ERROR, error_handler) + + # keep the test thread alive + max_timeout = 2 + start = time.time() + while not channel.parse_error_received: + now = time.time() + if now - start > max_timeout: + assert False + sleep(0.01) From a76bcf626d26ecd4eebeaa1d878749c89e2893ae Mon Sep 17 00:00:00 2001 From: "christian.lutnik" Date: Tue, 18 Mar 2025 12:04:11 +0100 Subject: [PATCH 02/30] reformatting Signed-off-by: christian.lutnik --- .../provider/flagd/resolvers/in_process.py | 16 ++++++---- .../process/connector/file_watcher.py | 22 ++++++++++---- .../provider/flagd/resolvers/process/flags.py | 30 ++++++++++++++----- .../tests/e2e/step/flag_step.py | 12 ++++---- .../tests/test_file_store.py | 2 +- .../tests/test_metadata.py | 13 ++++---- 6 files changed, 64 insertions(+), 31 deletions(-) diff --git a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/in_process.py b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/in_process.py index a2d4a40b..067f1c0d 100644 --- a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/in_process.py +++ b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/in_process.py @@ -19,7 +19,7 @@ def _merge_metadata( flag_metadata: typing.Mapping[str, typing.Union[float, int, str, bool]], - flag_set_metadata: typing.Mapping[str, typing.Union[float, int, str, bool]] + flag_set_metadata: typing.Mapping[str, typing.Union[float, int, str, bool]], ) -> typing.Mapping[str, typing.Union[float, int, str, bool]]: metadata = {} if flag_set_metadata is not None: @@ -122,17 +122,23 @@ def _resolve( metadata = _merge_metadata(flag.metadata, self.flag_store.flag_set_metadata) if flag.state == "DISABLED": - return FlagResolutionDetails(default_value, flag_metadata=metadata, reason=Reason.DISABLED) + return FlagResolutionDetails( + default_value, flag_metadata=metadata, reason=Reason.DISABLED + ) if not flag.targeting: variant, value = flag.default - return FlagResolutionDetails(value, variant=variant, flag_metadata=metadata, reason=Reason.STATIC) + return FlagResolutionDetails( + value, variant=variant, flag_metadata=metadata, reason=Reason.STATIC + ) variant = targeting(flag.key, flag.targeting, evaluation_context) if variant is None: variant, value = flag.default - return FlagResolutionDetails(value, variant=variant, flag_metadata=metadata, reason=Reason.DEFAULT) + return FlagResolutionDetails( + value, variant=variant, flag_metadata=metadata, reason=Reason.DEFAULT + ) if not isinstance(variant, (str, bool)): raise ParseError( "Parsed JSONLogic targeting did not return a string or bool" @@ -146,5 +152,5 @@ def _resolve( value, variant=variant, reason=Reason.TARGETING_MATCH, - flag_metadata=metadata + flag_metadata=metadata, ) diff --git a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/connector/file_watcher.py b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/connector/file_watcher.py index 0cd99c11..084b9e17 100644 --- a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/connector/file_watcher.py +++ b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/connector/file_watcher.py @@ -6,15 +6,15 @@ import typing import yaml +from openfeature.evaluation_context import EvaluationContext +from openfeature.event import ProviderEventDetails +from openfeature.exception import ParseError, ProviderNotReadyError, ErrorCode from openfeature.contrib.provider.flagd.config import Config from openfeature.contrib.provider.flagd.resolvers.process.connector import ( FlagStateConnector, ) from openfeature.contrib.provider.flagd.resolvers.process.flags import FlagStore -from openfeature.evaluation_context import EvaluationContext -from openfeature.event import ProviderEventDetails -from openfeature.exception import ParseError, ProviderNotReadyError, ErrorCode logger = logging.getLogger("openfeature.contrib") @@ -77,8 +77,14 @@ def safe_load_data(self) -> None: except yaml.error.YAMLError: self.handle_error("Could not parse YAML flag data from file") except ParseError as e: - self.handle_error("Could not parse flag data using flagd syntax: " + ( - "no error message provided" if e is None or e.error_message is None else e.error_message)) + self.handle_error( + "Could not parse flag data using flagd syntax: " + + ( + "no error message provided" + if e is None or e.error_message is None + else e.error_message + ) + ) except Exception: self.handle_error("Could not read flags from file") @@ -105,4 +111,8 @@ def _load_data(self, modified_time: typing.Optional[float] = None) -> None: def handle_error(self, error_message: str) -> None: logger.exception(error_message) self.should_emit_ready_on_success = True - self.emit_provider_error(ProviderEventDetails(message=error_message, error_code=ErrorCode.PARSE_ERROR)) + self.emit_provider_error( + ProviderEventDetails( + message=error_message, error_code=ErrorCode.PARSE_ERROR + ) + ) diff --git a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/flags.py b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/flags.py index b5a34c41..40023dba 100644 --- a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/flags.py +++ b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/flags.py @@ -11,13 +11,23 @@ def _validate_metadata(key, value): if key is None: raise ParseError("Metadata key must be set") elif not isinstance(key, str): - raise ParseError("Metadata key " + str(key) + " must be of type str, but is " + str(type(key))) + raise ParseError( + "Metadata key " + + str(key) + + " must be of type str, but is " + + str(type(key)) + ) if value is None: raise ParseError("Metadata value for key " + str(key) + " must be set") elif not isinstance(value, typing.Union[float, int, str, bool]): - raise ParseError("Metadata value " + str(value) + - " for key " + str(key) + - " must be of type float, int, str or bool, but is " + str(type(value))) + raise ParseError( + "Metadata value " + + str(value) + + " for key " + + str(key) + + " must be of type float, int, str or bool, but is " + + str(type(value)) + ) class FlagStore: @@ -29,7 +39,9 @@ def __init__( ): self.emit_provider_configuration_changed = emit_provider_configuration_changed self.flags: typing.Mapping[str, Flag] = {} - self.flag_set_metadata: typing.Mapping[str, typing.Union[float, int, str, bool]] = {} + self.flag_set_metadata: typing.Mapping[ + str, typing.Union[float, int, str, bool] + ] = {} def get_flag(self, key: str) -> typing.Optional["Flag"]: return self.flags.get(key) @@ -57,7 +69,9 @@ def update(self, flags_data: dict) -> None: self.flag_set_metadata = metadata self.emit_provider_configuration_changed( - ProviderEventDetails(flags_changed=list(self.flags.keys()), metadata=metadata) + ProviderEventDetails( + flags_changed=list(self.flags.keys()), metadata=metadata + ) ) @@ -68,7 +82,9 @@ class Flag: variants: typing.Mapping[str, typing.Any] default_variant: typing.Union[bool, str] targeting: typing.Optional[dict] = None - metadata: typing.Optional[typing.Mapping[str, typing.Union[float, int, str, bool]]] = None + metadata: typing.Optional[ + typing.Mapping[str, typing.Union[float, int, str, bool]] + ] = None def __post_init__(self) -> None: if not self.state or not isinstance(self.state, str): diff --git a/providers/openfeature-provider-flagd/tests/e2e/step/flag_step.py b/providers/openfeature-provider-flagd/tests/e2e/step/flag_step.py index db5e8b38..e0ff97ce 100644 --- a/providers/openfeature-provider-flagd/tests/e2e/step/flag_step.py +++ b/providers/openfeature-provider-flagd/tests/e2e/step/flag_step.py @@ -96,16 +96,14 @@ def resolve_details_reason( assert_equal(details.reason, Reason(reason)) -@then( - parsers.cfparse("the resolved metadata should contain") -) -def step_impl(details: FlagEvaluationDetails[JsonPrimitive], datatable): - assert_equal(len(details.flag_metadata), len(datatable) - 1) # skip table header +@then(parsers.cfparse("the resolved metadata should contain")) +def metadata_contains(details: FlagEvaluationDetails[JsonPrimitive], datatable): + assert_equal(len(details.flag_metadata), len(datatable) - 1) # skip table header for i in range(1, len(datatable)): key, metadata_type, expected = datatable[i] assert_equal(details.flag_metadata[key], type_cast[metadata_type](expected)) @then("the resolved metadata is empty") -def step_impl(details: FlagEvaluationDetails[JsonPrimitive]): - assert_equal(len(details.flag_metadata), 0) \ No newline at end of file +def empty_metadata(details: FlagEvaluationDetails[JsonPrimitive]): + assert_equal(len(details.flag_metadata), 0) diff --git a/providers/openfeature-provider-flagd/tests/test_file_store.py b/providers/openfeature-provider-flagd/tests/test_file_store.py index 4d88cbdf..30b93477 100644 --- a/providers/openfeature-provider-flagd/tests/test_file_store.py +++ b/providers/openfeature-provider-flagd/tests/test_file_store.py @@ -76,4 +76,4 @@ def test_file_load_metadata(): assert flag_set_metadata["string"] == "a" assert flag_set_metadata["integer"] == 1 assert flag_set_metadata["float"] == 1.2 - assert flag_set_metadata["bool"] == True \ No newline at end of file + assert flag_set_metadata["bool"] diff --git a/providers/openfeature-provider-flagd/tests/test_metadata.py b/providers/openfeature-provider-flagd/tests/test_metadata.py index ea567840..131aa45b 100644 --- a/providers/openfeature-provider-flagd/tests/test_metadata.py +++ b/providers/openfeature-provider-flagd/tests/test_metadata.py @@ -32,7 +32,7 @@ def test_should_load_flag_set_metadata(): assert res.flag_metadata["string"] == "a" assert res.flag_metadata["integer"] == 1 assert res.flag_metadata["float"] == 1.2 - assert res.flag_metadata["bool"] == True + assert res.flag_metadata["bool"] def test_should_load_flag_metadata(): @@ -45,7 +45,7 @@ def test_should_load_flag_metadata(): assert res.flag_metadata["string"] == "a" assert res.flag_metadata["integer"] == 1 assert res.flag_metadata["float"] == 1.2 - assert res.flag_metadata["bool"] == True + assert res.flag_metadata["bool"] def test_should_load_flag_combined_metadata(): @@ -58,17 +58,19 @@ def test_should_load_flag_combined_metadata(): assert res.flag_metadata["string"] == "a" assert res.flag_metadata["integer"] == 1 assert res.flag_metadata["float"] == 1.2 - assert res.flag_metadata["bool"] == True + assert res.flag_metadata["bool"] assert res.flag_metadata["flag-set-string"] == "c" assert res.flag_metadata["flag-set-integer"] == 3 assert res.flag_metadata["flag-set-float"] == 3.2 - assert res.flag_metadata["flag-set-bool"] == False + assert not res.flag_metadata["flag-set-bool"] + class Channel: parse_error_received = False def create_error_handler(): channel = Channel() + def error_handler(details: EventDetails): nonlocal channel if details.error_code == ErrorCode.PARSE_ERROR: @@ -76,6 +78,7 @@ def error_handler(details: EventDetails): return error_handler, channel + @pytest.mark.parametrize( "file_name", [ @@ -97,5 +100,5 @@ def test_invalid_flag_set_metadata(file_name): while not channel.parse_error_received: now = time.time() if now - start > max_timeout: - assert False + raise AssertionError() sleep(0.01) From eab778b1cbe84e4caaaeb7a33c8b016ea9e679e8 Mon Sep 17 00:00:00 2001 From: "christian.lutnik" Date: Tue, 18 Mar 2025 12:07:48 +0100 Subject: [PATCH 03/30] fix type errors Signed-off-by: christian.lutnik --- .../contrib/provider/flagd/resolvers/process/flags.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/flags.py b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/flags.py index 40023dba..5e3ee628 100644 --- a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/flags.py +++ b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/flags.py @@ -7,7 +7,7 @@ from openfeature.exception import ParseError -def _validate_metadata(key, value): +def _validate_metadata(key: str, value: typing.Union[float, int, str, bool]): if key is None: raise ParseError("Metadata key must be set") elif not isinstance(key, str): @@ -19,7 +19,7 @@ def _validate_metadata(key, value): ) if value is None: raise ParseError("Metadata value for key " + str(key) + " must be set") - elif not isinstance(value, typing.Union[float, int, str, bool]): + elif not isinstance(value, float | int | str | bool): raise ParseError( "Metadata value " + str(value) + From 390144dce97e0dfa6c0c20fb1628001646115e3d Mon Sep 17 00:00:00 2001 From: "christian.lutnik" Date: Tue, 18 Mar 2025 12:23:26 +0100 Subject: [PATCH 04/30] fix type errors and fmt Signed-off-by: christian.lutnik --- .../contrib/provider/flagd/resolvers/in_process.py | 8 ++++---- .../resolvers/process/connector/file_watcher.py | 6 +++--- .../provider/flagd/resolvers/process/flags.py | 13 +++++++++---- .../tests/test_file_store.py | 4 ++-- .../tests/test_metadata.py | 4 ++-- 5 files changed, 20 insertions(+), 15 deletions(-) diff --git a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/in_process.py b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/in_process.py index 067f1c0d..a9f89e2a 100644 --- a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/in_process.py +++ b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/in_process.py @@ -18,13 +18,13 @@ def _merge_metadata( - flag_metadata: typing.Mapping[str, typing.Union[float, int, str, bool]], - flag_set_metadata: typing.Mapping[str, typing.Union[float, int, str, bool]], + flag_metadata: typing.Mapping[str, typing.Union[float, int, str, bool]] | None, + flag_set_metadata: typing.Mapping[str, typing.Union[float, int, str, bool]] | None, ) -> typing.Mapping[str, typing.Union[float, int, str, bool]]: metadata = {} + if flag_set_metadata is not None: - for key, value in flag_set_metadata.items(): - metadata[key] = value + metadata = { key: flag_set_metadata[key] for key in flag_set_metadata.keys() } if flag_metadata is not None: for key, value in flag_metadata.items(): diff --git a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/connector/file_watcher.py b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/connector/file_watcher.py index 084b9e17..8fa9b648 100644 --- a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/connector/file_watcher.py +++ b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/connector/file_watcher.py @@ -6,15 +6,15 @@ import typing import yaml -from openfeature.evaluation_context import EvaluationContext -from openfeature.event import ProviderEventDetails -from openfeature.exception import ParseError, ProviderNotReadyError, ErrorCode from openfeature.contrib.provider.flagd.config import Config from openfeature.contrib.provider.flagd.resolvers.process.connector import ( FlagStateConnector, ) from openfeature.contrib.provider.flagd.resolvers.process.flags import FlagStore +from openfeature.evaluation_context import EvaluationContext +from openfeature.event import ProviderEventDetails +from openfeature.exception import ParseError, ProviderNotReadyError, ErrorCode logger = logging.getLogger("openfeature.contrib") diff --git a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/flags.py b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/flags.py index 5e3ee628..0289cc03 100644 --- a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/flags.py +++ b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/flags.py @@ -7,7 +7,7 @@ from openfeature.exception import ParseError -def _validate_metadata(key: str, value: typing.Union[float, int, str, bool]): +def _validate_metadata(key: str, value: typing.Union[float, int, str, bool]) -> None: if key is None: raise ParseError("Metadata key must be set") elif not isinstance(key, str): @@ -19,11 +19,16 @@ def _validate_metadata(key: str, value: typing.Union[float, int, str, bool]): ) if value is None: raise ParseError("Metadata value for key " + str(key) + " must be set") - elif not isinstance(value, float | int | str | bool): + elif not ( + isinstance(value, float) + or isinstance(value, int) + or isinstance(value, str) + or isinstance(value, bool) + ): raise ParseError( "Metadata value " - + str(value) + - " for key " + + str(value) + + " for key " + str(key) + " must be of type float, int, str or bool, but is " + str(type(value)) diff --git a/providers/openfeature-provider-flagd/tests/test_file_store.py b/providers/openfeature-provider-flagd/tests/test_file_store.py index 30b93477..7af4ccfd 100644 --- a/providers/openfeature-provider-flagd/tests/test_file_store.py +++ b/providers/openfeature-provider-flagd/tests/test_file_store.py @@ -74,6 +74,6 @@ def test_file_load_metadata(): assert isinstance(flag_set_metadata, dict) assert len(flag_set_metadata) == 4 assert flag_set_metadata["string"] == "a" - assert flag_set_metadata["integer"] == 1 - assert flag_set_metadata["float"] == 1.2 + assert flag_set_metadata["integer"] == 1 + assert flag_set_metadata["float"] == 1.2 assert flag_set_metadata["bool"] diff --git a/providers/openfeature-provider-flagd/tests/test_metadata.py b/providers/openfeature-provider-flagd/tests/test_metadata.py index 131aa45b..afe1f296 100644 --- a/providers/openfeature-provider-flagd/tests/test_metadata.py +++ b/providers/openfeature-provider-flagd/tests/test_metadata.py @@ -3,12 +3,12 @@ from time import sleep import pytest -from openfeature.event import EventDetails, ProviderEvent -from openfeature.exception import ErrorCode from openfeature import api from openfeature.contrib.provider.flagd import FlagdProvider from openfeature.contrib.provider.flagd.config import ResolverType +from openfeature.event import EventDetails, ProviderEvent +from openfeature.exception import ErrorCode def create_client(file_name): From 7bf74e1b44f9f0ab86a7f08ac2c0b3e90ea9efd2 Mon Sep 17 00:00:00 2001 From: "christian.lutnik" Date: Tue, 18 Mar 2025 12:35:16 +0100 Subject: [PATCH 05/30] fix type errors and fmt Signed-off-by: christian.lutnik --- .../contrib/provider/flagd/resolvers/in_process.py | 6 +++--- .../flagd/resolvers/process/connector/file_watcher.py | 2 +- .../contrib/provider/flagd/resolvers/process/flags.py | 7 +------ .../openfeature-provider-flagd/tests/test_metadata.py | 1 + 4 files changed, 6 insertions(+), 10 deletions(-) diff --git a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/in_process.py b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/in_process.py index a9f89e2a..36117158 100644 --- a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/in_process.py +++ b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/in_process.py @@ -18,13 +18,13 @@ def _merge_metadata( - flag_metadata: typing.Mapping[str, typing.Union[float, int, str, bool]] | None, - flag_set_metadata: typing.Mapping[str, typing.Union[float, int, str, bool]] | None, + flag_metadata: typing.Optional[typing.Mapping[str, typing.Union[float, int, str, bool]]], + flag_set_metadata: typing.Optional[typing.Mapping[str, typing.Union[float, int, str, bool]]], ) -> typing.Mapping[str, typing.Union[float, int, str, bool]]: metadata = {} if flag_set_metadata is not None: - metadata = { key: flag_set_metadata[key] for key in flag_set_metadata.keys() } + metadata = { key: value for key, value in flag_set_metadata } if flag_metadata is not None: for key, value in flag_metadata.items(): diff --git a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/connector/file_watcher.py b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/connector/file_watcher.py index 8fa9b648..6149dc1d 100644 --- a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/connector/file_watcher.py +++ b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/connector/file_watcher.py @@ -14,7 +14,7 @@ from openfeature.contrib.provider.flagd.resolvers.process.flags import FlagStore from openfeature.evaluation_context import EvaluationContext from openfeature.event import ProviderEventDetails -from openfeature.exception import ParseError, ProviderNotReadyError, ErrorCode +from openfeature.exception import ErrorCode, ParseError, ProviderNotReadyError logger = logging.getLogger("openfeature.contrib") diff --git a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/flags.py b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/flags.py index 0289cc03..b7715695 100644 --- a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/flags.py +++ b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/flags.py @@ -19,12 +19,7 @@ def _validate_metadata(key: str, value: typing.Union[float, int, str, bool]) -> ) if value is None: raise ParseError("Metadata value for key " + str(key) + " must be set") - elif not ( - isinstance(value, float) - or isinstance(value, int) - or isinstance(value, str) - or isinstance(value, bool) - ): + elif not isinstance(value, (float, int, str, bool)): raise ParseError( "Metadata value " + str(value) diff --git a/providers/openfeature-provider-flagd/tests/test_metadata.py b/providers/openfeature-provider-flagd/tests/test_metadata.py index afe1f296..cce0ead7 100644 --- a/providers/openfeature-provider-flagd/tests/test_metadata.py +++ b/providers/openfeature-provider-flagd/tests/test_metadata.py @@ -68,6 +68,7 @@ def test_should_load_flag_combined_metadata(): class Channel: parse_error_received = False + def create_error_handler(): channel = Channel() From ed1f91f3f241f14d794169e0ac4f3c6f52e3fc4f Mon Sep 17 00:00:00 2001 From: "christian.lutnik" Date: Tue, 18 Mar 2025 12:41:12 +0100 Subject: [PATCH 06/30] fix format Signed-off-by: christian.lutnik --- .../contrib/provider/flagd/resolvers/in_process.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/in_process.py b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/in_process.py index 36117158..252473a8 100644 --- a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/in_process.py +++ b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/in_process.py @@ -18,13 +18,17 @@ def _merge_metadata( - flag_metadata: typing.Optional[typing.Mapping[str, typing.Union[float, int, str, bool]]], - flag_set_metadata: typing.Optional[typing.Mapping[str, typing.Union[float, int, str, bool]]], + flag_metadata: typing.Optional[ + typing.Mapping[str, typing.Union[float, int, str, bool]] + ], + flag_set_metadata: typing.Optional[ + typing.Mapping[str, typing.Union[float, int, str, bool]] + ], ) -> typing.Mapping[str, typing.Union[float, int, str, bool]]: metadata = {} if flag_set_metadata is not None: - metadata = { key: value for key, value in flag_set_metadata } + metadata = dict(flag_set_metadata) if flag_metadata is not None: for key, value in flag_metadata.items(): From a6d8715d9853cd20d488d22d88ccba6f81da5060 Mon Sep 17 00:00:00 2001 From: "christian.lutnik" Date: Tue, 18 Mar 2025 12:58:11 +0100 Subject: [PATCH 07/30] fix format, add tests Signed-off-by: christian.lutnik --- .../provider/flagd/resolvers/process/flags.py | 3 +- .../tests/test_metadata.py | 44 ++++++++++++++++++- 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/flags.py b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/flags.py index b7715695..041ac367 100644 --- a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/flags.py +++ b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/flags.py @@ -17,6 +17,8 @@ def _validate_metadata(key: str, value: typing.Union[float, int, str, bool]) -> + " must be of type str, but is " + str(type(key)) ) + elif not key: + raise ParseError("key must not be empty") if value is None: raise ParseError("Metadata value for key " + str(key) + " must be set") elif not isinstance(value, (float, int, str, bool)): @@ -110,7 +112,6 @@ def __post_init__(self) -> None: for key, value in self.metadata.items(): _validate_metadata(key, value) - @classmethod def from_dict(cls, key: str, data: dict) -> "Flag": if "defaultVariant" in data: diff --git a/providers/openfeature-provider-flagd/tests/test_metadata.py b/providers/openfeature-provider-flagd/tests/test_metadata.py index cce0ead7..44c6ca46 100644 --- a/providers/openfeature-provider-flagd/tests/test_metadata.py +++ b/providers/openfeature-provider-flagd/tests/test_metadata.py @@ -8,7 +8,9 @@ from openfeature.contrib.provider.flagd import FlagdProvider from openfeature.contrib.provider.flagd.config import ResolverType from openfeature.event import EventDetails, ProviderEvent -from openfeature.exception import ErrorCode +from openfeature.exception import ErrorCode, ParseError + +from openfeature.contrib.provider.flagd.resolvers.process.flags import _validate_metadata def create_client(file_name): @@ -103,3 +105,43 @@ def test_invalid_flag_set_metadata(file_name): if now - start > max_timeout: raise AssertionError() sleep(0.01) + + +def test_validate_metadata_with_none_key(): + try: + _validate_metadata(None, "a") + except ParseError: + return + raise AssertionError() + + +def test_validate_metadata_with_empty_key(): + try: + _validate_metadata("", "a") + except ParseError: + return + raise AssertionError() + + +def test_validate_metadata_with_non_string_key(): + try: + _validate_metadata(1, "a") + except ParseError: + return + raise AssertionError() + + +def test_validate_metadata_with_non_string_value(): + try: + _validate_metadata("a", []) + except ParseError: + return + raise AssertionError() + + +def test_validate_metadata_with_none_value(): + try: + _validate_metadata("a", None) + except ParseError: + return + raise AssertionError() From 3cc2048b94c11f8e04a53e643d1ba8a39c2f0f24 Mon Sep 17 00:00:00 2001 From: "christian.lutnik" Date: Tue, 18 Mar 2025 13:01:47 +0100 Subject: [PATCH 08/30] fix format Signed-off-by: christian.lutnik --- providers/openfeature-provider-flagd/tests/test_metadata.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/providers/openfeature-provider-flagd/tests/test_metadata.py b/providers/openfeature-provider-flagd/tests/test_metadata.py index 44c6ca46..1b51d25a 100644 --- a/providers/openfeature-provider-flagd/tests/test_metadata.py +++ b/providers/openfeature-provider-flagd/tests/test_metadata.py @@ -7,11 +7,12 @@ from openfeature import api from openfeature.contrib.provider.flagd import FlagdProvider from openfeature.contrib.provider.flagd.config import ResolverType +from openfeature.contrib.provider.flagd.resolvers.process.flags import ( + _validate_metadata +) from openfeature.event import EventDetails, ProviderEvent from openfeature.exception import ErrorCode, ParseError -from openfeature.contrib.provider.flagd.resolvers.process.flags import _validate_metadata - def create_client(file_name): path = os.path.abspath(os.path.join(os.path.dirname(__file__), "./flags/")) From c9e5ed7c2e2ca720e45679d0adc5fb37558c0fc8 Mon Sep 17 00:00:00 2001 From: "christian.lutnik" Date: Tue, 18 Mar 2025 13:03:31 +0100 Subject: [PATCH 09/30] fix format Signed-off-by: christian.lutnik --- providers/openfeature-provider-flagd/tests/test_metadata.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/providers/openfeature-provider-flagd/tests/test_metadata.py b/providers/openfeature-provider-flagd/tests/test_metadata.py index 1b51d25a..939af96f 100644 --- a/providers/openfeature-provider-flagd/tests/test_metadata.py +++ b/providers/openfeature-provider-flagd/tests/test_metadata.py @@ -8,7 +8,7 @@ from openfeature.contrib.provider.flagd import FlagdProvider from openfeature.contrib.provider.flagd.config import ResolverType from openfeature.contrib.provider.flagd.resolvers.process.flags import ( - _validate_metadata + _validate_metadata, ) from openfeature.event import EventDetails, ProviderEvent from openfeature.exception import ErrorCode, ParseError From 94967fbbaf4b02d2d84ce295b5ef369f323f4c3e Mon Sep 17 00:00:00 2001 From: "christian.lutnik" Date: Tue, 18 Mar 2025 13:56:50 +0100 Subject: [PATCH 10/30] switch to new version of flagd testbed Signed-off-by: christian.lutnik --- .gitmodules | 8 ++++---- .../openfeature-provider-flagd/openfeature/test-harness | 1 - 2 files changed, 4 insertions(+), 5 deletions(-) delete mode 160000 providers/openfeature-provider-flagd/openfeature/test-harness diff --git a/.gitmodules b/.gitmodules index d063bd57..95458096 100644 --- a/.gitmodules +++ b/.gitmodules @@ -2,10 +2,10 @@ path = providers/openfeature-provider-flagd/openfeature/schemas url = https://github.com/open-feature/schemas branch = protobuf-v0.6.1 -[submodule "providers/openfeature-provider-flagd/test-harness"] - path = providers/openfeature-provider-flagd/openfeature/test-harness - url = git@github.com:open-feature/flagd-testbed.git - branch = v2.5.0 [submodule "providers/openfeature-provider-flagd/spec"] path = providers/openfeature-provider-flagd/openfeature/spec url = https://github.com/open-feature/spec +[submodule "providers/openfeature-provider-flagd/openfeature/test-harness"] + path = providers/openfeature-provider-flagd/openfeature/test-harness + url = https://github.com/open-feature/flagd-testbed.git + branch = v2.6.0 diff --git a/providers/openfeature-provider-flagd/openfeature/test-harness b/providers/openfeature-provider-flagd/openfeature/test-harness deleted file mode 160000 index 9d35a07f..00000000 --- a/providers/openfeature-provider-flagd/openfeature/test-harness +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 9d35a07f43c6b5e1810a5e83029aae62a5dbd494 From 11666ff6d9339f231a4418f676ea3d8175df6ee2 Mon Sep 17 00:00:00 2001 From: "christian.lutnik" Date: Tue, 18 Mar 2025 14:03:05 +0100 Subject: [PATCH 11/30] switch to new version of flagd testbed v2 Signed-off-by: christian.lutnik --- providers/openfeature-provider-flagd/openfeature/test-harness | 1 + 1 file changed, 1 insertion(+) create mode 160000 providers/openfeature-provider-flagd/openfeature/test-harness diff --git a/providers/openfeature-provider-flagd/openfeature/test-harness b/providers/openfeature-provider-flagd/openfeature/test-harness new file mode 160000 index 00000000..a78a8e1a --- /dev/null +++ b/providers/openfeature-provider-flagd/openfeature/test-harness @@ -0,0 +1 @@ +Subproject commit a78a8e1ab74a9a982ab07ca755bfc99aa5d43d07 From 69da51c169b055beb34b614b5974eac37a0adf8a Mon Sep 17 00:00:00 2001 From: "christian.lutnik" Date: Tue, 18 Mar 2025 14:10:14 +0100 Subject: [PATCH 12/30] fix zero value errors Signed-off-by: christian.lutnik --- .../openfeature/contrib/provider/flagd/resolvers/in_process.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/in_process.py b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/in_process.py index 252473a8..cd90a718 100644 --- a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/in_process.py +++ b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/in_process.py @@ -149,7 +149,7 @@ def _resolve( ) variant, value = flag.get_variant(variant) - if not value: + if value is None: raise ParseError(f"Resolved variant {variant} not in variants config.") return FlagResolutionDetails( From 3eff27208b05222dab56697549a572cafba8cc89 Mon Sep 17 00:00:00 2001 From: "christian.lutnik" Date: Wed, 19 Mar 2025 08:39:19 +0100 Subject: [PATCH 13/30] switch to new version of flagd testbed v3 Signed-off-by: christian.lutnik --- .gitmodules | 2 +- .../openfeature-provider-flagd/openfeature/test-harness | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 160000 providers/openfeature-provider-flagd/providers/openfeature-provider-flagd/openfeature/test-harness diff --git a/.gitmodules b/.gitmodules index 95458096..0987f8f8 100644 --- a/.gitmodules +++ b/.gitmodules @@ -8,4 +8,4 @@ [submodule "providers/openfeature-provider-flagd/openfeature/test-harness"] path = providers/openfeature-provider-flagd/openfeature/test-harness url = https://github.com/open-feature/flagd-testbed.git - branch = v2.6.0 + branch = v2.6.1 diff --git a/providers/openfeature-provider-flagd/providers/openfeature-provider-flagd/openfeature/test-harness b/providers/openfeature-provider-flagd/providers/openfeature-provider-flagd/openfeature/test-harness new file mode 160000 index 00000000..a78a8e1a --- /dev/null +++ b/providers/openfeature-provider-flagd/providers/openfeature-provider-flagd/openfeature/test-harness @@ -0,0 +1 @@ +Subproject commit a78a8e1ab74a9a982ab07ca755bfc99aa5d43d07 From 850ce81e211bd05ce88b245151186ae8839339c7 Mon Sep 17 00:00:00 2001 From: "christian.lutnik" Date: Wed, 19 Mar 2025 08:39:39 +0100 Subject: [PATCH 14/30] switch to new version of flagd testbed v3 Signed-off-by: christian.lutnik --- providers/openfeature-provider-flagd/openfeature/test-harness | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/providers/openfeature-provider-flagd/openfeature/test-harness b/providers/openfeature-provider-flagd/openfeature/test-harness index a78a8e1a..716f7a99 160000 --- a/providers/openfeature-provider-flagd/openfeature/test-harness +++ b/providers/openfeature-provider-flagd/openfeature/test-harness @@ -1 +1 @@ -Subproject commit a78a8e1ab74a9a982ab07ca755bfc99aa5d43d07 +Subproject commit 716f7a9905d40d97852906bb65575cf162e94f22 From f2b37b541f24db2e5409d482ce2ceca77d0a8d14 Mon Sep 17 00:00:00 2001 From: "christian.lutnik" Date: Wed, 19 Mar 2025 08:42:52 +0100 Subject: [PATCH 15/30] switch to new version of flagd testbed v4 Signed-off-by: christian.lutnik --- providers/openfeature-provider-flagd/openfeature/test-harness | 1 - 1 file changed, 1 deletion(-) delete mode 160000 providers/openfeature-provider-flagd/openfeature/test-harness diff --git a/providers/openfeature-provider-flagd/openfeature/test-harness b/providers/openfeature-provider-flagd/openfeature/test-harness deleted file mode 160000 index 716f7a99..00000000 --- a/providers/openfeature-provider-flagd/openfeature/test-harness +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 716f7a9905d40d97852906bb65575cf162e94f22 From 503d3e5f2fa8b58e4d78824b020041cfe7710d49 Mon Sep 17 00:00:00 2001 From: "christian.lutnik" Date: Wed, 19 Mar 2025 08:47:21 +0100 Subject: [PATCH 16/30] switch to new version of flagd testbed v5 Signed-off-by: christian.lutnik --- providers/openfeature-provider-flagd/openfeature/test-harness | 1 + 1 file changed, 1 insertion(+) create mode 160000 providers/openfeature-provider-flagd/openfeature/test-harness diff --git a/providers/openfeature-provider-flagd/openfeature/test-harness b/providers/openfeature-provider-flagd/openfeature/test-harness new file mode 160000 index 00000000..716f7a99 --- /dev/null +++ b/providers/openfeature-provider-flagd/openfeature/test-harness @@ -0,0 +1 @@ +Subproject commit 716f7a9905d40d97852906bb65575cf162e94f22 From 257a42130ccf4c94edfd74b59460c08750378870 Mon Sep 17 00:00:00 2001 From: "christian.lutnik" Date: Wed, 19 Mar 2025 08:49:47 +0100 Subject: [PATCH 17/30] switch to new version of flagd testbed v6 Signed-off-by: christian.lutnik --- .../openfeature-provider-flagd/openfeature/test-harness | 1 - 1 file changed, 1 deletion(-) delete mode 160000 providers/openfeature-provider-flagd/providers/openfeature-provider-flagd/openfeature/test-harness diff --git a/providers/openfeature-provider-flagd/providers/openfeature-provider-flagd/openfeature/test-harness b/providers/openfeature-provider-flagd/providers/openfeature-provider-flagd/openfeature/test-harness deleted file mode 160000 index a78a8e1a..00000000 --- a/providers/openfeature-provider-flagd/providers/openfeature-provider-flagd/openfeature/test-harness +++ /dev/null @@ -1 +0,0 @@ -Subproject commit a78a8e1ab74a9a982ab07ca755bfc99aa5d43d07 From 761f6505ee068eb95d31684ed833157c571e76b1 Mon Sep 17 00:00:00 2001 From: "christian.lutnik" Date: Wed, 19 Mar 2025 14:12:24 +0100 Subject: [PATCH 18/30] minor improvements, adjust to workaround for fladg issue Signed-off-by: christian.lutnik --- .../contrib/provider/flagd/resolvers/in_process.py | 6 +++--- .../tests/e2e/step/provider_steps.py | 3 +++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/in_process.py b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/in_process.py index cd90a718..592aafbd 100644 --- a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/in_process.py +++ b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/in_process.py @@ -25,9 +25,9 @@ def _merge_metadata( typing.Mapping[str, typing.Union[float, int, str, bool]] ], ) -> typing.Mapping[str, typing.Union[float, int, str, bool]]: - metadata = {} - - if flag_set_metadata is not None: + if flag_set_metadata is None: + metadata = {} + else: metadata = dict(flag_set_metadata) if flag_metadata is not None: diff --git a/providers/openfeature-provider-flagd/tests/e2e/step/provider_steps.py b/providers/openfeature-provider-flagd/tests/e2e/step/provider_steps.py index e041437a..c780dcc9 100644 --- a/providers/openfeature-provider-flagd/tests/e2e/step/provider_steps.py +++ b/providers/openfeature-provider-flagd/tests/e2e/step/provider_steps.py @@ -31,6 +31,7 @@ class TestProviderType(Enum): UNSTABLE = "unstable" SSL = "ssl" SOCKET = "socket" + METADATA = "metadata" @given("a provider is registered", target_fixture="client") @@ -68,6 +69,8 @@ def get_default_options_for_provider( launchpad = "ssl" elif t == TestProviderType.SOCKET: return options, True + elif t == TestProviderType.METADATA: + launchpad = "metadata" if resolver_type == ResolverType.FILE: if "selector" in option_values: From 494fb749532ab6561ab8a84c3f23fb284aaa140d Mon Sep 17 00:00:00 2001 From: "christian.lutnik" Date: Wed, 19 Mar 2025 14:22:48 +0100 Subject: [PATCH 19/30] update test harness v10000 Signed-off-by: christian.lutnik --- .gitmodules | 2 +- providers/openfeature-provider-flagd/openfeature/test-harness | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index 0987f8f8..ac0d737c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -8,4 +8,4 @@ [submodule "providers/openfeature-provider-flagd/openfeature/test-harness"] path = providers/openfeature-provider-flagd/openfeature/test-harness url = https://github.com/open-feature/flagd-testbed.git - branch = v2.6.1 + branch = v2.7.0 diff --git a/providers/openfeature-provider-flagd/openfeature/test-harness b/providers/openfeature-provider-flagd/openfeature/test-harness index 716f7a99..4008f2da 160000 --- a/providers/openfeature-provider-flagd/openfeature/test-harness +++ b/providers/openfeature-provider-flagd/openfeature/test-harness @@ -1 +1 @@ -Subproject commit 716f7a9905d40d97852906bb65575cf162e94f22 +Subproject commit 4008f2dadbbe43c6ec623076d65be2908d9b28ad From 2a991b9a0cf8ecf7a2faeb04122181fb910a8ed5 Mon Sep 17 00:00:00 2001 From: "christian.lutnik" Date: Wed, 19 Mar 2025 14:24:35 +0100 Subject: [PATCH 20/30] fix format Signed-off-by: christian.lutnik --- .../contrib/provider/flagd/resolvers/in_process.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/in_process.py b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/in_process.py index 592aafbd..4b70afdd 100644 --- a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/in_process.py +++ b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/in_process.py @@ -25,10 +25,7 @@ def _merge_metadata( typing.Mapping[str, typing.Union[float, int, str, bool]] ], ) -> typing.Mapping[str, typing.Union[float, int, str, bool]]: - if flag_set_metadata is None: - metadata = {} - else: - metadata = dict(flag_set_metadata) + metadata = {} if flag_set_metadata is None else dict(flag_set_metadata) if flag_metadata is not None: for key, value in flag_metadata.items(): From 0c4f28f1f009488e1787f7648c8e90937dd7ac4b Mon Sep 17 00:00:00 2001 From: "christian.lutnik" Date: Thu, 20 Mar 2025 08:39:06 +0100 Subject: [PATCH 21/30] attempt to fix tests Signed-off-by: christian.lutnik --- providers/openfeature-provider-flagd/tests/e2e/rpc/conftest.py | 2 +- .../openfeature-provider-flagd/tests/e2e/step/flag_step.py | 3 +++ .../tests/e2e/step/provider_steps.py | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/providers/openfeature-provider-flagd/tests/e2e/rpc/conftest.py b/providers/openfeature-provider-flagd/tests/e2e/rpc/conftest.py index 77ca44b1..de3cb850 100644 --- a/providers/openfeature-provider-flagd/tests/e2e/rpc/conftest.py +++ b/providers/openfeature-provider-flagd/tests/e2e/rpc/conftest.py @@ -4,7 +4,7 @@ from tests.e2e.testfilter import TestFilter resolver = ResolverType.RPC -feature_list = ["~targetURI", "~unixsocket", "~sync"] +feature_list = ["~targetURI", "~unixsocket", "~sync", "~metadata"] def pytest_collection_modifyitems(config, items): diff --git a/providers/openfeature-provider-flagd/tests/e2e/step/flag_step.py b/providers/openfeature-provider-flagd/tests/e2e/step/flag_step.py index e0ff97ce..ab911d52 100644 --- a/providers/openfeature-provider-flagd/tests/e2e/step/flag_step.py +++ b/providers/openfeature-provider-flagd/tests/e2e/step/flag_step.py @@ -1,3 +1,5 @@ +from time import sleep + import requests from asserts import assert_equal from pytest_bdd import given, parsers, then, when @@ -46,6 +48,7 @@ def evaluate_with_details( @when("the flag was modified") def assert_flag_change_event(container): requests.post(f"{container.get_launchpad_url()}/change", timeout=1) + sleep(.2) @then("the flag should be part of the event payload") diff --git a/providers/openfeature-provider-flagd/tests/e2e/step/provider_steps.py b/providers/openfeature-provider-flagd/tests/e2e/step/provider_steps.py index c780dcc9..28cb19f2 100644 --- a/providers/openfeature-provider-flagd/tests/e2e/step/provider_steps.py +++ b/providers/openfeature-provider-flagd/tests/e2e/step/provider_steps.py @@ -77,7 +77,7 @@ def get_default_options_for_provider( path = option_values["selector"] path = path.replace("rawflags/", "") options["offline_flag_source_path"] = os.path.join( - "..", "openfeature", "test-harness", "flags", path + Path(__file__).parents[3], "openfeature", "test-harness", "flags", path ) else: options["offline_flag_source_path"] = os.path.join( From ed608183f0f548fa40a43208ef547fe3061b71f7 Mon Sep 17 00:00:00 2001 From: "christian.lutnik" Date: Thu, 20 Mar 2025 08:40:42 +0100 Subject: [PATCH 22/30] fix format Signed-off-by: christian.lutnik --- .../openfeature-provider-flagd/tests/e2e/step/flag_step.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/providers/openfeature-provider-flagd/tests/e2e/step/flag_step.py b/providers/openfeature-provider-flagd/tests/e2e/step/flag_step.py index ab911d52..2809558a 100644 --- a/providers/openfeature-provider-flagd/tests/e2e/step/flag_step.py +++ b/providers/openfeature-provider-flagd/tests/e2e/step/flag_step.py @@ -48,7 +48,7 @@ def evaluate_with_details( @when("the flag was modified") def assert_flag_change_event(container): requests.post(f"{container.get_launchpad_url()}/change", timeout=1) - sleep(.2) + sleep(0.2) @then("the flag should be part of the event payload") From b2ad90c3c0bc85297f828a67c523f9ef9eebedef Mon Sep 17 00:00:00 2001 From: "christian.lutnik" Date: Fri, 21 Mar 2025 09:28:02 +0100 Subject: [PATCH 23/30] fix failing tests, upgrade test harness Signed-off-by: christian.lutnik --- providers/openfeature-provider-flagd/openfeature/test-harness | 2 +- .../openfeature-provider-flagd/tests/e2e/step/event_steps.py | 2 +- .../openfeature-provider-flagd/tests/e2e/step/flag_step.py | 1 - .../openfeature-provider-flagd/tests/e2e/step/provider_steps.py | 2 +- providers/openfeature-provider-flagd/tests/test_errors.py | 2 +- 5 files changed, 4 insertions(+), 5 deletions(-) diff --git a/providers/openfeature-provider-flagd/openfeature/test-harness b/providers/openfeature-provider-flagd/openfeature/test-harness index 4008f2da..dee690a1 160000 --- a/providers/openfeature-provider-flagd/openfeature/test-harness +++ b/providers/openfeature-provider-flagd/openfeature/test-harness @@ -1 +1 @@ -Subproject commit 4008f2dadbbe43c6ec623076d65be2908d9b28ad +Subproject commit dee690a1d274000e0cb6c36d569299f6237daebf diff --git a/providers/openfeature-provider-flagd/tests/e2e/step/event_steps.py b/providers/openfeature-provider-flagd/tests/e2e/step/event_steps.py index ccd0e9ce..eb663182 100644 --- a/providers/openfeature-provider-flagd/tests/e2e/step/event_steps.py +++ b/providers/openfeature-provider-flagd/tests/e2e/step/event_steps.py @@ -44,7 +44,7 @@ def handler(event): def assert_handlers(handles, event_type: str, max_wait: int = 2): - poll_interval = 1 + poll_interval = .2 while max_wait > 0: found = any(h["type"] == event_type for h in handles) if not found: diff --git a/providers/openfeature-provider-flagd/tests/e2e/step/flag_step.py b/providers/openfeature-provider-flagd/tests/e2e/step/flag_step.py index 2809558a..5d55e5be 100644 --- a/providers/openfeature-provider-flagd/tests/e2e/step/flag_step.py +++ b/providers/openfeature-provider-flagd/tests/e2e/step/flag_step.py @@ -48,7 +48,6 @@ def evaluate_with_details( @when("the flag was modified") def assert_flag_change_event(container): requests.post(f"{container.get_launchpad_url()}/change", timeout=1) - sleep(0.2) @then("the flag should be part of the event payload") diff --git a/providers/openfeature-provider-flagd/tests/e2e/step/provider_steps.py b/providers/openfeature-provider-flagd/tests/e2e/step/provider_steps.py index 28cb19f2..587f070c 100644 --- a/providers/openfeature-provider-flagd/tests/e2e/step/provider_steps.py +++ b/providers/openfeature-provider-flagd/tests/e2e/step/provider_steps.py @@ -130,7 +130,7 @@ def flagd_restart( resolver_type: ResolverType, ): requests.post( - f"{container.get_launchpad_url()}/restart?seconds={seconds}", timeout=2 + f"{container.get_launchpad_url()}/restart?seconds={seconds}", timeout=float(seconds) + 2 ) pass diff --git a/providers/openfeature-provider-flagd/tests/test_errors.py b/providers/openfeature-provider-flagd/tests/test_errors.py index 15ea2d8c..872994c8 100644 --- a/providers/openfeature-provider-flagd/tests/test_errors.py +++ b/providers/openfeature-provider-flagd/tests/test_errors.py @@ -108,5 +108,5 @@ def fail(*args, **kwargs): ) elapsed = time.time() - t - assert abs(elapsed - wait * 0.001) < 0.11 + assert abs(elapsed - wait * 0.001) < 0.15 assert init_failed From 88224645bcefdc5e2c921dd8083dd4092c20344e Mon Sep 17 00:00:00 2001 From: "christian.lutnik" Date: Fri, 21 Mar 2025 09:35:56 +0100 Subject: [PATCH 24/30] fix format Signed-off-by: christian.lutnik --- .../openfeature-provider-flagd/tests/e2e/step/event_steps.py | 2 +- .../tests/e2e/step/provider_steps.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/providers/openfeature-provider-flagd/tests/e2e/step/event_steps.py b/providers/openfeature-provider-flagd/tests/e2e/step/event_steps.py index eb663182..7aec3648 100644 --- a/providers/openfeature-provider-flagd/tests/e2e/step/event_steps.py +++ b/providers/openfeature-provider-flagd/tests/e2e/step/event_steps.py @@ -44,7 +44,7 @@ def handler(event): def assert_handlers(handles, event_type: str, max_wait: int = 2): - poll_interval = .2 + poll_interval = 0.2 while max_wait > 0: found = any(h["type"] == event_type for h in handles) if not found: diff --git a/providers/openfeature-provider-flagd/tests/e2e/step/provider_steps.py b/providers/openfeature-provider-flagd/tests/e2e/step/provider_steps.py index 587f070c..3d8d5195 100644 --- a/providers/openfeature-provider-flagd/tests/e2e/step/provider_steps.py +++ b/providers/openfeature-provider-flagd/tests/e2e/step/provider_steps.py @@ -130,7 +130,8 @@ def flagd_restart( resolver_type: ResolverType, ): requests.post( - f"{container.get_launchpad_url()}/restart?seconds={seconds}", timeout=float(seconds) + 2 + f"{container.get_launchpad_url()}/restart?seconds={seconds}", + timeout=float(seconds) + 2, ) pass From 519c3a5815b5eca65cf9f38e6ad4fc5297576a62 Mon Sep 17 00:00:00 2001 From: "christian.lutnik" Date: Fri, 21 Mar 2025 09:45:57 +0100 Subject: [PATCH 25/30] fix format Signed-off-by: christian.lutnik --- .../openfeature-provider-flagd/tests/e2e/step/flag_step.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/providers/openfeature-provider-flagd/tests/e2e/step/flag_step.py b/providers/openfeature-provider-flagd/tests/e2e/step/flag_step.py index 5d55e5be..e0ff97ce 100644 --- a/providers/openfeature-provider-flagd/tests/e2e/step/flag_step.py +++ b/providers/openfeature-provider-flagd/tests/e2e/step/flag_step.py @@ -1,5 +1,3 @@ -from time import sleep - import requests from asserts import assert_equal from pytest_bdd import given, parsers, then, when From e054a4fe379a209e02d428ef6496ac46b5343ee7 Mon Sep 17 00:00:00 2001 From: chrfwow Date: Tue, 25 Mar 2025 13:28:43 +0100 Subject: [PATCH 26/30] Update providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/flags.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Anton Grübel Signed-off-by: chrfwow --- .../contrib/provider/flagd/resolvers/process/flags.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/flags.py b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/flags.py index f1bd6a34..af5b6ff7 100644 --- a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/flags.py +++ b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/flags.py @@ -22,14 +22,7 @@ def _validate_metadata(key: str, value: typing.Union[float, int, str, bool]) -> if value is None: raise ParseError("Metadata value for key " + str(key) + " must be set") elif not isinstance(value, (float, int, str, bool)): - raise ParseError( - "Metadata value " - + str(value) - + " for key " - + str(key) - + " must be of type float, int, str or bool, but is " - + str(type(value)) - ) + raise ParseError(f"Metadata value {value} for key {key} must be of type float, int, str or bool, but is {type(value)}") class FlagStore: From 21e52b7ec5d0acc0f413a1ee94573535c068737d Mon Sep 17 00:00:00 2001 From: chrfwow Date: Tue, 25 Mar 2025 13:28:57 +0100 Subject: [PATCH 27/30] Update providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/flags.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Anton Grübel Signed-off-by: chrfwow --- .../contrib/provider/flagd/resolvers/process/flags.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/flags.py b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/flags.py index af5b6ff7..ba5a38ee 100644 --- a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/flags.py +++ b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/flags.py @@ -11,12 +11,7 @@ def _validate_metadata(key: str, value: typing.Union[float, int, str, bool]) -> if key is None: raise ParseError("Metadata key must be set") elif not isinstance(key, str): - raise ParseError( - "Metadata key " - + str(key) - + " must be of type str, but is " - + str(type(key)) - ) + raise ParseError(f"Metadata key {key} must be of type str, but is {type(key}") elif not key: raise ParseError("key must not be empty") if value is None: From f5a376bc1d8672509019396841ba133b777d3a35 Mon Sep 17 00:00:00 2001 From: chrfwow Date: Tue, 25 Mar 2025 13:29:06 +0100 Subject: [PATCH 28/30] Update providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/flags.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Anton Grübel Signed-off-by: chrfwow --- .../contrib/provider/flagd/resolvers/process/flags.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/flags.py b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/flags.py index ba5a38ee..d439f375 100644 --- a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/flags.py +++ b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/flags.py @@ -15,7 +15,7 @@ def _validate_metadata(key: str, value: typing.Union[float, int, str, bool]) -> elif not key: raise ParseError("key must not be empty") if value is None: - raise ParseError("Metadata value for key " + str(key) + " must be set") + raise ParseError(f"Metadata value for key {key} must be set") elif not isinstance(value, (float, int, str, bool)): raise ParseError(f"Metadata value {value} for key {key} must be of type float, int, str or bool, but is {type(value)}") From aaff1613ad6350ceec74046cdd7500ffeaab826d Mon Sep 17 00:00:00 2001 From: "christian.lutnik" Date: Tue, 25 Mar 2025 13:31:48 +0100 Subject: [PATCH 29/30] fix string format Signed-off-by: christian.lutnik --- .../contrib/provider/flagd/resolvers/process/flags.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/flags.py b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/flags.py index d439f375..d6e3a8b9 100644 --- a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/flags.py +++ b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/flags.py @@ -11,7 +11,7 @@ def _validate_metadata(key: str, value: typing.Union[float, int, str, bool]) -> if key is None: raise ParseError("Metadata key must be set") elif not isinstance(key, str): - raise ParseError(f"Metadata key {key} must be of type str, but is {type(key}") + raise ParseError(f"Metadata key {key} must be of type str, but is {type(key)}") elif not key: raise ParseError("key must not be empty") if value is None: From 85b70f678a4e3c49e4e4324d45484fed2a5b2105 Mon Sep 17 00:00:00 2001 From: "christian.lutnik" Date: Tue, 25 Mar 2025 13:32:53 +0100 Subject: [PATCH 30/30] fix format Signed-off-by: christian.lutnik --- .../contrib/provider/flagd/resolvers/process/flags.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/flags.py b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/flags.py index d6e3a8b9..9559761e 100644 --- a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/flags.py +++ b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/flags.py @@ -17,7 +17,9 @@ def _validate_metadata(key: str, value: typing.Union[float, int, str, bool]) -> if value is None: raise ParseError(f"Metadata value for key {key} must be set") elif not isinstance(value, (float, int, str, bool)): - raise ParseError(f"Metadata value {value} for key {key} must be of type float, int, str or bool, but is {type(value)}") + raise ParseError( + f"Metadata value {value} for key {key} must be of type float, int, str or bool, but is {type(value)}" + ) class FlagStore: