def redis_get_all():
            r = None
            try:
                r = yield self._get_connection()
                """ :type: RedisClient """
                all_features = yield r.hgetall(self._features_key)
                if all_features is None or all_features is "":
                    log.warn(
                        "TwistedRedisFeatureStore: call to get all flags returned no results. Returning None."
                    )
                    defer.returnValue(None)

                results = {}
                for k, f_json in all_features.items() or {}:
                    f = json.loads(f_json.decode('utf-8'))
                    if 'deleted' in f and f['deleted'] is False:
                        results[f['key']] = f
                defer.returnValue(results)
            except Exception as e:
                log.error("Could not connect to Redis using url: " +
                          self._url + " with error message: " + e.message)
                defer.returnValue(None)
            finally:
                if r:
                    r.quit()
            defer.returnValue(None)
Beispiel #2
0
    def toggle(self, key, user, default):
        """Deprecated synonym for :func:`variation()`.

        .. deprecated:: 2.0.0
        """
        log.warn(
            "Deprecated method: toggle() called. Use variation() instead.")
        return self.variation(key, user, default)
Beispiel #3
0
    def identify(self, user):
        """Registers the user.

        :param dict user: attributes of the user to register
        """
        self._sanitize_user(user)
        if user is None or user.get('key') is None:
            log.warn("Missing user or user key when calling identify().")
        self._send_event({'kind': 'identify', 'key': user.get('key'), 'user': user})
Beispiel #4
0
 def identify(self, user):
     self._sanitize_user(user)
     if user is None or user.get('key') is None:
         log.warn("Missing user or user key when calling identify().")
     self._send_event({
         'kind': 'identify',
         'key': user.get('key'),
         'user': user
     })
Beispiel #5
0
 def track(self, event_name, user, data=None):
     self._sanitize_user(user)
     if user is None or user.get('key') is None:
         log.warn("Missing user or user key when calling track().")
     self._send_event({
         'kind': 'custom',
         'key': event_name,
         'user': user,
         'data': data
     })
Beispiel #6
0
    def track(self, event_name, user, data=None):
        """Tracks that a user performed an event.

        :param string event_name: The name of the event.
        :param dict user: The attributes of the user.
        :param data: Optional additional data associated with the event.
        """
        self._sanitize_user(user)
        if user is None or user.get('key') is None:
            log.warn("Missing user or user key when calling track().")
        self._send_event({'kind': 'custom', 'key': event_name, 'user': user, 'data': data})
Beispiel #7
0
    def __init__(self, sdk_key=None, config=None, start_wait=5):
        """Constructs a new LDClient instance.

        :param string sdk_key: the SDK key for your LaunchDarkly environment
        :param ldclient.config.Config config: optional custom configuration
        :param float start_wait: the number of seconds to wait for a successful connection to LaunchDarkly
        """
        check_uwsgi()

        if config is not None and config.sdk_key is not None and sdk_key is not None:
            raise Exception(
                "LaunchDarkly client init received both sdk_key and config with sdk_key. "
                "Only one of either is expected")

        if sdk_key is not None:
            log.warn(
                "Deprecated sdk_key argument was passed to init. Use config object instead."
            )
            self._config = Config(sdk_key=sdk_key)
        else:
            self._config = config or Config.default()
        self._config._validate()

        self._event_processor = None
        self._lock = Lock()

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

        if self._config.offline:
            log.info("Started LaunchDarkly Client in offline mode")

        if self._config.use_ldd:
            log.info("Started LaunchDarkly Client in LDD mode")

        self._event_processor = self._make_event_processor(self._config)

        update_processor_ready = threading.Event()
        self._update_processor = self._make_update_processor(
            self._config, self._store, update_processor_ready)
        self._update_processor.start()

        if start_wait > 0 and not self._config.offline and not self._config.use_ldd:
            log.info("Waiting up to " + str(start_wait) +
                     " seconds for LaunchDarkly client to initialize...")
            update_processor_ready.wait(start_wait)

        if self._update_processor.initialized() is True:
            log.info("Started LaunchDarkly Client: OK")
        else:
            log.warn(
                "Initialization timeout exceeded for LaunchDarkly Client or an error occurred. "
                "Feature Flags may not yet be available.")
