Skip to content

Commit

Permalink
prepare 7.3.0 release (#161)
Browse files Browse the repository at this point in the history
  • Loading branch information
LaunchDarklyReleaseBot authored Dec 10, 2021
1 parent b35bad6 commit f72f1b3
Show file tree
Hide file tree
Showing 44 changed files with 2,622 additions and 941 deletions.
1 change: 1 addition & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ jobs:
name: verify typehints
command: |
export PATH="/home/circleci/.local/bin:$PATH"
mypy --install-types --non-interactive ldclient testing
mypy --config-file mypy.ini ldclient testing
- unless:
Expand Down
12 changes: 12 additions & 0 deletions docs/api-deprecated.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
Deprecated modules
===============================

ldclient.flag module
--------------------

This module is deprecated. For the :class:`~ldclient.evaluation.EvaluationDetail` type, please use :mod:`ldclient.evaluation`.

ldclient.flags_state module
---------------------------

This module is deprecated. For the :class:`~ldclient.evaluation.FeatureFlagsState` type, please use :mod:`ldclient.evaluation`.
13 changes: 3 additions & 10 deletions docs/api-main.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,8 @@ ldclient.config module
.. automodule:: ldclient.config
:members:

ldclient.flag module
--------------------
ldclient.evaluation module
--------------------------

.. automodule:: ldclient.flag
:members: EvaluationDetail

ldclient.flags_state module
---------------------------

.. automodule:: ldclient.flags_state
.. automodule:: ldclient.evaluation
:members:
:exclude-members: __init__, add_flag
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ For more information, see LaunchDarkly's `Quickstart <https://docs.launchdarkly.
api-main
api-integrations
api-extending
api-deprecated
40 changes: 31 additions & 9 deletions ldclient/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"""

from typing import Optional, Any, Dict, Mapping

from .impl import AnyNum

import hashlib
Expand All @@ -15,11 +16,12 @@
from ldclient.event_processor import DefaultEventProcessor
from ldclient.feature_requester import FeatureRequesterImpl
from ldclient.feature_store import _FeatureStoreDataSetSorter
from ldclient.flag import EvaluationDetail, evaluate, error_reason
from ldclient.flags_state import FeatureFlagsState
from ldclient.evaluation import EvaluationDetail, FeatureFlagsState
from ldclient.impl.big_segments import BigSegmentStoreManager
from ldclient.impl.evaluator import Evaluator, error_reason
from ldclient.impl.event_factory import _EventFactory
from ldclient.impl.stubs import NullEventProcessor, NullUpdateProcessor
from ldclient.interfaces import FeatureStore
from ldclient.interfaces import BigSegmentStoreStatusProvider, FeatureRequester, FeatureStore
from ldclient.polling import PollingUpdateProcessor
from ldclient.streaming import StreamingUpdateProcessor
from ldclient.util import check_uwsgi, log
Expand Down Expand Up @@ -85,8 +87,17 @@ def __init__(self, config: Config, start_wait: float=5):
self._event_factory_default = _EventFactory(False)
self._event_factory_with_reasons = _EventFactory(True)

self._store = _FeatureStoreClientWrapper(self._config.feature_store)
""" :type: FeatureStore """
store = _FeatureStoreClientWrapper(self._config.feature_store)
self._store = store # type: FeatureStore

big_segment_store_manager = BigSegmentStoreManager(self._config.big_segments)
self.__big_segment_store_manager = big_segment_store_manager

self._evaluator = Evaluator(
lambda key: store.get(FEATURES, key, lambda x: x),
lambda key: store.get(SEGMENTS, key, lambda x: x),
lambda key: big_segment_store_manager.get_user_membership(key)
)

if self._config.offline:
log.info("Started LaunchDarkly Client in offline mode")
Expand Down Expand Up @@ -139,8 +150,7 @@ def _make_update_processor(self, config, store, ready, diagnostic_accumulator):
if config.feature_requester_class:
feature_requester = config.feature_requester_class(config)
else:
feature_requester = FeatureRequesterImpl(config)
""" :type: FeatureRequester """
feature_requester = FeatureRequesterImpl(config) # type: FeatureRequester

return PollingUpdateProcessor(config, feature_requester, store, ready)

Expand All @@ -157,6 +167,7 @@ def close(self):
log.info("Closing LaunchDarkly client..")
self._event_processor.stop()
self._update_processor.stop()
self.__big_segment_store_manager.stop()

# These magic methods allow a client object to be automatically cleaned up by the "with" scope operator
def __enter__(self):
Expand Down Expand Up @@ -312,7 +323,7 @@ def _evaluate_internal(self, key, user, default, event_factory):
return EvaluationDetail(default, None, reason)

try:
result = evaluate(flag, user, self._store, event_factory)
result = self._evaluator.evaluate(flag, user, event_factory)
for event in result.events or []:
self._send_event(event)
detail = result.detail
Expand Down Expand Up @@ -383,7 +394,7 @@ def all_flags_state(self, user: dict, **kwargs) -> FeatureFlagsState:
if client_only and not flag.get('clientSide', False):
continue
try:
detail = evaluate(flag, user, self._store, self._event_factory_default).detail
detail = self._evaluator.evaluate(flag, user, self._event_factory_default).detail
state.add_flag(flag, detail.value, detail.variation_index,
detail.reason if with_reasons else None, details_only_if_tracked)
except Exception as e:
Expand All @@ -409,5 +420,16 @@ def secure_mode_hash(self, user: dict) -> str:
return ""
return hmac.new(self._config.sdk_key.encode(), key.encode(), hashlib.sha256).hexdigest()

@property
def big_segment_store_status_provider(self) -> BigSegmentStoreStatusProvider:
"""
Returns an interface for tracking the status of a Big Segment store.
The :class:`ldclient.interfaces.BigSegmentStoreStatusProvider` has methods for checking
whether the Big Segment store is (as far as the SDK knows) currently operational and
tracking changes in this status.
"""
return self.__big_segment_store_manager.status_provider


__all__ = ['LDClient', 'Config']
76 changes: 73 additions & 3 deletions ldclient/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,75 @@

from ldclient.feature_store import InMemoryFeatureStore
from ldclient.util import log
from ldclient.interfaces import EventProcessor, FeatureStore, UpdateProcessor, FeatureRequester
from ldclient.interfaces import BigSegmentStore, EventProcessor, FeatureStore, UpdateProcessor, FeatureRequester

GET_LATEST_FEATURES_PATH = '/sdk/latest-flags'
STREAM_FLAGS_PATH = '/flags'


class BigSegmentsConfig:
"""Configuration options related to Big Segments.
Big Segments are a specific type of user segments. For more information, read the LaunchDarkly
documentation: https://docs.launchdarkly.com/home/users/big-segments
If your application uses Big Segments, you will need to create a `BigSegmentsConfig` that at a
minimum specifies what database integration to use, and then pass the `BigSegmentsConfig`
object as the `big_segments` parameter when creating a :class:`Config`.
This example shows Big Segments being configured to use Redis:
::
from ldclient.config import Config, BigSegmentsConfig
from ldclient.integrations import Redis
store = Redis.new_big_segment_store(url='redis://localhost:6379')
config = Config(big_segments=BigSegmentsConfig(store = store))
"""
def __init__(self,
store: Optional[BigSegmentStore] = None,
user_cache_size: int=1000,
user_cache_time: float=5,
status_poll_interval: float=5,
stale_after: float=120):
"""
:param store: the implementation of :class:`ldclient.interfaces.BigSegmentStore` that will
be used to query the Big Segments database
:param user_cache_size: the maximum number of users whose Big Segment state will be cached
by the SDK at any given time
:param user_cache_time: the maximum length of time (in seconds) that the Big Segment state
for a user will be cached by the SDK
:param status_poll_interval: the interval (in seconds) at which the SDK will poll the Big
Segment store to make sure it is available and to determine how long ago it was updated
:param stale_after: the maximum length of time between updates of the Big Segments data
before the data is considered out of date
"""
self.__store = store
self.__user_cache_size = user_cache_size
self.__user_cache_time = user_cache_time
self.__status_poll_interval = status_poll_interval
self.__stale_after = stale_after
pass

@property
def store(self) -> Optional[BigSegmentStore]:
return self.__store

@property
def user_cache_size(self) -> int:
return self.__user_cache_size

@property
def user_cache_time(self) -> float:
return self.__user_cache_time

@property
def status_poll_interval(self) -> float:
return self.__status_poll_interval

@property
def stale_after(self) -> float:
return self.__stale_after

class HTTPConfig:
"""Advanced HTTP configuration options for the SDK client.
Expand Down Expand Up @@ -109,7 +172,8 @@ def __init__(self,
diagnostic_recording_interval: int=900,
wrapper_name: Optional[str]=None,
wrapper_version: Optional[str]=None,
http: HTTPConfig=HTTPConfig()):
http: HTTPConfig=HTTPConfig(),
big_segments: Optional[BigSegmentsConfig]=None):
"""
:param sdk_key: The SDK key for your LaunchDarkly account. This is always required.
:param base_uri: The base URL for the LaunchDarkly server. Most users should use the default
Expand Down Expand Up @@ -204,6 +268,7 @@ def __init__(self,
self.__wrapper_name = wrapper_name
self.__wrapper_version = wrapper_version
self.__http = http
self.__big_segments = BigSegmentsConfig() if not big_segments else big_segments

def copy_with_new_sdk_key(self, new_sdk_key: str) -> 'Config':
"""Returns a new ``Config`` instance that is the same as this one, except for having a different SDK key.
Expand Down Expand Up @@ -236,7 +301,8 @@ def copy_with_new_sdk_key(self, new_sdk_key: str) -> 'Config':
diagnostic_recording_interval=self.__diagnostic_recording_interval,
wrapper_name=self.__wrapper_name,
wrapper_version=self.__wrapper_version,
http=self.__http)
http=self.__http,
big_segments=self.__big_segments)

# for internal use only - probably should be part of the client logic
def get_default(self, key, default):
Expand Down Expand Up @@ -366,6 +432,10 @@ def wrapper_version(self) -> Optional[str]:
def http(self) -> HTTPConfig:
return self.__http

@property
def big_segments(self) -> BigSegmentsConfig:
return self.__big_segments

def _validate(self):
if self.offline is False and self.sdk_key is None or self.sdk_key == '':
log.warning("Missing or blank sdk_key.")
Loading

0 comments on commit f72f1b3

Please sign in to comment.