Skip to content

Commit ddb8c6c

Browse files
committed
Merge branch 'development' of github.com:splitio/python-client into development
2 parents 54c4a7c + 7156bdc commit ddb8c6c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

75 files changed

+4655
-733
lines changed

.github/workflows/ci.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,20 @@ on:
1010

1111
jobs:
1212
test:
13-
runs-on: ubuntu-latest
13+
runs-on: ubuntu-20.04
1414
services:
1515
redis:
1616
image: redis
1717
ports:
1818
- 6379:6379
1919
steps:
2020
- name: Checkout code
21-
uses: actions/checkout@v2
21+
uses: actions/checkout@v3
2222
with:
2323
fetch-depth: 0
2424

2525
- name: Set up Python
26-
uses: actions/setup-python@v2
26+
uses: actions/setup-python@v3
2727
with:
2828
python-version: '3.6'
2929

CHANGES.txt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,18 @@
1+
9.3.0-rc1 (Jan 20, 2023)
2+
- Updated SDK telemetry storage, metrics and updater to be more effective and send less often.
3+
- Removed deprecated threading.Thread.setDaemon() method.
4+
5+
9.2.2 (Dec 13, 2022)
6+
- Fixed RedisSenderAdapter instantiation to store mtk keys.
7+
8+
9.2.1 (Dec 2, 2022)
9+
- Changed redis record type for impressions counts from list using rpush to hashed key using hincrby.
10+
- Apply Timeout Exception when incorrect SDK API Key is used.
11+
- Changed potential initial fetching segment Warning to Debug in logging.
12+
13+
9.2.0 (Oct 14, 2022)
14+
- Added a new impressions mode for the SDK called NONE , to be used in factory when there is no desire to capture impressions on an SDK factory to feed Split's analytics engine. Running NONE mode, the SDK will only capture unique keys evaluated for a particular feature flag instead of full blown impressions
15+
116
9.1.3 (July 25, 2022)
217
- Fixed synching missed segment(s) after receiving split update
318

LICENSE.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
Copyright © 2022 Split Software, Inc.
1+
Copyright © 2023 Split Software, Inc.
22

33
Licensed under the Apache License, Version 2.0 (the "License");
44
you may not use this file except in compliance with the License.

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
'pytest-cov',
1313
'importlib-metadata==4.2',
1414
'tomli==1.2.3',
15+
'iniconfig==1.1.1'
1516
]
1617