Beispiel #8
0
    def all_flags(self, user):
        if self._config.offline:
            log.warn(
                "all_flags() called, but client is in offline mode. Returning None"
            )
            return None

        if not self.is_initialized():
            if self._store.initialized:
                log.warn(
                    "all_flags() called before client has finished initializing! Using last known values from feature store"
                )
            else:
                log.warn(
                    "all_flags() called before client has finished initializing! Feature store unavailable - returning None"
                )
                return None

        if user is None or user.get('key') is None:
            log.warn(
                "User or user key is None when calling all_flags(). Returning None."
            )
            return None

        def cb(all_flags):
            try:
                return self._evaluate_multi(user, all_flags)
            except Exception as e:
                log.error("Exception caught in all_flags: " + e.message +
                          " for user: " + str(user))
            return {}

        return self._store.all(FEATURES, cb)
Beispiel #9
0
        def cb(flag):
            try:
                if not flag:
                    log.warn("Feature Flag key: " + key +
                             " not found in Feature Store. Returning default.")
                    send_event(default)
                    return default

                return self._evaluate_and_send_events(flag, user, default)

            except Exception as e:
                log.error("Exception caught in variation: " + e.message +
                          " for flag key: " + key + " and user: " + str(user))

            return default
Beispiel #10
0
    def variation(self, key, user, default):
        default = self._config.get_default(key, default)
        self._sanitize_user(user)

        if self._config.offline:
            return default

        def send_event(value, version=None):
            self._send_event({
                'kind': 'feature',
                'key': key,
                'user': user,
                'value': value,
                'default': default,
                'version': version
            })

        if not self.is_initialized():
            if self._store.initialized:
                log.warn(
                    "Feature Flag evaluation attempted before client has initialized - using last known values from feature store for feature key: "
                    + key)
            else:
                log.warn(
                    "Feature Flag evaluation attempted before client has initialized! Feature store unavailable - returning default: "
                    + str(default) + " for feature key: " + key)
                send_event(default)
                return default

        if user is None or user.get('key') is None:
            log.warn(
                "Missing user or user key when evaluating Feature Flag key: " +
                key + ". Returning default.")
            send_event(default)
            return default

        if user.get('key', "") == "":
            log.warn(
                "User key is blank. Flag evaluation will proceed, but the user will not be stored in LaunchDarkly."
            )

        def cb(flag):
            try:
                if not flag:
                    log.info("Feature Flag key: " + key +
                             " not found in Feature Store. Returning default.")
                    send_event(default)
                    return default

                return self._evaluate_and_send_events(flag, user, default)

            except Exception as e:
                log.error("Exception caught in variation: " + e.message +
                          " for flag key: " + key + " and user: " + str(user))

            return default

        return self._store.get(FEATURES, key, cb)
 def _start_auto_updater(self):
     resolved_paths = []
     for path in self._paths:
         try:
             resolved_paths.append(os.path.realpath(path))
         except:
             log.warn(
                 'Cannot watch for changes to data file "%s" because it is an invalid path'
                 % path)
     if have_watchdog and not self._force_polling:
         return _FileDataSource.WatchdogAutoUpdater(resolved_paths,
                                                    self._load_all)
     else:
         return _FileDataSource.PollingAutoUpdater(resolved_paths,
                                                   self._load_all,
                                                   self._poll_interval)
Beispiel #12
0
    def identify(self, user):
        """Registers the user.

        This simply creates an analytics event that will transmit the given user properties to
        LaunchDarkly, so that the user will be visible on your dashboard even if you have not
        evaluated any flags for that user. It has no other effect.

        :param dict user: attributes of the user to register
        """
        if user is None or user.get('key') is None:
            log.warn("Missing user or user key when calling identify().")
        else:
            self._send_event({
                'kind': 'identify',
                'key': str(user.get('key')),
                'user': user
            })
