def get_all_data(self):
        uri = self._poll_uri
        hdrs = _headers(self._config)
        cache_entry = self._cache.get(uri)
        if cache_entry is not None:
            hdrs['If-None-Match'] = cache_entry.etag
        r = self._http.request('GET',
                               uri,
                               headers=hdrs,
                               timeout=urllib3.Timeout(
                                   connect=self._config.connect_timeout,
                                   read=self._config.read_timeout),
                               retries=1)
        throw_if_unsuccessful_response(r)
        if r.status == 304 and cache_entry is not None:
            data = cache_entry.data
            etag = cache_entry.etag
            from_cache = True
        else:
            data = json.loads(r.data.decode('UTF-8'))
            etag = r.getheader('ETag')
            from_cache = False
            if etag is not None:
                self._cache[uri] = CacheEntry(data=data, etag=etag)
        log.debug("%s response status:[%d] From cache? [%s] ETag:[%s]", uri,
                  r.status, from_cache, etag)

        return {FEATURES: data['flags'], SEGMENTS: data['segments']}
        def do_send(should_retry):
            # noinspection PyBroadException
            try:
                if isinstance(events, dict):
                    body = [events]
                else:
                    body = events

                json_body = jsonpickle.encode(body, unpicklable=False)
                log.debug('Sending events payload: ' + json_body)
                hdrs = _headers(self.sdk_key)
                uri = self._config.events_uri
                r = self._session.post(uri,
                                       headers=hdrs,
                                       timeout=(self._config.connect_timeout,
                                                self._config.read_timeout),
                                       data=json_body)
                r.raise_for_status()
            except ProtocolError as e:
                if e.args is not None and len(
                        e.args) > 1 and e.args[1] is not None:
                    inner = e.args[1]
                    if inner.errno is not None and inner.errno == errno.ECONNRESET and should_retry:
                        log.warning(
                            'ProtocolError exception caught while sending events. Retrying.'
                        )
                        do_send(False)
                else:
                    log.exception(
                        'Unhandled exception in event consumer. Analytics events were not processed.'
                    )
            except:
                log.exception(
                    'Unhandled exception in event consumer. Analytics events were not processed.'
                )
Example #3
0
 def _do_request(self, uri, allow_cache):
     hdrs = _headers(self._config.sdk_key)
     if allow_cache:
         cache_entry = self._cache.get(uri)
         if cache_entry is not None:
             hdrs['If-None-Match'] = cache_entry.etag
     r = self._http.request('GET',
                            uri,
                            headers=hdrs,
                            timeout=urllib3.Timeout(
                                connect=self._config.connect_timeout,
                                read=self._config.read_timeout),
                            retries=1)
     throw_if_unsuccessful_response(r)
     if r.status == 304 and allow_cache and cache_entry is not None:
         data = cache_entry.data
         etag = cache_entry.etag
         from_cache = True
     else:
         data = json.loads(r.data.decode('UTF-8'))
         etag = r.getheader('ETag')
         from_cache = False
         if allow_cache and etag is not None:
             self._cache[uri] = CacheEntry(data=data, etag=etag)
     log.debug("%s response status:[%d] From cache? [%s] ETag:[%s]", uri,
               r.status, from_cache, etag)
     return data
Example #4
0
 def process_message(store, requester, msg, ready):
     payload = json.loads(msg.data)
     log.debug("Received stream event {}".format(msg.event))
     if msg.event == 'put':
         store.init(payload)
         if not ready.is_set() and store.initialized:
             ready.set()
             log.info("StreamingUpdateProcessor initialized ok")
     elif msg.event == 'patch':
         key = payload['path'][1:]
         feature = payload['data']
         log.debug("Updating feature {}".format(key))
         store.upsert(key, feature)
     elif msg.event == "indirect/patch":
         key = payload['data']
         store.upsert(key, requester.get_one(key))
     elif msg.event == "indirect/put":
         store.init(requester.get_all())
         if not ready.is_set() and store.initialized:
             ready.set()
             log.info("StreamingUpdateProcessor initialized ok")
     elif msg.event == 'delete':
         key = payload['path'][1:]
         # noinspection PyShadowingNames
         version = payload['version']
         store.delete(key, version)
     else:
         log.warning('Unhandled event in stream processor: ' + msg.event)
