Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions ldclient/async_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ def __init__(
application: Optional[dict] = None,
hooks: Optional[List[AsyncHook]] = None,
plugins: Optional[List[AsyncPlugin]] = None,
enable_event_compression: bool = False,
enable_event_compression: bool = True,
omit_anonymous_contexts: bool = False,
payload_filter_key: Optional[str] = None,
datasystem_config: Optional[AsyncDataSystemConfig] = None,
Expand Down Expand Up @@ -245,7 +245,11 @@ def __init__(
:param application: Optional properties for setting application metadata. See :py:attr:`~application`
:param hooks: Hooks provide entrypoints which allow for observation of SDK functions.
:param plugins: A list of plugins to be used with the SDK. Plugin support is currently experimental and subject to change.
:param enable_event_compression: Whether or not to enable GZIP compression for outgoing events.
:param enable_event_compression: Whether to use GZIP compression for the event payloads that the SDK
sends. The default is True. Set this to False if you send events to a Relay Proxy older than
version 8.9, because those versions cannot accept compressed events. Relay Proxy users can also
set this to False to prevent extra CPU use, because the Relay Proxy decompresses and recompresses
the payloads.
: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. Payload filtering is not supported with the FDv2 data system, so this has no effect on FDv2 requests.
:param datasystem_config: Configuration for the data system, which controls how the SDK gets flag and segment data. When set, it overrides ``feature_store``, ``update_processor_class``, ``stream``, and ``use_ldd``.
Expand Down
8 changes: 6 additions & 2 deletions ldclient/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ def __init__(
application: Optional[dict] = None,
hooks: Optional[List[Hook]] = None,
plugins: Optional[List[Plugin]] = None,
enable_event_compression: bool = False,
enable_event_compression: bool = True,
omit_anonymous_contexts: bool = False,
payload_filter_key: Optional[str] = None,
datasystem_config: Optional[DataSystemConfig] = None,
Expand Down Expand Up @@ -394,7 +394,11 @@ def __init__(
:param application: Optional properties for setting application metadata. See :py:attr:`~application`
:param hooks: Hooks provide entrypoints which allow for observation of SDK functions.
:param plugins: A list of plugins to be used with the SDK. Plugin support is currently experimental and subject to change.
:param enable_event_compression: Whether or not to enable GZIP compression for outgoing events.
:param enable_event_compression: Whether to use GZIP compression for the event payloads that the SDK
sends. The default is True. Set this to False if you send events to a Relay Proxy older than
version 8.9, because those versions cannot accept compressed events. Relay Proxy users can also
set this to False to prevent extra CPU use, because the Relay Proxy decompresses and recompresses
the payloads.
: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. Payload filtering is not supported with the FDv2 data system, so this has no effect on FDv2 requests.
:param datasystem_config: Configuration for the data system, which controls how the SDK gets flag and segment data. When set, it overrides ``feature_store``, ``update_processor_class``, ``stream``, and ``use_ldd``.
Expand Down
6 changes: 5 additions & 1 deletion ldclient/testing/http_util.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import gzip
import json
import queue
import socket
Expand Down Expand Up @@ -122,7 +123,10 @@ def __init__(self, request):
self.headers = request.headers
content_length = int(request.headers.get('content-length', 0))
if content_length:
self.body = request.rfile.read(content_length).decode('UTF-8')
raw = request.rfile.read(content_length)
if request.headers.get('content-encoding') == 'gzip':
raw = gzip.decompress(raw)
self.body = raw.decode('UTF-8')
else:
self.body = None

Expand Down
28 changes: 24 additions & 4 deletions ldclient/testing/impl/events/test_async_event_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
EventInputIdentify
)
from ldclient.testing.builders import FlagBuilder
from ldclient.testing.stub_util import _MockHTTPHeaderDict
from ldclient.testing.stub_util import _MockHTTPHeaderDict, decode_request_body

pytestmark = pytest.mark.asyncio

Expand Down Expand Up @@ -83,6 +83,13 @@ def request(self, method, uri, headers=None, data=None, timeout=None, proxy=None

@property
def request_data(self):
"""Return the body of the last request. Decompress the body if it is gzip-encoded."""
if len(self._recorded_requests) != 0:
return decode_request_body(*self._recorded_requests[-1])

@property
def raw_request_data(self):
"""Return the body of the last request as the SDK sent it."""
if len(self._recorded_requests) != 0:
return self._recorded_requests[-1][1]

Expand Down Expand Up @@ -481,15 +488,28 @@ async def event_consumer():
assert had_no_more


async def test_event_payload_is_gzip_compressed_when_enabled():
async def test_event_payload_is_gzip_compressed_by_default():
mock_http = MockAioHttp()
async with make_processor(mock_http, enable_event_compression=True) as ep:
async with make_processor(mock_http) as ep:
ep.send_event(EventInputIdentify(timestamp, context))
ep.flush()
await ep._wait_until_inactive()

assert mock_http.request_headers.get('Content-Encoding') == 'gzip'
output = json.loads(gzip.decompress(mock_http.request_data))
output = json.loads(gzip.decompress(mock_http.raw_request_data))
assert len(output) == 1
assert output[0]['kind'] == 'identify'


async def test_event_payload_is_not_compressed_when_disabled():
mock_http = MockAioHttp()
async with make_processor(mock_http, enable_event_compression=False) as ep:
ep.send_event(EventInputIdentify(timestamp, context))
ep.flush()
await ep._wait_until_inactive()

assert mock_http.request_headers.get('Content-Encoding') is None
output = json.loads(mock_http.raw_request_data)
assert len(output) == 1
assert output[0]['kind'] == 'identify'

Expand Down
25 changes: 25 additions & 0 deletions ldclient/testing/impl/events/test_event_processor.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import gzip
import json
import time
import uuid
Expand Down Expand Up @@ -765,6 +766,30 @@ def verify_recoverable_http_error(status):
assert mock_http.request_data is not None


def test_event_payload_is_gzip_compressed_by_default():
with DefaultTestProcessor() as ep:
ep.send_event(EventInputIdentify(timestamp, context))
ep.flush()
ep._wait_until_inactive()

assert mock_http.request_headers.get('Content-Encoding') == 'gzip'
output = json.loads(gzip.decompress(mock_http.raw_request_data))
assert len(output) == 1
assert output[0]['kind'] == 'identify'


def test_event_payload_is_not_compressed_when_disabled():
with DefaultTestProcessor(enable_event_compression=False) as ep:
ep.send_event(EventInputIdentify(timestamp, context))
ep.flush()
ep._wait_until_inactive()

assert mock_http.request_headers.get('Content-Encoding') is None
output = json.loads(mock_http.raw_request_data)
assert len(output) == 1
assert output[0]['kind'] == 'identify'


def test_event_payload_id_is_sent():
with DefaultEventProcessor(Config(sdk_key='SDK_KEY'), mock_http) as ep:
ep.send_event(EventInputIdentify(timestamp, context))
Expand Down
15 changes: 15 additions & 0 deletions ldclient/testing/stub_util.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import gzip
import json
from email.utils import formatdate

Expand All @@ -11,6 +12,13 @@
from ldclient.testing.http_util import ChunkedResponse, JsonResponse


def decode_request_body(headers, body):
"""Return the request body. Decompress the body if the headers show gzip encoding."""
if headers is not None and headers.get('Content-Encoding') == 'gzip':
return gzip.decompress(body)
return body


def item_as_json(item):
return item.to_json_dict() if isinstance(item, ModelEntity) else item

Expand Down Expand Up @@ -141,6 +149,13 @@ def clear(self):

@property
def request_data(self):
"""Return the body of the last request. Decompress the body if it is gzip-encoded."""
if len(self._recorded_requests) != 0:
return decode_request_body(*self._recorded_requests[-1])

@property
def raw_request_data(self):
"""Return the body of the last request as the SDK sent it."""
if len(self._recorded_requests) != 0:
return self._recorded_requests[-1][1]

Expand Down
14 changes: 14 additions & 0 deletions ldclient/testing/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,20 @@ def test_a_configured_initial_reconnect_delay_is_reported_as_given(config_class,
assert config.initial_reconnect_delay == configured


@pytest.mark.parametrize("config_class", CONFIG_CLASSES, ids=CONFIG_IDS)
def test_event_compression_is_enabled_by_default(config_class):
config = config_class(sdk_key="SDK_KEY")

assert config.enable_event_compression


@pytest.mark.parametrize("config_class", CONFIG_CLASSES, ids=CONFIG_IDS)
def test_event_compression_can_be_disabled(config_class):
config = config_class(sdk_key="SDK_KEY", enable_event_compression=False)

assert not config.enable_event_compression


def test_can_set_valid_diagnostic_interval():
config = Config(sdk_key="SDK_KEY", diagnostic_recording_interval=61)
assert config.diagnostic_recording_interval == 61
Expand Down
Loading