Beispiel #13
0
    def variation(self, key, user, default):
        default = self._config.get_default(key, default)
        self._sanitize_user(user)

        if self._config.offline:
            return default

        def send_event(value, version=None):
            self._send_event({
                'kind': 'feature',
                'key': key,
                'user': user,
                'value': value,
                'default': default,
                'version': version
            })

        if not self.is_initialized():
            log.warn(
                "Feature Flag evaluation attempted before client has finished initializing! Returning default: "
                + str(default) + " for feature key: " + key)
            send_event(default)
            return default

        if user is None or user.get('key') is None:
            log.warn(
                "Missing user or user key when evaluating Feature Flag key: " +
                key + ". Returning default.")
            send_event(default)
            return default

        if user.get('key', "") == "":
            log.warn(
                "User key is blank. Flag evaluation will proceed, but the user will not be stored in LaunchDarkly."
            )

        flag = self._store.get(key)
        if not flag:
            log.warn("Feature Flag key: " + key +
                     " not found in Feature Store. Returning default.")
            send_event(default)
            return default

        value, events = evaluate(flag, user, self._store)
        for event in events or []:
            self._send_event(event)
            log.debug("Sending event: " + str(event))

        if value is not None:
            send_event(value, flag.get('version'))
            return value

        send_event(default, flag.get('version'))
        return default
Beispiel #14
0
    def all_flags_state(self, user, **kwargs):
        """Returns an object that encapsulates the state of all feature flags for a given user,
        including the flag values and also metadata that can be used on the front end. 
        
        This method does not send analytics events back to LaunchDarkly.

        :param dict user: the end user requesting the feature flags
        :param kwargs: optional parameters affecting how the state is computed: set
          `client_side_only=True` to limit it to only flags that are marked for use with the
          client-side SDK (by default, all flags are included); set `with_reasons=True` to
          include evaluation reasons in the state (see `variation_detail`)
        :return: a FeatureFlagsState object (will never be None; its 'valid' property will be False
          if the client is offline, has not been initialized, or the user is None or has no key)
        :rtype: FeatureFlagsState
        """
        if self._config.offline:
            log.warn("all_flags_state() called, but client is in offline mode. Returning empty state")
            return FeatureFlagsState(False)

        if not self.is_initialized():
            if self._store.initialized:
                log.warn("all_flags_state() called before client has finished initializing! Using last known values from feature store")
            else:
                log.warn("all_flags_state() called before client has finished initializing! Feature store unavailable - returning empty state")
                return FeatureFlagsState(False)

        if user is None or user.get('key') is None:
            log.warn("User or user key is None when calling all_flags_state(). Returning empty state.")
            return FeatureFlagsState(False)
        
        state = FeatureFlagsState(True)
        client_only = kwargs.get('client_side_only', False)
        with_reasons = kwargs.get('with_reasons', False)
        try:
            flags_map = self._store.all(FEATURES, lambda x: x)
        except Exception as e:
            log.error("Unable to read flags for all_flag_state: %s" % e)
            return FeatureFlagsState(False)
        
        for key, flag in flags_map.items():
            if client_only and not flag.get('clientSide', False):
                continue
            try:
                detail = evaluate(flag, user, self._store, False).detail
                state.add_flag(flag, detail.value, detail.variation_index,
                    detail.reason if with_reasons else None)
            except Exception as e:
                log.error("Error evaluating flag \"%s\" in all_flags_state: %s" % (key, e))
                log.debug(traceback.format_exc())
                reason = {'kind': 'ERROR', 'errorKind': 'EXCEPTION'}
                state.add_flag(flag, None, None, reason if with_reasons else None)
        
        return state