Example #5
0
 def init(self, features):
     try:
         self._lock.lock()
         self._features = dict(features)
         self._initialized = True
         log.debug("Initialized feature store with " + str(len(features)) + " features")
     finally:
         self._lock.unlock()
Example #6
0
 def upsert(self, key, feature):
     try:
         self._lock.lock()
         f = self._features.get(key)
         if f is None or f['version'] < feature['version']:
             self._features[key] = feature
             log.debug("Updated feature {0} to version {1}".format(key, feature['version']))
     finally:
         self._lock.unlock()
Example #7
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)
Example #8
0
 def run(self):
     log.debug("Starting stream processor")
     self._running = True
     hdrs = _stream_headers(self._api_key)
     uri = self._config.stream_uri + "/"
     messages = SSEClient(uri, verify=self._config.verify, headers=hdrs)
     for msg in messages:
         if not self._running:
             break
         self.process_message(self._store, msg)
Example #9
0
 def run(self):
     log.debug("Starting stream processor")
     self._running = True
     hdrs = _stream_headers(self._api_key)
     uri = self._config.stream_uri + "/"
     messages = SSEClient(uri, verify=self._config.verify, headers=hdrs)
     for msg in messages:
         if not self._running:
             break
         self.process_message(self._store, msg)
Example #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():
            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
 def get_one(self, key):
     hdrs = _headers(self._sdk_key)
     uri = self._config.get_latest_features_uri + '/' + key
     log.debug("Getting one feature flag using uri: " + uri)
     r = self._session.get(uri,
                           headers=hdrs,
                           timeout=(self._config.connect_timeout,
                                    self._config.read_timeout))
     r.raise_for_status()
     feature = r.json()
     return feature
Example #12
0
 def init(self, all_data):
     try:
         self._lock.rlock()
         self._items.clear()
         self._items.update(all_data)
         self._initialized = True
         for k in all_data:
             log.debug("Initialized '%s' store with %d items", k.namespace,
                       len(all_data[k]))
     finally:
         self._lock.runlock()
 def get_all(self):
     hdrs = _headers(self._sdk_key)
     uri = self._config.get_latest_features_uri
     log.debug("Getting all flags using uri: " + uri)
     r = self._session.get(uri,
                           headers=hdrs,
                           timeout=(self._config.connect_timeout,
                                    self._config.read_timeout))
     r.raise_for_status()
     features = r.json()
     return features
 def init(self, all_data):
     """
     """
     try:
         self._lock.rlock()
         self._items.clear()
         self._items.update(all_data)
         self._initialized = True
         for k in all_data:
             log.debug("Initialized '%s' store with %d items", k.namespace, len(all_data[k]))
     finally:
         self._lock.runlock()
Example #15
0
 def upsert(self, kind, item):
     key = item['key']
     try:
         self._lock.rlock()
         itemsOfKind = self._items[kind]
         i = itemsOfKind.get(key)
         if i is None or i['version'] < item['version']:
             itemsOfKind[key] = item
             log.debug("Updated %s in '%s' to version %d", key,
                       kind.namespace, item['version'])
     finally:
         self._lock.runlock()
Example #16
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
Example #17
0
 def run(self):
     # noinspection PyBroadException
     try:
         json_body = json.dumps(self._event_body)
         log.debug('Sending diagnostic event: ' + json_body)
         _post_events_with_retry(
             self._http, self._config,
             self._config.events_base_uri + '/diagnostic', None, json_body,
             "diagnostic event")
     except Exception as e:
         log.warning(
             'Unhandled exception in event processor. Diagnostic event was not sent. [%s]',
             e)
 def upsert(self, kind, item):
     """
     """
     key = item['key']
     try:
         self._lock.rlock()
         itemsOfKind = self._items[kind]
         i = itemsOfKind.get(key)
         if i is None or i['version'] < item['version']:
             itemsOfKind[key] = item
             log.debug("Updated %s in '%s' to version %d", key, kind.namespace, item['version'])
     finally:
         self._lock.runlock()
