From 13c413208e64e14a4a551a9258ccdb1400bb4846 Mon Sep 17 00:00:00 2001 From: Simon Schrottner Date: Fri, 18 Sep 2026 12:53:04 +0200 Subject: [PATCH] chore(deps): move the workspace onto openfeature-sdk 0.10.0 The lock has resolved 0.8.4 since 2025-12-09 while 0.9.0 and 0.10.0 have shipped, so nothing in this repository has ever been tested against either. Nothing pinned it there: every floor is a `>=`, every member is `requires-python >=3.10`, and the lock carries no constraints -- renovate's lock-file maintenance simply never picked the release up. `uv lock --upgrade-package openfeature-sdk` moves 0.8.4 -> 0.10.0; the only other change is the lock's stale openfeature-provider-flagd version, 0.5.2 -> 0.6.1. Two behaviour changes reach the tests. Neither touches the flagd source, which needs no change and stays clean under `poe mypy` on 26 files. First, 0.10.0 made `set_provider` non-blocking, so a test that sets a provider and evaluates on the next line now races initialisation and reads PROVIDER_NOT_READY -- 20 failures across test_errors.py and test_metadata.py. `set_provider_and_wait` restores the old blocking behaviour with one difference that matters here: it re-raises whatever `initialize` raised, where `set_provider` only dispatched PROVIDER_ERROR. Several of these scenarios feed the provider a deliberately broken flag file, so that raise is the scenario rather than a failure, hence the `contextlib.suppress`; it is not papering over an unexpected error. Second, 0.10.0 isolated event-handler dispatch onto a ThreadPoolExecutor where 0.8.4 called handlers inline -- open-feature/python-sdk#599. test_grpc_sync_fail_deadline read a flag set by a PROVIDER_ERROR handler on the line after the call returned, which is now a race: `_run_initialize` submits the event and then re-raises, and submit is not run. Rather than wait on the handler, the assertion goes: it existed because the old `set_provider` blocked and swallowed the error, leaving the event as the only way to observe a failed init, and `pytest.raises(ProviderNotReadyError)` now carries that claim directly. What the handler would assert today is that the SDK's registry dispatches PROVIDER_ERROR when `initialize` raises -- SDK plumbing, uniform across providers, and not this suite's to cover. The deadline measurement it shared the test with is untouched, though it now has to wait rather than time a call that would return immediately. The other two handler-driven tests here -- test_invalid_flag_set_metadata and the e2e event steps -- already poll with a timeout and need nothing. The tests are the only thing that needs 0.10.0, so the floor goes in flagd's dev group and the runtime floor stays at 0.8.2. Measured on main 9a57dbb under Python 3.14: flagd's unit tests are 220 passed with this change on every repeated run, and `poe mypy` is clean. tests/e2e needs docker and is left to CI. Unleash's `track` signature was fixed separately in #474, which this branch no longer touches. Signed-off-by: Simon Schrottner --- .../openfeature-provider-flagd/pyproject.toml | 3 ++ .../tests/test_errors.py | 35 +++++++++---------- .../tests/test_metadata.py | 8 +++-- uv.lock | 10 +++--- 4 files changed, 32 insertions(+), 24 deletions(-) diff --git a/providers/openfeature-provider-flagd/pyproject.toml b/providers/openfeature-provider-flagd/pyproject.toml index 12371d3b8..503879384 100644 --- a/providers/openfeature-provider-flagd/pyproject.toml +++ b/providers/openfeature-provider-flagd/pyproject.toml @@ -35,6 +35,9 @@ dev = [ "coverage[toml]>=7.10.0,<8.0.0", "grpcio-health-checking>=1.82.1,<2.0.0", "mypy>=1.18.0,<2.0.0", + # The tests call `api.set_provider_and_wait`, which arrived in 0.10.0. The + # runtime floor stays at 0.8.2 because the provider itself needs nothing newer. + "openfeature-sdk>=0.10.0", "poethepoet>=0.37.0", "pytest>=9.0.0,<10.0.0", "pytest-bdd>=8.1.0,<9.0.0", diff --git a/providers/openfeature-provider-flagd/tests/test_errors.py b/providers/openfeature-provider-flagd/tests/test_errors.py index e335fc1c1..c35e228e4 100644 --- a/providers/openfeature-provider-flagd/tests/test_errors.py +++ b/providers/openfeature-provider-flagd/tests/test_errors.py @@ -1,3 +1,4 @@ +import contextlib import os import time @@ -7,13 +8,19 @@ from openfeature.contrib.provider.flagd import FlagdProvider from openfeature.contrib.provider.flagd.config import ResolverType from openfeature.evaluation_context import EvaluationContext -from openfeature.event import ProviderEvent -from openfeature.exception import ErrorCode +from openfeature.exception import ( + ErrorCode, + OpenFeatureError, + ProviderNotReadyError, +) from openfeature.flag_evaluation import Reason def create_client(provider: FlagdProvider): - api.set_provider(provider) + # These flag files are deliberately broken, so init may fail; what the + # evaluation returns afterwards is the assertion. + with contextlib.suppress(OpenFeatureError): + api.set_provider_and_wait(provider) return api.get_client() @@ -119,23 +126,15 @@ def test_flag_disabled(): @pytest.mark.parametrize("wait", (500, 250)) def test_grpc_sync_fail_deadline(wait: int): - init_failed = False - - def fail(*args, **kwargs): - nonlocal init_failed - init_failed = True - - api.get_client().add_handler(ProviderEvent.PROVIDER_ERROR, fail) - t = time.time() - api.set_provider( - FlagdProvider( - resolver_type=ResolverType.IN_PROCESS, - port=99999, # dead port to test failure - deadline_ms=wait, + with pytest.raises(ProviderNotReadyError): + api.set_provider_and_wait( + FlagdProvider( + resolver_type=ResolverType.IN_PROCESS, + port=99999, # dead port to test failure + deadline_ms=wait, + ) ) - ) elapsed = time.time() - t assert abs(elapsed - wait * 0.001) < 0.17 - assert init_failed diff --git a/providers/openfeature-provider-flagd/tests/test_metadata.py b/providers/openfeature-provider-flagd/tests/test_metadata.py index 939af96fb..ef8331c51 100644 --- a/providers/openfeature-provider-flagd/tests/test_metadata.py +++ b/providers/openfeature-provider-flagd/tests/test_metadata.py @@ -1,3 +1,4 @@ +import contextlib import os import time from time import sleep @@ -11,7 +12,7 @@ _validate_metadata, ) from openfeature.event import EventDetails, ProviderEvent -from openfeature.exception import ErrorCode, ParseError +from openfeature.exception import ErrorCode, OpenFeatureError, ParseError def create_client(file_name): @@ -21,7 +22,10 @@ def create_client(file_name): offline_flag_source_path=f"{path}/{file_name}", ) - api.set_provider(provider) + # Some of these flag files are deliberately broken, so init may fail; what + # the evaluation returns afterwards is the assertion. + with contextlib.suppress(OpenFeatureError): + api.set_provider_and_wait(provider) return api.get_client() diff --git a/uv.lock b/uv.lock index 524edd4f6..14fb6ccea 100644 --- a/uv.lock +++ b/uv.lock @@ -1914,7 +1914,7 @@ dev = [ [[package]] name = "openfeature-provider-flagd" -version = "0.5.2" +version = "0.6.1" source = { editable = "providers/openfeature-provider-flagd" } dependencies = [ { name = "cachebox" }, @@ -1931,6 +1931,7 @@ dev = [ { name = "coverage", extra = ["toml"] }, { name = "grpcio-health-checking" }, { name = "mypy" }, + { name = "openfeature-sdk" }, { name = "poethepoet" }, { name = "pytest" }, { name = "pytest-bdd" }, @@ -1956,6 +1957,7 @@ dev = [ { name = "coverage", extras = ["toml"], specifier = ">=7.10.0,<8.0.0" }, { name = "grpcio-health-checking", specifier = ">=1.82.1,<2.0.0" }, { name = "mypy", specifier = ">=1.18.0,<2.0.0" }, + { name = "openfeature-sdk", specifier = ">=0.10.0" }, { name = "poethepoet", specifier = ">=0.37.0" }, { name = "pytest", specifier = ">=9.0.0,<10.0.0" }, { name = "pytest-bdd", specifier = ">=8.1.0,<9.0.0" }, @@ -2121,11 +2123,11 @@ dev = [ [[package]] name = "openfeature-sdk" -version = "0.8.4" +version = "0.10.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/3b/08/f6698d0614b8703170117b786bd77b7b0a04f3ee00f19fbe9b360d2dee69/openfeature_sdk-0.8.4.tar.gz", hash = "sha256:66abf71f928ec8c0db1111072bb0ef2635dfbd09510f77f4b548e5d0ea0e6c1a", size = 29676, upload-time = "2025-12-09T07:31:13.137Z" } +sdist = { url = "https://files.pythonhosted.org/packages/24/30/cfc684b7d8314398d476ae8ed515c10db99c4d7f950989db464b4ded12ce/openfeature_sdk-0.10.0.tar.gz", hash = "sha256:938c2540bdea4da3b01ef507517ee636f223a35abaaca845c5587e594151b052", size = 33516, upload-time = "2026-06-01T19:45:35.136Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9c/80/f6532778188c573cc83790b11abccde717d4c1442514e722d6bb6140e55c/openfeature_sdk-0.8.4-py3-none-any.whl", hash = "sha256:805ba090669798fc343ca9fdcbc56ff0f4b57bf6757533f0854d2021192e620a", size = 35986, upload-time = "2025-12-09T07:31:12.092Z" }, + { url = "https://files.pythonhosted.org/packages/da/44/8a4f5225e930ff0d999fd43f5d743a4babeb6c7e76dddc00f0e118878ef3/openfeature_sdk-0.10.0-py3-none-any.whl", hash = "sha256:75497ea75d73f684eef509a25f79ad6386368862e050af80ab70a44ae49b33e4", size = 38941, upload-time = "2026-06-01T19:45:33.011Z" }, ] [[package]]