Beispiel #15
0
    def track(self, event_name, user, data=None):
        """Tracks that a user performed an event.

        LaunchDarkly automatically tracks pageviews and clicks that are specified in the Goals
        section of the dashboard. This can be used to track custom goals or other events that do
        not currently have goals.

        :param string event_name: the name of the event, which may correspond to a goal in A/B tests
        :param dict user: the attributes of the user
        :param data: optional additional data associated with the event
        """
        if user is None or user.get('key') is None:
            log.warn("Missing user or user key when calling track().")
        else:
            self._send_event({
                'kind': 'custom',
                'key': event_name,
                'user': user,
                'data': data
            })
        def redis_get():
            r = None
            try:
                r = yield self._get_connection()
                """ :type: RedisClient """
                get_result = yield r.hget(self._features_key, key)
                if not get_result:
                    log.warn("Didn't get response from redis for key: " + key +
                             " Returning None.")
                    defer.returnValue(None)
                f_json = get_result.get(key)
                if f_json is None or f_json is "":
                    log.warn(
                        "TwistedRedisFeatureStore: feature flag with key: " +
                        key + " not found in Redis. Returning None.")
                    defer.returnValue(None)

                f = json.loads(f_json.decode('utf-8'))
                if f.get('deleted', False) is True:
                    log.warn(
                        "TwistedRedisFeatureStore: get returned deleted flag from Redis. Returning None."
                    )
                    defer.returnValue(None)
                self._cache[key] = f
                defer.returnValue(f)
            except Exception as e:
                log.error("Could not connect to Redis using url: " +
                          self._url + " with error message: " + e.message)
                defer.returnValue(None)
            finally:
                if r:
                    r.quit()
            defer.returnValue(None)
Beispiel #17
0
    def _evaluate_internal(self, key, user, default, include_reasons_in_events):
        default = self._config.get_default(key, default)

        if self._config.offline:
            return EvaluationDetail(default, None, error_reason('CLIENT_NOT_READY'))
        
        if user is not None:
            self._sanitize_user(user)

        def send_event(value, variation=None, flag=None, reason=None):
            self._send_event({'kind': 'feature', 'key': key, 'user': user,
                              'value': value, 'variation': variation, 'default': default,
                              'version': flag.get('version') if flag else None,
                              'trackEvents': flag.get('trackEvents') if flag else None,
                              'debugEventsUntilDate': flag.get('debugEventsUntilDate') if flag else None,
                              'reason': reason if include_reasons_in_events else None})

        if not self.is_initialized():
            if self._store.initialized:
                log.warn("Feature Flag evaluation attempted before client has initialized - using last known values from feature store for feature key: " + key)
            else:
                log.warn("Feature Flag evaluation attempted before client has initialized! Feature store unavailable - returning default: "
                         + str(default) + " for feature key: " + key)
                reason = error_reason('CLIENT_NOT_READY')
                send_event(default, None, None, reason)
                return EvaluationDetail(default, None, reason)
        
        if user is not None and user.get('key', "") == "":
            log.warn("User key is blank. Flag evaluation will proceed, but the user will not be stored in LaunchDarkly.")

        flag = self._store.get(FEATURES, key, lambda x: x)
        if not flag:
            reason = error_reason('FLAG_NOT_FOUND')
            send_event(default, None, None, reason)
            return EvaluationDetail(default, None, reason)
        else:
            if user is None or user.get('key') is None:
                reason = error_reason('USER_NOT_SPECIFIED')
                send_event(default, None, flag, reason)
                return EvaluationDetail(default, None, reason)

            try:
                result = evaluate(flag, user, self._store, include_reasons_in_events)
                for event in result.events or []:
                    self._send_event(event)
                detail = result.detail
                if detail.is_default_value():
                    detail = EvaluationDetail(default, None, detail.reason)
                send_event(detail.value, detail.variation_index, flag, detail.reason)
                return detail
            except Exception as e:
                log.error("Unexpected error while evaluating feature flag \"%s\": %s" % (key, e))
                log.debug(traceback.format_exc())
                reason = error_reason('EXCEPTION')
                send_event(default, None, flag, reason)
                return EvaluationDetail(default, None, reason)