Example #19
0
 def get(self, key, callback):
     try:
         self._lock.rlock()
         f = self._features.get(key)
         if f is None:
             log.debug("Attempted to get missing feature: " + str(key) + " Returning None")
             return callback(None)
         if 'deleted' in f and f['deleted']:
             log.debug("Attempted to get deleted feature: " + str(key) + " Returning None")
             return callback(None)
         return callback(f)
     finally:
         self._lock.runlock()
Example #20
0
 def dispatch_event(self):
     """
     Dispatch the event
     """
     # If last character is LF, strip it.
     if self.data.endswith('\n'):
         self.data = self.data[:-1]
     log.debug("Dispatching event %s[%s]: %s",
               self.event, self.id, self.data)
     event = Event(self.data, self.event, self.id, self.retry)
     self.on_event(event)
     if self.id:
         self.last_id = self.id
     self.reset()
Example #21
0
 def process_message(store, requester, msg):
     if msg.event == 'put':
         all_data = json.loads(msg.data)
         init_data = {
             FEATURES: all_data['data']['flags'],
             SEGMENTS: all_data['data']['segments']
         }
         log.debug("Received put event with %d flags and %d segments",
                   len(init_data[FEATURES]), len(init_data[SEGMENTS]))
         store.init(init_data)
         return True
     elif msg.event == 'patch':
         payload = json.loads(msg.data)
         path = payload['path']
         obj = payload['data']
         log.debug("Received patch event for %s, New version: [%d]", path,
                   obj.get("version"))
         target = StreamingUpdateProcessor._parse_path(path)
         if target is not None:
             store.upsert(target.kind, obj)
         else:
             log.warning("Patch for unknown path: %s", path)
     elif msg.event == "indirect/patch":
         path = msg.data
         log.debug("Received indirect/patch event for %s", path)
         target = StreamingUpdateProcessor._parse_path(path)
         if target is not None:
             store.upsert(target.kind,
                          requester.get_one(target.kind, target.key))
         else:
             log.warning("Indirect patch for unknown path: %s", path)
     elif msg.event == "indirect/put":
         log.debug("Received indirect/put event")
         store.init(requester.get_all_data())
         return True
     elif msg.event == 'delete':
         payload = json.loads(msg.data)
         path = payload['path']
         # noinspection PyShadowingNames
         version = payload['version']
         log.debug("Received delete event for %s, New version: [%d]", path,
                   version)
         target = StreamingUpdateProcessor._parse_path(path)
         if target is not None:
             store.delete(target.kind, target.key, version)
         else:
             log.warning("Delete for unknown path: %s", path)
     else:
         log.warning('Unhandled event in stream processor: ' + msg.event)
     return False
Example #22
0
 def dispatch_event(self):
     """
     Dispatch the event
     """
     # If last character is LF, strip it.
     if self.data.endswith('\n'):
         self.data = self.data[:-1]
     log.debug("Dispatching event %s[%s]: %s", self.event, self.id,
               self.data)
     event = Event(self.data, self.event, self.id, self.retry)
     self.on_event(event)
     if self.id:
         self.last_id = self.id
     self.reset()
 def run(self):
     # noinspection PyBroadException
     try:
         json_body = json.dumps(self._event_body)
         log.debug('Sending diagnostic event: ' + json_body)
         hdrs = _headers(self._config)
         uri = self._config.events_base_uri + '/diagnostic'
         r = self._http.request('POST', uri,
                                headers=hdrs,
                                timeout=urllib3.Timeout(connect=self._config.connect_timeout, read=self._config.read_timeout),
                                body=json_body,
                                retries=1)
     except Exception as e:
         log.warning(
             'Unhandled exception in event processor. Diagnostic event was not sent. [%s]', e)
