def test_get_one_flag_throws_on_error():
    with start_server() as server:
        config = Config(sdk_key='sdk-key', base_uri=server.uri)
        fr = FeatureRequesterImpl(config)
        with pytest.raises(UnsuccessfulResponseException) as e:
            fr.get_one(FEATURES, 'didnt-set-up-a-response-for-this-flag')
        assert e.value.status == 404
def test_get_one_flag_returns_data():
    with start_server() as server:
        config = Config(sdk_key='sdk-key', base_uri=server.uri)
        fr = FeatureRequesterImpl(config)
        key = 'flag1'
        flag_data = {'key': key}
        server.setup_json_response('/sdk/latest-flags/' + key, flag_data)
        result = fr.get_one(FEATURES, key)
        assert result == flag_data
def test_get_one_flag_sends_wrapper_header_without_version():
    with start_server() as server:
        config = Config(sdk_key='sdk-key',
                        base_uri=server.uri,
                        wrapper_name='Flask')
        fr = FeatureRequesterImpl(config)
        key = 'flag1'
        flag_data = {'key': key}
        server.setup_json_response('/sdk/latest-flags/' + key, flag_data)
        fr.get_one(FEATURES, key)
        req = server.require_request()
        assert req.headers.get('X-LaunchDarkly-Wrapper') == 'Flask'
def test_get_one_flag_sends_headers():
    with start_server() as server:
        config = Config(sdk_key='sdk-key', base_uri=server.uri)
        fr = FeatureRequesterImpl(config)
        key = 'flag1'
        flag_data = {'key': key}
        server.setup_json_response('/sdk/latest-flags/' + key, flag_data)
        fr.get_one(FEATURES, key)
        req = server.require_request()
        assert req.headers['Authorization'] == 'sdk-key'
        assert req.headers['User-Agent'] == 'PythonClient/' + VERSION
        assert req.headers.get('X-LaunchDarkly-Wrapper') is None
def test_get_all_data_sends_wrapper_header_without_version():
    with start_server() as server:
        config = Config(sdk_key='sdk-key',
                        base_uri=server.uri,
                        wrapper_name='Flask')
        fr = FeatureRequesterImpl(config)

        resp_data = {'flags': {}, 'segments': {}}
        server.for_path('/sdk/latest-all', JsonResponse(resp_data))

        fr.get_all_data()
        req = server.require_request()
        assert req.headers.get('X-LaunchDarkly-Wrapper') == 'Flask'
def test_get_all_data_sends_headers():
    with start_server() as server:
        config = Config(sdk_key='sdk-key', base_uri=server.uri)
        fr = FeatureRequesterImpl(config)

        resp_data = {'flags': {}, 'segments': {}}
        server.for_path('/sdk/latest-all', JsonResponse(resp_data))

        fr.get_all_data()
        req = server.require_request()
        assert req.headers['Authorization'] == 'sdk-key'
        assert req.headers['User-Agent'] == 'PythonClient/' + VERSION
        assert req.headers.get('X-LaunchDarkly-Wrapper') is None
def test_get_all_data_returns_data():
    with start_server() as server:
        config = Config(sdk_key='sdk-key', base_uri=server.uri)
        fr = FeatureRequesterImpl(config)

        flags = {'flag1': {'key': 'flag1'}}
        segments = {'segment1': {'key': 'segment1'}}
        resp_data = {'flags': flags, 'segments': segments}
        expected_data = {FEATURES: flags, SEGMENTS: segments}
        server.for_path('/sdk/latest-all', JsonResponse(resp_data))

        result = fr.get_all_data()
        assert result == expected_data
def _verify_https_proxy_is_used(server, config):
    fr = FeatureRequesterImpl(config)

    resp_data = {'flags': {}, 'segments': {}}
    server.setup_json_response(config.base_uri + '/sdk/latest-all', resp_data)

    # Our simple stub server implementation can't really do HTTPS proxying, so the request will fail, but
    # it can still record that it *got* the request, which proves that the request went to the proxy.
    try:
        fr.get_all_data()
    except:
        pass
    req = server.require_request()
    assert req.method == 'CONNECT'