Beispiel #18
0
    def _make_update_processor(self, config, store, ready):
        if config.update_processor_class:
            log.info("Using user-specified update processor: " +
                     str(config.update_processor_class))
            return config.update_processor_class(config, store, ready)

        if config.offline or config.use_ldd:
            return NullUpdateProcessor(config, store, ready)

        if config.feature_requester_class:
            feature_requester = config.feature_requester_class(config)
        else:
            feature_requester = FeatureRequesterImpl(config)
        """ :type: FeatureRequester """

        if config.stream:
            return StreamingUpdateProcessor(config, feature_requester, store,
                                            ready)

        log.info("Disabling streaming API")
        log.warn(
            "You should only disable the streaming API if instructed to do so by LaunchDarkly support"
        )
        return PollingUpdateProcessor(config, feature_requester, store, ready)
Beispiel #19
0
    def all_flags(self, user):
        if self._config.offline:
            log.warn("all_flags() called, but client is in offline mode. Returning None")
            return None

        if not self.is_initialized():
            log.warn("all_flags() called before client has finished initializing! Returning None")
            return None

        if user is None or user.get('key') is None:
            log.warn("User or user key is None when calling all_flags(). Returning None.")
            return None

        return {k: evaluate(v, user, self._store)[0] for k, v in self._store.all().items() or {}}
Beispiel #20
0
 def _validate(self):
     if self.offline is False and self.sdk_key is None or self.sdk_key is '':
         log.warn("Missing or blank sdk_key.")
Beispiel #21
0
 def toggle(self, key, user, default):
     log.warn(
         "Deprecated method: toggle() called. Use variation() instead.")
     return self.variation(key, user, default)
Beispiel #22
0
    def __init__(self, sdk_key=None, config=None, start_wait=5):
        check_uwsgi()

        if config is not None and config.sdk_key is not None and sdk_key is not None:
            raise Exception(
                "LaunchDarkly client init received both sdk_key and config with sdk_key. "
                "Only one of either is expected")

        if sdk_key is not None:
            log.warn(
                "Deprecated sdk_key argument was passed to init. Use config object instead."
            )
            self._config = Config(sdk_key=sdk_key)
        else:
            self._config = config or Config.default()
        self._config._validate()

        self._session = CacheControl(requests.Session())
        self._queue = queue.Queue(self._config.events_max_pending)
        self._event_consumer = None
        self._lock = Lock()

        self._store = self._config.feature_store
        """ :type: FeatureStore """

        if self._config.offline:
            log.info("Started LaunchDarkly Client in offline mode")
            return

        if self._config.send_events:
            self._event_consumer = self._config.event_consumer_class(
                self._queue, self._config)
            self._event_consumer.start()

        if self._config.use_ldd:
            log.info("Started LaunchDarkly Client in LDD mode")
            return

        if self._config.feature_requester_class:
            self._feature_requester = self._config.feature_requester_class(
                self._config)
        else:
            self._feature_requester = FeatureRequesterImpl(self._config)
        """ :type: FeatureRequester """

        update_processor_ready = threading.Event()

        if self._config.update_processor_class:
            log.info("Using user-specified update processor: " +
                     str(self._config.update_processor_class))
            self._update_processor = self._config.update_processor_class(
                self._config, self._feature_requester, self._store,
                update_processor_ready)
        else:
            if self._config.stream:
                self._update_processor = StreamingUpdateProcessor(
                    self._config, self._feature_requester, self._store,
                    update_processor_ready)
            else:
                log.info("Disabling streaming API")
                log.warn(
                    "You should only disable the streaming API if instructed to do so by LaunchDarkly support"
                )
                self._update_processor = PollingUpdateProcessor(
                    self._config, self._feature_requester, self._store,
                    update_processor_ready)
        """ :type: UpdateProcessor """

        self._update_processor.start()
        log.info("Waiting up to " + str(start_wait) +
                 " seconds for LaunchDarkly client to initialize...")
        update_processor_ready.wait(start_wait)

        if self._update_processor.initialized() is True:
            log.info("Started LaunchDarkly Client: OK")
        else:
            log.warn(
                "Initialization timeout exceeded for LaunchDarkly Client or an error occurred. "
                "Feature Flags may not yet be available.")
