Source code for ld_patch.ld_patch
import sys
from functools import wraps
from typing import Any
from unittest.mock import MagicMock, Mock
import ldclient
TEST_PREFIX = "test_"
[docs]
class patch_feature:
def __init__(self, feature_flag: str, value: Any, client=None):
self.feature_flag = feature_flag
self.value = value
self._client = client
if self._client:
self._original_variation = self._client.variation
else:
self._original_variation = None
@property
def client(self):
if self._client is None:
self._client = ldclient.get()
return self._client
@property
def original_variation(self):
if self._original_variation is None:
self._original_variation = self.client.variation
return self._original_variation
[docs]
def copy(self):
return patch_feature(
feature_flag=self.feature_flag, value=self.value, client=self.client
)
[docs]
def decorate_class(self, klass):
# based on unittest.mock._patch.decorate_class
for attr in dir(klass):
if not attr.startswith(TEST_PREFIX):
continue
attr_value = getattr(klass, attr)
if not callable(attr_value):
continue
patcher = self.copy()
setattr(klass, attr, patcher(attr_value))
return klass
[docs]
def decorate_method(self, method):
@wraps(method)
def wrapper(*args, **kwargs):
with patch_feature(self.feature_flag, self.value):
return method(*args, **kwargs)
return wrapper
def __enter__(self):
def get_variation(flag, user, default):
if flag == self.feature_flag:
return self.value
return self.original_variation(flag, user, default)
if not isinstance(self.original_variation, Mock):
variation = MagicMock(spec=self.original_variation)
else:
variation = MagicMock()
variation.side_effect = get_variation
if not self.client.is_offline():
print(
"patch_feature is patching an online Launch Darkly client. "
"Set the client config to be offline for testing, or "
"initalize the client earlier.",
file=sys.stderr,
)
self.client.variation = variation
def __exit__(self, exc_type, exc_value, exc_tb):
self.client.variation = self.original_variation
def __call__(self, thing):
if isinstance(thing, type):
ret = self.decorate_class(thing)
else:
ret = self.decorate_method(thing)
return ret