From 62889ff9c3bc14effc93c3d4f0700cf1ee35a448 Mon Sep 17 00:00:00 2001 From: Devin AI Date: Wed, 19 Aug 2026 16:26:25 +0000 Subject: [PATCH 1/2] feat: Report provider name and version to LaunchDarkly --- ld_openfeature/provider.py | 22 +++++++++++++++++++++- ld_openfeature/version.py | 1 + release-please-config.json | 2 +- tests/test_provider.py | 7 +++++++ 4 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 ld_openfeature/version.py diff --git a/ld_openfeature/provider.py b/ld_openfeature/provider.py index 5865b98..1bcd0b7 100644 --- a/ld_openfeature/provider.py +++ b/ld_openfeature/provider.py @@ -1,3 +1,4 @@ +import copy import threading from typing import Any, List, Mapping, Optional, Sequence, Union @@ -14,15 +15,34 @@ from ld_openfeature.impl.context_converter import EvaluationContextConverter from ld_openfeature.impl.details_converter import ResolutionDetailsConverter +from ld_openfeature.version import VERSION + + +WRAPPER_NAME = "open-feature-python-server" class LaunchDarklyProvider(AbstractProvider): def __init__(self, config: Config): - self.__client = LDClient(config) + self.__client = LDClient(self.__with_wrapper_info(config)) self.__context_converter = EvaluationContextConverter() self.__details_converter = ResolutionDetailsConverter() + @staticmethod + def __with_wrapper_info(config: Config) -> Config: + """ + Identify the provider, rather than the SDK, as the source of requests. + + The Python SDK only accepts wrapper information through the ``Config`` + constructor, so the provided config is copied and its wrapper fields + are replaced. + """ + wrapped = copy.copy(config) + wrapped._Config__wrapper_name = WRAPPER_NAME # type: ignore[attr-defined] + wrapped._Config__wrapper_version = VERSION # type: ignore[attr-defined] + + return wrapped + @property def client(self) -> LDClient: """ diff --git a/ld_openfeature/version.py b/ld_openfeature/version.py new file mode 100644 index 0000000..5e8875d --- /dev/null +++ b/ld_openfeature/version.py @@ -0,0 +1 @@ +VERSION = "0.6.0" # x-release-please-version diff --git a/release-please-config.json b/release-please-config.json index e06f67d..5e913a7 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -4,7 +4,7 @@ "release-type": "python", "versioning": "default", "include-v-in-tag": false, - "extra-files": ["docs/conf.py"], + "extra-files": ["docs/conf.py", "ld_openfeature/version.py"], "include-component-in-tag": false } } diff --git a/tests/test_provider.py b/tests/test_provider.py index c0e1d5c..f9324a8 100644 --- a/tests/test_provider.py +++ b/tests/test_provider.py @@ -14,6 +14,7 @@ from openfeature import api from ld_openfeature import LaunchDarklyProvider, Config +from ld_openfeature.version import VERSION from tests.test_data_sources import FailingDataSource, StaleDataSource, UpdatingDataSource, DelayedFailingDataSource @@ -47,6 +48,12 @@ def test_ldclient_is_accessible(provider: LaunchDarklyProvider): assert type(provider.client) is LDClient +def test_provider_identifies_itself_as_the_wrapper(provider: LaunchDarklyProvider, config: Config): + assert provider.client._config.wrapper_name == "open-feature-python-server" + assert provider.client._config.wrapper_version == VERSION + assert config.wrapper_name is None + + def test_not_providing_context_returns_error(provider: LaunchDarklyProvider): resolution_details = provider.resolve_boolean_details("flag-key", True, None) From 892c9053a85b286b68235a7a51417dc052a29976 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 19 Aug 2026 16:37:26 +0000 Subject: [PATCH 2/2] refactor: Use Config.with_wrapper_information Co-Authored-By: rlamb@launchdarkly.com <4955475+kinyoklion@users.noreply.github.com> --- ld_openfeature/provider.py | 18 +----------------- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 18 deletions(-) diff --git a/ld_openfeature/provider.py b/ld_openfeature/provider.py index 1bcd0b7..6d1636e 100644 --- a/ld_openfeature/provider.py +++ b/ld_openfeature/provider.py @@ -1,4 +1,3 @@ -import copy import threading from typing import Any, List, Mapping, Optional, Sequence, Union @@ -23,26 +22,11 @@ class LaunchDarklyProvider(AbstractProvider): def __init__(self, config: Config): - self.__client = LDClient(self.__with_wrapper_info(config)) + self.__client = LDClient(config.with_wrapper_information(WRAPPER_NAME, VERSION)) self.__context_converter = EvaluationContextConverter() self.__details_converter = ResolutionDetailsConverter() - @staticmethod - def __with_wrapper_info(config: Config) -> Config: - """ - Identify the provider, rather than the SDK, as the source of requests. - - The Python SDK only accepts wrapper information through the ``Config`` - constructor, so the provided config is copied and its wrapper fields - are replaced. - """ - wrapped = copy.copy(config) - wrapped._Config__wrapper_name = WRAPPER_NAME # type: ignore[attr-defined] - wrapped._Config__wrapper_version = VERSION # type: ignore[attr-defined] - - return wrapped - @property def client(self) -> LDClient: """ diff --git a/pyproject.toml b/pyproject.toml index 63cd10e..443178b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,7 +27,7 @@ packages = [ [tool.poetry.dependencies] python = "^3.10" openfeature-sdk = ">=0.8.0,<1" -launchdarkly-server-sdk = "<10" +launchdarkly-server-sdk = ">=9.17.0,<10" [tool.poetry.group.dev.dependencies]