Beispiel #23
0
    def __init__(self, sdk_key, config=None, start_wait=5):
        check_uwsgi()
        self._sdk_key = sdk_key
        self._config = config or Config.default()
        self._session = CacheControl(requests.Session())
        self._queue = queue.Queue(self._config.events_max_pending)
        self._event_consumer = None
        self._lock = Lock()

        self._store = self._config.feature_store
        """ :type: FeatureStore """

        if self._config.offline:
            self._config.events_enabled = False
            log.info("Started LaunchDarkly Client in offline mode")
            return

        if self._config.events_enabled:
            self._event_consumer = self._config.event_consumer_class(
                self._queue, self._sdk_key, self._config)
            self._event_consumer.start()

        if self._config.use_ldd:
            log.info("Started LaunchDarkly Client in LDD mode")
            return

        if self._config.feature_requester_class:
            self._feature_requester = self._config.feature_requester_class(
                sdk_key, self._config)
        else:
            self._feature_requester = FeatureRequesterImpl(
                sdk_key, self._config)
        """ :type: FeatureRequester """

        update_processor_ready = threading.Event()

        if self._config.update_processor_class:
            log.info("Using user-specified update processor: " +
                     str(self._config.update_processor_class))
            self._update_processor = self._config.update_processor_class(
                sdk_key, self._config, self._feature_requester, self._store,
                update_processor_ready)
        else:
            if self._config.stream:
                self._update_processor = StreamingUpdateProcessor(
                    sdk_key, self._config, self._feature_requester,
                    self._store, update_processor_ready)
            else:
                self._update_processor = PollingUpdateProcessor(
                    sdk_key, self._config, self._feature_requester,
                    self._store, update_processor_ready)
        """ :type: UpdateProcessor """

        self._update_processor.start()
        log.info("Waiting up to " + str(start_wait) +
                 " seconds for LaunchDarkly client to initialize...")
        update_processor_ready.wait(start_wait)

        if self._update_processor.initialized() is True:
            log.info("Started LaunchDarkly Client: OK")
        else:
            log.warn(
                "Initialization timeout exceeded for LaunchDarkly Client or an error occurred. "
                "Feature Flags may not yet be available.")
Beispiel #24
0
    def __init__(self, sdk_key=None, config=None, start_wait=5):
        """Constructs a new LDClient instance.

        :param string sdk_key: the SDK key for your LaunchDarkly environment
        :param ldclient.config.Config config: optional custom configuration
        :param float start_wait: the number of seconds to wait for a successful connection to LaunchDarkly
        """
        check_uwsgi()

        if config is not None and config.sdk_key is not None and sdk_key is not None:
            raise Exception(
                "LaunchDarkly client init received both sdk_key and config with sdk_key. "
                "Only one of either is expected")

        if sdk_key is not None:
            log.warn(
                "Deprecated sdk_key argument was passed to init. Use config object instead."
            )
            self._config = Config(sdk_key=sdk_key)
        else:
            self._config = config or Config.default()
        self._config._validate()

        self._event_processor = None
        self._lock = Lock()

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

        if self._config.offline or not self._config.send_events:
            self._event_processor = NullEventProcessor()
        else:
            self._event_processor = self._config.event_processor_class(
                self._config)

        if self._config.offline:
            log.info("Started LaunchDarkly Client in offline mode")
            return

        if self._config.use_ldd:
            log.info("Started LaunchDarkly Client in LDD mode")
            return

        update_processor_ready = threading.Event()

        if self._config.update_processor_class:
            log.info("Using user-specified update processor: " +
                     str(self._config.update_processor_class))
            self._update_processor = self._config.update_processor_class(
                self._config, self._store, update_processor_ready)
        else:
            if self._config.feature_requester_class:
                feature_requester = self._config.feature_requester_class(
                    self._config)
            else:
                feature_requester = FeatureRequesterImpl(self._config)
            """ :type: FeatureRequester """

            if self._config.stream:
                self._update_processor = StreamingUpdateProcessor(
                    self._config, feature_requester, self._store,
                    update_processor_ready)
            else:
                log.info("Disabling streaming API")
                log.warn(
                    "You should only disable the streaming API if instructed to do so by LaunchDarkly support"
                )
                self._update_processor = PollingUpdateProcessor(
                    self._config, feature_requester, self._store,
                    update_processor_ready)
        """ :type: UpdateProcessor """

        self._update_processor.start()
        log.info("Waiting up to " + str(start_wait) +
                 " seconds for LaunchDarkly client to initialize...")
        update_processor_ready.wait(start_wait)

        if self._update_processor.initialized() is True:
            log.info("Started LaunchDarkly Client: OK")
        else:
            log.warn(
                "Initialization timeout exceeded for LaunchDarkly Client or an error occurred. "
                "Feature Flags may not yet be available.")