def _verify_http_proxy_is_used(server, config):
    fr = FeatureRequesterImpl(config)

    resp_data = {'flags': {}, 'segments': {}}
    expected_data = {FEATURES: {}, SEGMENTS: {}}
    server.setup_json_response(config.base_uri + '/sdk/latest-all', resp_data)

    # For an insecure proxy request, our stub server behaves enough like the real thing to satisfy the
    # HTTP client, so we should be able to see the request go through. Note that the URI path will
    # actually be an absolute URI for a proxy request.
    result = fr.get_all_data()
    assert result == expected_data
    req = server.require_request()
    assert req.method == 'GET'
 def _feature_requester_proxy_test(server, config, secure):
     resp_data = {'flags': {}, 'segments': {}}
     expected_data = {FEATURES: {}, SEGMENTS: {}}
     server.for_path(config.base_uri + '/sdk/latest-all',
                     JsonResponse(resp_data))
     fr = FeatureRequesterImpl(config)
     if secure:
         try:
             fr.get_all_data()
         except:
             pass  # we expect this to fail because we don't have a real HTTPS proxy server
     else:
         result = fr.get_all_data()
         assert result == expected_data
예제 #11
0
    def _make_update_processor(self, config, store, ready,
                               diagnostic_accumulator):
        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, diagnostic_accumulator)

        log.info("Disabling streaming API")
        log.warning(
            "You should only disable the streaming API if instructed to do so by LaunchDarkly support"
        )
        return PollingUpdateProcessor(config, feature_requester, store, ready)
예제 #12
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:
            if self._store.__class__ == "RedisFeatureStore":
                log.info("Started LaunchDarkly Client in LDD mode")
                return
            log.error("LDD mode requires a RedisFeatureStore.")
            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:
            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:
            log.info("Started LaunchDarkly Client: OK")
        else:
            log.info("Initialization timeout exceeded for LaunchDarkly Client. Feature Flags may not yet be available.")
def test_get_one_flag_does_not_use_etags():
    with start_server() as server:
        config = Config(sdk_key='sdk-key', base_uri=server.uri)
        fr = FeatureRequesterImpl(config)

        etag = 'my-etag'
        key = 'flag1'
        flag_data = {'key': key}
        req_path = '/sdk/latest-flags/' + key
        server.setup_json_response(req_path, flag_data, {'Etag': etag})

        result = fr.get_one(FEATURES, key)
        assert result == flag_data
        req = server.require_request()
        assert 'If-None-Match' not in req.headers.keys()

        result = fr.get_one(FEATURES, key)
        assert result == flag_data
        req = server.require_request()
        assert 'If-None-Match' not in req.headers.keys(
        )  # did not send etag from previous request
def test_get_all_data_can_use_cached_data():
    with start_server() as server:
        config = Config(sdk_key='sdk-key', base_uri=server.uri)
        fr = FeatureRequesterImpl(config)

        etag1 = 'my-etag-1'
        etag2 = 'my-etag-2'
        resp_data1 = {'flags': {}, 'segments': {}}
        resp_data2 = {'flags': {'flag1': {'key': 'flag1'}}, 'segments': {}}
        expected_data1 = {FEATURES: {}, SEGMENTS: {}}
        expected_data2 = {FEATURES: {'flag1': {'key': 'flag1'}}, SEGMENTS: {}}
        req_path = '/sdk/latest-all'
        server.for_path(req_path, JsonResponse(resp_data1, {'Etag': etag1}))

        result = fr.get_all_data()
        assert result == expected_data1
        req = server.require_request()
        assert 'If-None-Match' not in req.headers.keys()

        server.for_path(req_path, BasicResponse(304, None, {'Etag': etag1}))

        result = fr.get_all_data()
        assert result == expected_data1
        req = server.require_request()
        assert req.headers['If-None-Match'] == etag1

        server.for_path(req_path, JsonResponse(resp_data2, {'Etag': etag2}))

        result = fr.get_all_data()
        assert result == expected_data2
        req = server.require_request()
        assert req.headers['If-None-Match'] == etag1

        server.for_path(req_path, BasicResponse(304, None, {'Etag': etag2}))

        result = fr.get_all_data()
        assert result == expected_data2
        req = server.require_request()
        assert req.headers['If-None-Match'] == etag2
예제 #15
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.")
예제 #16
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.events_enabled:
            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:
                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.")