Example #24
0
 def _do_send(self, output_events):
     # noinspection PyBroadException
     try:
         json_body = json.dumps(output_events)
         log.debug('Sending events payload: ' + json_body)
         payload_id = str(uuid.uuid4())
         r = _post_events_with_retry(
             self._http, self._config, self._config.events_uri, payload_id,
             json_body, "%d events" % len(self._payload.events))
         if r:
             self._response_fn(r)
         return r
     except Exception as e:
         log.warning(
             'Unhandled exception in event processor. Analytics events were not processed. [%s]',
             e)
 def get(self, kind, key, callback):
     """
     """
     try:
         self._lock.rlock()
         itemsOfKind = self._items[kind]
         item = itemsOfKind.get(key)
         if item is None:
             log.debug("Attempted to get missing key %s in '%s', returning None", key, kind.namespace)
             return callback(None)
         if 'deleted' in item and item['deleted']:
             log.debug("Attempted to get deleted key %s in '%s', returning None", key, kind.namespace)
             return callback(None)
         return callback(item)
     finally:
         self._lock.runlock()
Example #26
0
 def process_message(store, requester, msg):
     if msg.event == 'put':
         all_data = json.loads(msg.data)
         init_data = {
             FEATURES: all_data['data']['flags'],
             SEGMENTS: all_data['data']['segments']
         }
         log.debug("Received put event with %d flags and %d segments",
             len(init_data[FEATURES]), len(init_data[SEGMENTS]))
         store.init(init_data)
         return True
     elif msg.event == 'patch':
         payload = json.loads(msg.data)
         path = payload['path']
         obj = payload['data']
         log.debug("Received patch event for %s, New version: [%d]", path, obj.get("version"))
         target = StreamingUpdateProcessor._parse_path(path)
         if target is not None:
             store.upsert(target.kind, obj)
         else:
             log.warning("Patch for unknown path: %s", path)
     elif msg.event == "indirect/patch":
         path = msg.data
         log.debug("Received indirect/patch event for %s", path)
         target = StreamingUpdateProcessor._parse_path(path)
         if target is not None:
             store.upsert(target.kind, requester.get_one(target.kind, target.key))
         else:
             log.warning("Indirect patch for unknown path: %s", path)
     elif msg.event == "indirect/put":
         log.debug("Received indirect/put event")
         store.init(requester.get_all_data())
         return True
     elif msg.event == 'delete':
         payload = json.loads(msg.data)
         path = payload['path']
         # noinspection PyShadowingNames
         version = payload['version']
         log.debug("Received delete event for %s, New version: [%d]", path, version)
         target = StreamingUpdateProcessor._parse_path(path)
         if target is not None:
             store.delete(target.kind, target.key, version)
         else:
             log.warning("Delete for unknown path: %s", path)
     else:
         log.warning('Unhandled event in stream processor: ' + msg.event)
     return False
Example #27
0
 def get(self, kind, key, callback):
     try:
         self._lock.rlock()
         itemsOfKind = self._items[kind]
         item = itemsOfKind.get(key)
         if item is None:
             log.debug(
                 "Attempted to get missing key %s in '%s', returning None",
                 key, kind.namespace)
             return callback(None)
         if 'deleted' in item and item['deleted']:
             log.debug(
                 "Attempted to get deleted key %s in '%s', returning None",
                 key, kind.namespace)
             return callback(None)
         return callback(item)
     finally:
         self._lock.runlock()
