diff --git a/contract-tests/client_entity.py b/contract-tests/client_entity.py index 8b0c097f..02c44aba 100644 --- a/contract-tests/client_entity.py +++ b/contract-tests/client_entity.py @@ -27,12 +27,16 @@ def __init__(self, tag, config): streaming = config["streaming"] if streaming.get("baseUri") is not None: opts["stream_uri"] = streaming["baseUri"] + if streaming.get("filter") is not None: + opts["payload_filter_key"] = streaming["filter"] _set_optional_time_prop(streaming, "initialRetryDelayMs", opts, "initial_reconnect_delay") else: opts['stream'] = False polling = config["polling"] if polling.get("baseUri") is not None: opts["base_uri"] = polling["baseUri"] + if polling.get("filter") is not None: + opts["payload_filter_key"] = polling["filter"] _set_optional_time_prop(polling, "pollIntervalMs", opts, "poll_interval") if config.get("events") is not None: diff --git a/contract-tests/service.py b/contract-tests/service.py index be7ef23f..ffa6d3e2 100644 --- a/contract-tests/service.py +++ b/contract-tests/service.py @@ -65,6 +65,7 @@ def status(): 'all-flags-details-only-for-tracked-flags', 'big-segments', 'context-type', + 'filtering', 'secure-mode-hash', 'tags', 'migrations', diff --git a/ldclient/config.py b/ldclient/config.py index 59248996..d73001cc 100644 --- a/ldclient/config.py +++ b/ldclient/config.py @@ -182,6 +182,7 @@ def __init__( hooks: Optional[List[Hook]] = None, enable_event_compression: bool = False, omit_anonymous_contexts: bool = False, + payload_filter_key: Optional[str] = None, ): """ :param sdk_key: The SDK key for your LaunchDarkly account. This is always required. @@ -250,6 +251,7 @@ def __init__( :param hooks: Hooks provide entrypoints which allow for observation of SDK functions. :param enable_event_compression: Whether or not to enable GZIP compression for outgoing events. :param omit_anonymous_contexts: Sets whether anonymous contexts should be omitted from index and identify events. + :param payload_filter_key: The payload filter is used to selectively limited the flags and segments delivered in the data source payload. """ self.__sdk_key = sdk_key @@ -285,6 +287,7 @@ def __init__( self.__hooks = [hook for hook in hooks if isinstance(hook, Hook)] if hooks else [] self.__enable_event_compression = enable_event_compression self.__omit_anonymous_contexts = omit_anonymous_contexts + self.__payload_filter_key = payload_filter_key self._data_source_update_sink: Optional[DataSourceUpdateSink] = None def copy_with_new_sdk_key(self, new_sdk_key: str) -> 'Config': @@ -484,6 +487,26 @@ def omit_anonymous_contexts(self) -> bool: """ return self.__omit_anonymous_contexts + @property + def payload_filter_key(self) -> Optional[str]: + """ + LaunchDarkly Server SDKs historically downloaded all flag configuration + and segments for a particular environment during initialization. + + For some customers, this is an unacceptably large amount of data, and + has contributed to performance issues within their products. + + Filtered environments aim to solve this problem. By allowing customers + to specify subsets of an environment's flags using a filter key, SDKs + will initialize faster and use less memory. + + This payload filter key only applies to the default streaming and + polling data sources. It will not affect TestData or FileData data + sources, nor will it be applied to any data source provided through the + {#data_source} config property. + """ + return self.__payload_filter_key + @property def data_source_update_sink(self) -> Optional[DataSourceUpdateSink]: """ diff --git a/ldclient/impl/datasource/feature_requester.py b/ldclient/impl/datasource/feature_requester.py index ac7a959e..83f044db 100644 --- a/ldclient/impl/datasource/feature_requester.py +++ b/ldclient/impl/datasource/feature_requester.py @@ -4,6 +4,7 @@ import json from collections import namedtuple +from urllib import parse import urllib3 @@ -24,6 +25,8 @@ def __init__(self, config): self._http = _http_factory(config).create_pool_manager(1, config.base_uri) self._config = config self._poll_uri = config.base_uri + LATEST_ALL_URI + if config.payload_filter_key is not None: + self._poll_uri += '?%s' % parse.urlencode({'filter': config.payload_filter_key}) def get_all_data(self): uri = self._poll_uri diff --git a/ldclient/impl/datasource/streaming.py b/ldclient/impl/datasource/streaming.py index d4f89b59..eb3c6724 100644 --- a/ldclient/impl/datasource/streaming.py +++ b/ldclient/impl/datasource/streaming.py @@ -3,6 +3,7 @@ from collections import namedtuple from threading import Thread from typing import Optional +from urllib import parse from ld_eventsource import SSEClient from ld_eventsource.actions import Event, Fault @@ -35,6 +36,8 @@ def __init__(self, config, store, ready, diagnostic_accumulator): Thread.__init__(self, name="ldclient.datasource.streaming") self.daemon = True self._uri = config.stream_base_uri + STREAM_ALL_PATH + if config.payload_filter_key is not None: + self._uri += '?%s' % parse.urlencode({'filter': config.payload_filter_key}) self._config = config self._data_source_update_sink = config.data_source_update_sink self._store = store diff --git a/ldclient/impl/http.py b/ldclient/impl/http.py index 016ea5bc..27864fd8 100644 --- a/ldclient/impl/http.py +++ b/ldclient/impl/http.py @@ -1,5 +1,5 @@ from os import environ -from typing import Tuple +from typing import Optional, Tuple from urllib.parse import urlparse import certifi