1718
INSTALL_REQUIRES = [

splitio/api/auth.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,19 @@
44
import json
55

66
from splitio.api import APIException
7-
from splitio.api.commons import headers_from_metadata
7+
from splitio.api.commons import headers_from_metadata, record_telemetry
8+
from splitio.util.time import get_current_epoch_time_ms
89
from splitio.api.client import HttpClientException
910
from splitio.models.token import from_raw
10-
11+
from splitio.models.telemetry import HTTPExceptionsAndLatencies
1112

1213
_LOGGER = logging.getLogger(__name__)
1314

1415

1516
class AuthAPI(object): # pylint: disable=too-few-public-methods
1617
"""Class that uses an httpClient to communicate with the SDK Auth Service API."""
1718

18-
def __init__(self, client, apikey, sdk_metadata):
19+
def __init__(self, client, apikey, sdk_metadata, telemetry_runtime_producer):
1920
"""
2021
Class constructor.
2122
@@ -29,6 +30,7 @@ def __init__(self, client, apikey, sdk_metadata):
2930
self._client = client
3031
self._apikey = apikey
3132
self._metadata = headers_from_metadata(sdk_metadata)
33+
self._telemetry_runtime_producer = telemetry_runtime_producer
3234

3335
def authenticate(self):
3436
"""
@@ -37,17 +39,21 @@ def authenticate(self):
3739
:return: Json representation of an authentication.
3840
:rtype: splitio.models.token.Token
3941
"""
42+
start = get_current_epoch_time_ms()
4043
try:
4144
response = self._client.get(
4245
'auth',
4346
'/v2/auth',
4447
self._apikey,
45-
extra_headers=self._metadata
48+
extra_headers=self._metadata,
4649
)
50+
record_telemetry(response.status_code, get_current_epoch_time_ms() - start, HTTPExceptionsAndLatencies.TOKEN, self._telemetry_runtime_producer)
4751
if 200 <= response.status_code < 300:
4852
payload = json.loads(response.body)
4953
return from_raw(payload)
5054
else:
55+
if (response.status_code >= 400 and response.status_code < 500):
56+
self._telemetry_runtime_producer.record_auth_rejections()
5157
raise APIException(response.body, response.status_code)
5258
except HttpClientException as exc:
5359
_LOGGER.error('Exception raised while authenticating')

splitio/api/client.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@
22
from collections import namedtuple
33

44
import requests
5+
import logging
6+
_LOGGER = logging.getLogger(__name__)
57

68
HttpResponse = namedtuple('HttpResponse', ['status_code', 'body'])
79

8-
910
class HttpClientException(Exception):
1011
"""HTTP Client exception."""
1112

@@ -42,7 +43,7 @@ def __init__(self, timeout=None, sdk_url=None, events_url=None, auth_url=None, t
4243
:param telemetry_url: Optional alternative telemetry URL.
4344
:type telemetry_url: str
4445
"""
45-
self._timeout = timeout/1000 if timeout else None # Convert ms to seconds.
46+
self._timeout = timeout/1000 if timeout else None # Convert ms to seconds.
4647
self._urls = {
4748
'sdk': sdk_url if sdk_url is not None else self.SDK_URL,
4849
'events': events_url if events_url is not None else self.EVENTS_URL,

splitio/api/commons.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"""Commons module."""
2-
2+
from splitio.util.time import get_current_epoch_time_ms
33

44
_CACHE_CONTROL = 'Cache-Control'
55
_CACHE_CONTROL_NO_CACHE = 'no-cache'
@@ -32,6 +32,27 @@ def headers_from_metadata(sdk_metadata, client_key=None):
3232

3333
return metadata
3434

35+
def record_telemetry(status_code, elapsed, metric_name, telemetry_runtime_producer):
36+
"""
37+
Record Telemetry info
38+
39+
:param status_code: http request status code
40+
:type status_code: int
41+
42+
:param elapsed: response time elapsed.
43+
:type status_code: int
44+
45+
:param metric_name: metric name for telemetry
46+
:type metric_name: str
47+
48+
:param telemetry_runtime_producer: telemetry recording instance
49+
:type telemetry_runtime_producer: splitio.engine.telemetry.TelemetryRuntimeProducer
50+
"""
51+
telemetry_runtime_producer.record_sync_latency(metric_name, elapsed)
52+
if 200 <= status_code < 300:
53+
telemetry_runtime_producer.record_successful_sync(metric_name, get_current_epoch_time_ms())
54+
return
55+
telemetry_runtime_producer.record_sync_error(metric_name, status_code)
3556

3657
class FetchOptions(object):
3758
"""Fetch Options object."""
@@ -92,4 +113,4 @@ def build_fetch(change_number, fetch_options, metadata):
92113
extra_headers[_CACHE_CONTROL] = _CACHE_CONTROL_NO_CACHE
93114
if fetch_options.change_number is not None:
94115
query['till'] = fetch_options.change_number
95-
return query, extra_headers
116+
return query, extra_headers

splitio/api/events.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
"""Events API module."""
22
import logging
3+
import time
34

45
from splitio.api import APIException
56
from splitio.api.client import HttpClientException
6-
from splitio.api.commons import headers_from_metadata
7+
from splitio.api.commons import headers_from_metadata, record_telemetry
8+
from splitio.util.time import get_current_epoch_time_ms
9+
from splitio.models.telemetry import HTTPExceptionsAndLatencies
710

811

912
_LOGGER = logging.getLogger(__name__)
@@ -12,7 +15,7 @@
1215
class EventsAPI(object): # pylint: disable=too-few-public-methods
1316
"""Class that uses an httpClient to communicate with the events API."""
1417

15-
def __init__(self, http_client, apikey, sdk_metadata):
18+
def __init__(self, http_client, apikey, sdk_metadata, telemetry_runtime_producer):
1619
"""
1720
Class constructor.
1821
@@ -26,6 +29,7 @@ def __init__(self, http_client, apikey, sdk_metadata):
2629
self._client = http_client
2730
self._apikey = apikey
2831
self._metadata = headers_from_metadata(sdk_metadata)
32+
self._telemetry_runtime_producer = telemetry_runtime_producer
2933

3034
@staticmethod
3135
def _build_bulk(events):
@@ -61,14 +65,16 @@ def flush_events(self, events):
6165
:rtype: bool
6266
"""
6367
bulk = self._build_bulk(events)
68+
start = get_current_epoch_time_ms()
6469
try:
6570
response = self._client.post(
6671
'events',
6772
'/events/bulk',
6873
self._apikey,
6974
body=bulk,
70-
extra_headers=self._metadata
75+
extra_headers=self._metadata,
7176
)
77+
record_telemetry(response.status_code, get_current_epoch_time_ms() - start, HTTPExceptionsAndLatencies.EVENT, self._telemetry_runtime_producer)
7278
if not 200 <= response.status_code < 300:
7379
raise APIException(response.body, response.status_code)
7480
except HttpClientException as exc:

splitio/api/impressions.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@
55

66
from splitio.api import APIException
77
from splitio.api.client import HttpClientException
8-
from splitio.api.commons import headers_from_metadata
9-
from splitio.engine.impressions.impressions import ImpressionsMode
8+
from splitio.api.commons import headers_from_metadata, record_telemetry
9+
from splitio.util.time import get_current_epoch_time_ms
10+
from splitio.engine.impressions import ImpressionsMode
11+
from splitio.models.telemetry import HTTPExceptionsAndLatencies
1012

1113

1214
_LOGGER = logging.getLogger(__name__)
@@ -15,7 +17,7 @@
1517
class ImpressionsAPI(object): # pylint: disable=too-few-public-methods
1618
"""Class that uses an httpClient to communicate with the impressions API."""
1719

18-
def __init__(self, client, apikey, sdk_metadata, mode=ImpressionsMode.OPTIMIZED):
20+
def __init__(self, client, apikey, sdk_metadata, telemetry_runtime_producer, mode=ImpressionsMode.OPTIMIZED):
1921
"""
2022
Class constructor.
2123
@@ -28,6 +30,7 @@ def __init__(self, client, apikey, sdk_metadata, mode=ImpressionsMode.OPTIMIZED)
2830
self._apikey = apikey
2931
self._metadata = headers_from_metadata(sdk_metadata)
3032
self._metadata['SplitSDKImpressionsMode'] = mode.name
33+
self._telemetry_runtime_producer = telemetry_runtime_producer
3134

3235
@staticmethod
3336
def _build_bulk(impressions):
@@ -91,14 +94,16 @@ def flush_impressions(self, impressions):
9194
:type impressions: list
9295
"""
9396
bulk = self._build_bulk(impressions)
97+
start = get_current_epoch_time_ms()
9498
try:
9599
response = self._client.post(
96100
'events',
97101
'/testImpressions/bulk',
98102
self._apikey,
99103
body=bulk,
100-
extra_headers=self._metadata
104+
extra_headers=self._metadata,
101105
)
106+
record_telemetry(response.status_code, get_current_epoch_time_ms() - start, HTTPExceptionsAndLatencies.IMPRESSION, self._telemetry_runtime_producer)
102107
if not 200 <= response.status_code < 300:
103108
raise APIException(response.body, response.status_code)
104109
except HttpClientException as exc:
@@ -116,14 +121,16 @@ def flush_counters(self, counters):
116121
:type impressions: list
117122
"""
118123
bulk = self._build_counters(counters)
124+
start = get_current_epoch_time_ms()
119125
try:
120126
response = self._client.post(
121127
'events',
122128
'/testImpressions/count',
123129
self._apikey,
124130
body=bulk,
125-
extra_headers=self._metadata
131+
extra_headers=self._metadata,
126132
)
133+
record_telemetry(response.status_code, get_current_epoch_time_ms() - start, HTTPExceptionsAndLatencies.IMPRESSION_COUNT, self._telemetry_runtime_producer)
127134
if not 200 <= response.status_code < 300:
128135
raise APIException(response.body, response.status_code)
129136
except HttpClientException as exc:

splitio/api/segments.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@
22

33
import json
44
import logging
5+
import time
56

67
from splitio.api import APIException
7-
from splitio.api.commons import headers_from_metadata, build_fetch
8+
from splitio.api.commons import headers_from_metadata, build_fetch, record_telemetry
9+
from splitio.util.time import get_current_epoch_time_ms
810
from splitio.api.client import HttpClientException
11+
from splitio.models.telemetry import HTTPExceptionsAndLatencies
912

1013

1114
_LOGGER = logging.getLogger(__name__)
@@ -14,7 +17,7 @@
1417
class SegmentsAPI(object): # pylint: disable=too-few-public-methods
1518
"""Class that uses an httpClient to communicate with the segments API."""
1619

17-
def __init__(self, http_client, apikey, sdk_metadata):
20+
def __init__(self, http_client, apikey, sdk_metadata, telemetry_runtime_producer):
1821
"""
1922
Class constructor.
2023
@@ -29,6 +32,7 @@ def __init__(self, http_client, apikey, sdk_metadata):
2932
self._client = http_client
3033
self._apikey = apikey
3134
self._metadata = headers_from_metadata(sdk_metadata)
35+
self._telemetry_runtime_producer = telemetry_runtime_producer
3236

3337
def fetch_segment(self, segment_name, change_number, fetch_options):
3438
"""
@@ -46,6 +50,7 @@ def fetch_segment(self, segment_name, change_number, fetch_options):
4650
:return: Json representation of a segmentChange response.
4751
:rtype: dict
4852
"""
53+
start = get_current_epoch_time_ms()
4954
try:
5055
query, extra_headers = build_fetch(change_number, fetch_options, self._metadata)
5156
response = self._client.get(
@@ -55,7 +60,7 @@ def fetch_segment(self, segment_name, change_number, fetch_options):
5560
extra_headers=extra_headers,
5661
query=query,
5762
)
58-
63+
record_telemetry(response.status_code, get_current_epoch_time_ms() - start, HTTPExceptionsAndLatencies.SEGMENT, self._telemetry_runtime_producer)
5964
if 200 <= response.status_code < 300:
6065
return json.loads(response.body)
6166
else:

0 commit comments

Comments
 (0)