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
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)
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
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.")
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.")