Beispiel #25
0
    def all_flags_state(self, user, **kwargs):
        """Returns an object that encapsulates the state of all feature flags for a given user,
        including the flag values and also metadata that can be used on the front end. See the
        JavaScript SDK Reference Guide on
        `Bootstrapping <https://docs.launchdarkly.com/docs/js-sdk-reference#section-bootstrapping>`_.
        
        This method does not send analytics events back to LaunchDarkly.

        :param dict user: the end user requesting the feature flags
        :param kwargs: optional parameters affecting how the state is computed - see below

        :Keyword Arguments:
          * **client_side_only** (*boolean*) --
            set to True to limit it to only flags that are marked for use with the client-side SDK
            (by default, all flags are included)
          * **with_reasons** (*boolean*) --
            set to True to include evaluation reasons in the state (see :func:`variation_detail()`)
          * **details_only_for_tracked_flags** (*boolean*) --
            set to True to omit any metadata that is normally only used for event generation, such
            as flag versions and evaluation reasons, unless the flag has event tracking or debugging
            turned on

        :return: a FeatureFlagsState object (will never be None; its ``valid`` property will be False
          if the client is offline, has not been initialized, or the user is None or has no key)
        :rtype: FeatureFlagsState
        """
        if self._config.offline:
            log.warn(
                "all_flags_state() called, but client is in offline mode. Returning empty state"
            )
            return FeatureFlagsState(False)

        if not self.is_initialized():
            if self._store.initialized:
                log.warn(
                    "all_flags_state() called before client has finished initializing! Using last known values from feature store"
                )
            else:
                log.warn(
                    "all_flags_state() called before client has finished initializing! Feature store unavailable - returning empty state"
                )
                return FeatureFlagsState(False)

        if user is None or user.get('key') is None:
            log.warn(
                "User or user key is None when calling all_flags_state(). Returning empty state."
            )
            return FeatureFlagsState(False)

        state = FeatureFlagsState(True)
        client_only = kwargs.get('client_side_only', False)
        with_reasons = kwargs.get('with_reasons', False)
        details_only_if_tracked = kwargs.get('details_only_for_tracked_flags',
                                             False)
        try:
            flags_map = self._store.all(FEATURES, lambda x: x)
            if flags_map is None:
                raise ValueError("feature store error")
        except Exception as e:
            log.error("Unable to read flags for all_flag_state: %s" % repr(e))
            return FeatureFlagsState(False)

        for key, flag in flags_map.items():
            if client_only and not flag.get('clientSide', False):
                continue
            try:
                detail = evaluate(flag, user, self._store, False).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:
                log.error(
                    "Error evaluating flag \"%s\" in all_flags_state: %s" %
                    (key, repr(e)))
                log.debug(traceback.format_exc())
                reason = {'kind': 'ERROR', 'errorKind': 'EXCEPTION'}
                state.add_flag(flag, None, None,
                               reason if with_reasons else None,
                               details_only_if_tracked)

        return state
Beispiel #26
0
 def _validate(self):
     if self.offline is False and self.sdk_key is None or self.sdk_key is '':
         log.warn("Missing or blank sdk_key.")