Example #28
0
 def _do_send(self, output_events):
     # noinspection PyBroadException
     try:
         json_body = json.dumps(output_events)
         log.debug('Sending events payload: ' + json_body)
         hdrs = _headers(self._config.sdk_key)
         hdrs['X-LaunchDarkly-Event-Schema'] = str(__CURRENT_EVENT_SCHEMA__)
         uri = self._config.events_uri
         r = self._http.request('POST', uri,
                                headers=hdrs,
                                timeout=urllib3.Timeout(connect=self._config.connect_timeout, read=self._config.read_timeout),
                                body=json_body,
                                retries=1)
         self._response_fn(r)
         return r
     except Exception as e:
         log.warning(
             'Unhandled exception in event processor. Analytics events were not processed. [%s]', e)
    def _do_request(self, base_uri, allow_cache):
        hdrs = _headers(self._config.sdk_key, client='PythonMobile')
        method = "GET"
        body = None
        uri = base_uri
        cache_uri = uri + EVALX_GET + '/' + self._config.user_b64
        if self._config.use_report:
            method = 'REPORT'
            body = self._config.user_json
            hdrs.update({'Content-Type': 'application/json'})
            uri = uri + EVALX_REPORT
        else:
            uri = cache_uri
        if self._config.evaluation_reasons:
            uri += '?withReasons=true'
            cache_uri += '?withReasons=true'

        if allow_cache:
            cache_entry = self._cache.get(cache_uri)
            if cache_entry is not None:
                hdrs['If-None-Match'] = cache_entry.etag

        r = self._http.request(method,
                               uri,
                               headers=hdrs,
                               timeout=urllib3.Timeout(
                                   connect=self._config.connect_timeout,
                                   read=self._config.read_timeout),
                               retries=1,
                               body=body)
        throw_if_unsuccessful_response(r)
        if r.status == 304 and allow_cache and cache_entry is not None:
            data = cache_entry.data
            etag = cache_entry.etag
            from_cache = True
        else:
            data = json.loads(r.data.decode('UTF-8'))
            etag = r.getheader('ETag')
            from_cache = False
            if allow_cache and etag is not None:
                self._cache[uri] = CacheEntry(data=data, etag=etag)
        log.debug("%s response status:[%d] From cache? [%s] ETag:[%s]", uri,
                  r.status, from_cache, etag)
        return data
Example #30
0
    def process_message(store, requester, msg):
        if msg.event == 'put':
            all_data = json.loads(msg.data)

            for k,v in all_data.items():
                v['key'] = k

            init_data = {
                FEATURES: all_data
            }
            log.debug("Received put event with %d flags",
                len(init_data[FEATURES]))
            store.init(init_data)
            return True
        elif msg.event == 'patch':
            recv_time = time.time() * 1000
            payload = json.loads(msg.data)
            if payload.get('key') == 'locust-heartbeat':
                value = int(payload.get('value') or 0)
                duration = int((recv_time - value))
                request_success.fire(request_type='sse:flag-update', name='/meval', response_time=duration, response_length=0)
                
            log.debug("Received patch event for %s, New version: [%d]", payload.get('key'), payload.get("version"))
            store.upsert(FEATURES, payload)
        elif msg.event == 'ping':
            log.debug('Received ping event')
            all_data = requester.get_all_data()
            store.init(all_data)
            log.debug("Received flags after ping event with %d flags", len(all_data[FEATURES]))
            return True
            
        elif msg.event == 'delete':
            payload = json.loads(msg.data)
            key = payload.get('key')
            # noinspection PyShadowingNames
            version = payload['version']
            log.debug("Received delete event for %s, New version: [%d]", key, version)
            target = ParsedPath(kind = FEATURES, key = key)
            store.delete(target.kind, target.key, version)
        else:
            log.warning('Unhandled event in stream processor: ' + msg.event)
        return False
Example #31
0
 def get(self,
         kind: VersionedDataKind,
         key: str,
         callback: Callable[[Any], Any] = lambda x: x) -> Any:
     """
     """
     try:
         self._lock.rlock()
         itemsOfKind = self._items[kind]
         item = itemsOfKind.get(key)
         if item is None:
             log.debug(
                 "Attempted to get missing key %s in '%s', returning None",
                 key, kind.namespace)
             return callback(None)
         if 'deleted' in item and item['deleted']:
             log.debug(
                 "Attempted to get deleted key %s in '%s', returning None",
                 key, kind.namespace)
             return callback(None)
         return callback(item)
     finally:
         self._lock.runlock()
 def _do_request(self, uri, allow_cache):
     hdrs = _headers(self._config.sdk_key)
     if allow_cache:
         cache_entry = self._cache.get(uri)
         if cache_entry is not None:
             hdrs['If-None-Match'] = cache_entry.etag
     r = self._http.request('GET', uri,
                            headers=hdrs,
                            timeout=urllib3.Timeout(connect=self._config.connect_timeout, read=self._config.read_timeout),
                            retries=1)
     throw_if_unsuccessful_response(r)
     if r.status == 304 and cache_entry is not None:
         data = cache_entry.data
         etag = cache_entry.etag
         from_cache = True
     else:
         data = json.loads(r.data.decode('UTF-8'))
         etag = r.getheader('ETag')
         from_cache = False
         if allow_cache and etag is not None:
             self._cache[uri] = CacheEntry(data=data, etag=etag)
     log.debug("%s response status:[%d] From cache? [%s] ETag:[%s]",
         uri, r.status, from_cache, etag)
     return data
Example #33
0
 def do_send(should_retry):
     # noinspection PyBroadException
     try:
         json_body = self._serializer.serialize_events(events)
         log.debug('Sending events payload: ' + json_body)
         hdrs = _headers(self._config.sdk_key)
         uri = self._config.events_uri
         r = self._session.post(uri,
                                headers=hdrs,
                                timeout=(self._config.connect_timeout,
                                         self._config.read_timeout),
                                data=json_body)
         if r.status_code == 401:
             log.error(
                 'Received 401 error, no further events will be posted since SDK key is invalid'
             )
             self.stop()
             return
         r.raise_for_status()
     except ProtocolError as e:
         if e.args is not None and len(
                 e.args) > 1 and e.args[1] is not None:
             inner = e.args[1]
             if inner.errno is not None and inner.errno == errno.ECONNRESET and should_retry:
                 log.warning(
                     'ProtocolError exception caught while sending events. Retrying.'
                 )
                 do_send(False)
         else:
             log.warning(
                 'Unhandled exception in event consumer. Analytics events were not processed.',
                 exc_info=True)
     except:
         log.warning(
             'Unhandled exception in event consumer. Analytics events were not processed.',
             exc_info=True)
Example #34
0
 def run(self):
     log.debug("Starting event consumer")
     self._running = True
     while self._running:
         self.send()
Example #35
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
Example #36
0
    def _evaluate_internal(self, key, user, default, event_factory):
        default = self._config.get_default(key, default)

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

        if not self.is_initialized():
            if self._store.initialized:
                log.warning(
                    "Feature Flag evaluation attempted before client has initialized - using last known values from feature store for feature key: "
                    + key)
            else:
                log.warning(
                    "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')
                self._send_event(
                    event_factory.new_unknown_flag_event(
                        key, user, default, reason))
                return EvaluationDetail(default, None, reason)

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

        try:
            flag = self._store.get(FEATURES, key, lambda x: x)
        except Exception as e:
            log.error(
                "Unexpected error while retrieving feature flag \"%s\": %s" %
                (key, repr(e)))
            log.debug(traceback.format_exc())
            reason = error_reason('EXCEPTION')
            self._send_event(
                event_factory.new_unknown_flag_event(key, user, default,
                                                     reason))
            return EvaluationDetail(default, None, reason)
        if not flag:
            reason = error_reason('FLAG_NOT_FOUND')
            self._send_event(
                event_factory.new_unknown_flag_event(key, user, default,
                                                     reason))
            return EvaluationDetail(default, None, reason)
        else:
            if user is None or user.get('key') is None:
                reason = error_reason('USER_NOT_SPECIFIED')
                self._send_event(
                    event_factory.new_default_event(flag, user, default,
                                                    reason))
                return EvaluationDetail(default, None, reason)

            try:
                result = evaluate(flag, user, self._store, event_factory)
                for event in result.events or []:
                    self._send_event(event)
                detail = result.detail
                if detail.is_default_value():
                    detail = EvaluationDetail(default, None, detail.reason)
                self._send_event(
                    event_factory.new_eval_event(flag, user, detail, default))
                return detail
            except Exception as e:
                log.error(
                    "Unexpected error while evaluating feature flag \"%s\": %s"
                    % (key, repr(e)))
                log.debug(traceback.format_exc())
                reason = error_reason('EXCEPTION')
                self._send_event(
                    event_factory.new_default_event(flag, user, default,
                                                    reason))
                return EvaluationDetail(default, None, reason)
Example #37
0
 def run(self):
     log.debug("Starting event consumer")
     self._running = True
     while self._running:
         self.send()