Skip to content

Commit 8ab97c1

Browse files
authored
Merge pull request #190 from splitio/development
Development
2 parents 69892f6 + bbeebc0 commit 8ab97c1

File tree

110 files changed

+109570
-1533
lines changed

Some content is hidden

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

110 files changed

+109570
-1533
lines changed

CHANGES.txt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
1+
8.3.0 (Nov 4, 2020)
2+
- Added local impressions deduping. Defaulting to optimized
3+
- Added support for the new Split streaming architecture. When enabled (default), the SDK will not poll for updates but instead receive notifications every time there's a change in your environments, allowing to process those much quicker. If disabled or in the event of an issue, the SDK will fallback to the known polling mechanism to provide a seamless experience.
4+
- Updated logging structure so that it's built in terms of a hierarchy with the root at 'splitio'
5+
- Fixed timing issue which caused factory.ready to return False if called immediately after .block_until_ready()
6+
17
8.2.1 (Aug 25, 2020)
2-
- Use mmh3cffi=0.1.5 which fixes xcode12 issue
8+
- Updated mmh3cffi to version 0.1.5 which fixes xcode12 issue
39

410
8.2.0 (Mar 27, 2020)
5-
- Support enabling in-memory cache via config options
11+
- Added support for enabling in-memory cache via config options
612

713
8.1.7 (Jan 23, 2020)
814
- Removed enum34 dependency for python versions > 3.4

setup.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,7 @@
2323
'futures>=3.0.5;python_version<"3"'
2424
]
2525

26-
with open(path.join(path.abspath(path.dirname(__file__)),
27-
'splitio', 'version.py')) as f:
26+
with open(path.join(path.abspath(path.dirname(__file__)), 'splitio', 'version.py')) as f:
2827
exec(f.read()) # pylint: disable=exec-used
2928

3029
setup(
@@ -42,7 +41,7 @@
4241
'test': TESTS_REQUIRES,
4342
'redis': ['redis>=2.10.5'],
4443
'uwsgi': ['uwsgi>=2.0.0'],
45-
'cpphash': ['mmh3cffi>=0.1.5']
44+
'cpphash': ['mmh3cffi==0.2.0'],
4645
},
4746
setup_requires=['pytest-runner'],
4847
classifiers=[

splitio/api/auth.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
"""Auth API module."""
2+
3+
import logging
4+
import json
5+
6+
from future.utils import raise_from
7+
8+
from splitio.api import APIException, headers_from_metadata
9+
from splitio.api.client import HttpClientException
10+
from splitio.models.token import from_raw
11+
12+
13+
_LOGGER = logging.getLogger(__name__)
14+
15+
16+
class AuthAPI(object): # pylint: disable=too-few-public-methods
17+
"""Class that uses an httpClient to communicate with the SDK Auth Service API."""
18+
19+
def __init__(self, client, apikey, sdk_metadata):
20+
"""
21+
Class constructor.
22+
23+
:param client: HTTP Client responsble for issuing calls to the backend.
24+
:type client: HttpClient
25+
:param apikey: User apikey token.
26+
:type apikey: string
27+
:param sdk_metadata: SDK version & machine name & IP.
28+
:type sdk_metadata: splitio.client.util.SdkMetadata
29+
"""
30+
self._client = client
31+
self._apikey = apikey
32+
self._metadata = headers_from_metadata(sdk_metadata)
33+
34+
def authenticate(self):
35+
"""
36+
Perform authentication.
37+
38+
:return: Json representation of an authentication.
39+
:rtype: splitio.models.token.Token
40+
"""
41+
try:
42+
response = self._client.get(
43+
'auth',
44+
'/auth',
45+
self._apikey,
46+
extra_headers=self._metadata
47+
)
48+
if 200 <= response.status_code < 300:
49+
payload = json.loads(response.body)
50+
return from_raw(payload)
51+
else:
52+
raise APIException(response.body, response.status_code)
53+
except HttpClientException as exc:
54+
_LOGGER.error('Exception raised while authenticating')
55+
_LOGGER.debug('Exception information: ', exc_info=True)
56+
raise_from(APIException('Could not perform authentication.'), exc)

splitio/api/client.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,9 @@ class HttpClient(object):
2727

2828
SDK_URL = 'https://sdk.split.io/api'
2929
EVENTS_URL = 'https://events.split.io/api'
30+
AUTH_URL = 'https://auth.split.io/api'
3031

31-
def __init__(self, timeout=None, sdk_url=None, events_url=None):
32+
def __init__(self, timeout=None, sdk_url=None, events_url=None, auth_url=None):
3233
"""
3334
Class constructor.
3435
@@ -38,11 +39,14 @@ def __init__(self, timeout=None, sdk_url=None, events_url=None):
3839
:type sdk_url: str
3940
:param events_url: Optional alternative events URL.
4041
:type events_url: str
42+
:param auth_url: Optional alternative auth URL.
43+
:type auth_url: str
4144
"""
42-
self._timeout = timeout / 1000 if timeout else None # Convert ms to seconds.
45+
self._timeout = timeout/1000 if timeout else None # Convert ms to seconds.
4346
self._urls = {
4447
'sdk': sdk_url if sdk_url is not None else self.SDK_URL,
4548
'events': events_url if events_url is not None else self.EVENTS_URL,
49+
'auth': auth_url if auth_url is not None else self.AUTH_URL,
4650
}
4751

4852
def _build_url(self, server, path):
@@ -72,11 +76,11 @@ def _build_basic_headers(apikey):
7276
'Authorization': "Bearer %s" % apikey
7377
}
7478

75-
def get(self, server, path, apikey, query=None, extra_headers=None): #pylint: disable=too-many-arguments
79+
def get(self, server, path, apikey, query=None, extra_headers=None): # pylint: disable=too-many-arguments
7680
"""
7781
Issue a get request.
7882
79-
:param server: Whether the request is for SDK server or Events server.
83+
:param server: Whether the request is for SDK server, Events server or Auth server.
8084
:typee server: str
8185
:param path: path to append to the host url.
8286
:type path: str
@@ -91,7 +95,6 @@ def get(self, server, path, apikey, query=None, extra_headers=None): #pylint: d
9195
:rtype: HttpResponse
9296
"""
9397
headers = self._build_basic_headers(apikey)
94-
9598
if extra_headers is not None:
9699
headers.update(extra_headers)
97100

@@ -103,10 +106,10 @@ def get(self, server, path, apikey, query=None, extra_headers=None): #pylint: d
103106
timeout=self._timeout
104107
)
105108
return HttpResponse(response.status_code, response.text)
106-
except Exception as exc: #pylint: disable=broad-except
109+
except Exception as exc: # pylint: disable=broad-except
107110
raise_from(HttpClientException('requests library is throwing exceptions'), exc)
108111

109-
def post(self, server, path, apikey, body, query=None, extra_headers=None): #pylint: disable=too-many-arguments
112+
def post(self, server, path, apikey, body, query=None, extra_headers=None): # pylint: disable=too-many-arguments
110113
"""
111114
Issue a POST request.
112115
@@ -140,5 +143,5 @@ def post(self, server, path, apikey, body, query=None, extra_headers=None): #py
140143
timeout=self._timeout
141144
)
142145
return HttpResponse(response.status_code, response.text)
143-
except Exception as exc: #pylint: disable=broad-except
146+
except Exception as exc: # pylint: disable=broad-except
144147
raise_from(HttpClientException('requests library is throwing exceptions'), exc)

splitio/api/events.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
from splitio.api.client import HttpClientException
88

99

10+
_LOGGER = logging.getLogger(__name__)
11+
12+
1013
class EventsAPI(object): # pylint: disable=too-few-public-methods
1114
"""Class that uses an httpClient to communicate with the events API."""
1215

@@ -21,7 +24,6 @@ def __init__(self, http_client, apikey, sdk_metadata):
2124
:param sdk_metadata: SDK version & machine name & IP.
2225
:type sdk_metadata: splitio.client.util.SdkMetadata
2326
"""
24-
self._logger = logging.getLogger(self.__class__.__name__)
2527
self._client = http_client
2628
self._apikey = apikey
2729
self._metadata = headers_from_metadata(sdk_metadata)
@@ -71,6 +73,6 @@ def flush_events(self, events):
7173
if not 200 <= response.status_code < 300:
7274
raise APIException(response.body, response.status_code)
7375
except HttpClientException as exc:
74-
self._logger.error('Http client is throwing exceptions')
75-
self._logger.debug('Error: ', exc_info=True)
76+
_LOGGER.error('Error posting events because an exception was raised by the HTTPClient')
77+
_LOGGER.debug('Error: ', exc_info=True)
7678
raise_from(APIException('Events not flushed properly.'), exc)

splitio/api/impressions.py

Lines changed: 67 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,16 @@
77

88
from splitio.api import APIException, headers_from_metadata
99
from splitio.api.client import HttpClientException
10+
from splitio.engine.impressions import ImpressionsMode
11+
12+
13+
_LOGGER = logging.getLogger(__name__)
1014

1115

1216
class ImpressionsAPI(object): # pylint: disable=too-few-public-methods
1317
"""Class that uses an httpClient to communicate with the impressions API."""
1418

15-
def __init__(self, client, apikey, sdk_metadata):
19+
def __init__(self, client, apikey, sdk_metadata, mode=ImpressionsMode.OPTIMIZED):
1620
"""
1721
Class constructor.
1822
@@ -21,10 +25,10 @@ def __init__(self, client, apikey, sdk_metadata):
2125
:param apikey: User apikey token.
2226
:type apikey: string
2327
"""
24-
self._logger = logging.getLogger(self.__class__.__name__)
2528
self._client = client
2629
self._apikey = apikey
2730
self._metadata = headers_from_metadata(sdk_metadata)
31+
self._metadata['SplitSDKImpressionsMode'] = mode.name
2832

2933
@staticmethod
3034
def _build_bulk(impressions):
@@ -35,19 +39,20 @@ def _build_bulk(impressions):
3539
:type impressions: list(splitio.models.impressions.Impression)
3640
3741
:return: Dictionary of lists of impressions.
38-
:rtype: dict
42+
:rtype: list
3943
"""
4044
return [
4145
{
42-
'testName': test_name,
43-
'keyImpressions': [
46+
'f': test_name,
47+
'i': [
4448
{
45-
'keyName': impression.matching_key,
46-
'treatment': impression.treatment,
47-
'time': impression.time,
48-
'changeNumber': impression.change_number,
49-
'label': impression.label,
50-
'bucketingKey': impression.bucketing_key
49+
'k': impression.matching_key,
50+
't': impression.treatment,
51+
'm': impression.time,
52+
'c': impression.change_number,
53+
'r': impression.label,
54+
'b': impression.bucketing_key,
55+
'pt': impression.previous_time
5156
}
5257
for impression in imps
5358
]
@@ -58,6 +63,27 @@ def _build_bulk(impressions):
5863
)
5964
]
6065

66+
@staticmethod
67+
def _build_counters(counters):
68+
"""
69+
Build an impression bulk formatted as the API expects it.
70+
71+
:param counters: List of impression counters per feature.
72+
:type counters: list[splitio.engine.impressions.Counter.CountPerFeature]
73+
74+
:return: dict with list of impression count dtos
75+
:rtype: dict
76+
"""
77+
return {
78+
'pf': [
79+
{
80+
'f': pf_count.feature,
81+
'm': pf_count.timeframe,
82+
'rc': pf_count.count
83+
} for pf_count in counters
84+
]
85+
}
86+
6187
def flush_impressions(self, impressions):
6288
"""
6389
Send impressions to the backend.
@@ -77,6 +103,34 @@ def flush_impressions(self, impressions):
77103
if not 200 <= response.status_code < 300:
78104
raise APIException(response.body, response.status_code)
79105
except HttpClientException as exc:
80-
self._logger.error('Http client is throwing exceptions')
81-
self._logger.debug('Error: ', exc_info=True)
106+
_LOGGER.error(
107+
'Error posting impressions because an exception was raised by the HTTPClient'
108+
)
109+
_LOGGER.debug('Error: ', exc_info=True)
110+
raise_from(APIException('Impressions not flushed properly.'), exc)
111+
112+
def flush_counters(self, counters):
113+
"""
114+
Send impressions to the backend.
115+
116+
:param impressions: Impressions bulk
117+
:type impressions: list
118+
"""
119+
bulk = self._build_counters(counters)
120+
try:
121+
response = self._client.post(
122+
'events',
123+
'/testImpressions/count',
124+
self._apikey,
125+
body=bulk,
126+
extra_headers=self._metadata
127+
)
128+
if not 200 <= response.status_code < 300:
129+
raise APIException(response.body, response.status_code)
130+
except HttpClientException as exc:
131+
_LOGGER.error(
132+
'Error posting impressions counters because an exception was raised by the '
133+
'HTTPClient'
134+
)
135+
_LOGGER.debug('Error: ', exc_info=True)
82136
raise_from(APIException('Impressions not flushed properly.'), exc)

splitio/api/segments.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@
99
from splitio.api.client import HttpClientException
1010

1111

12-
class SegmentsAPI(object): #pylint: disable=too-few-public-methods
12+
_LOGGER = logging.getLogger(__name__)
13+
14+
15+
class SegmentsAPI(object): # pylint: disable=too-few-public-methods
1316
"""Class that uses an httpClient to communicate with the segments API."""
1417

1518
def __init__(self, http_client, apikey):
@@ -21,7 +24,6 @@ def __init__(self, http_client, apikey):
2124
:param apikey: User apikey token.
2225
:type apikey: string
2326
"""
24-
self._logger = logging.getLogger(self.__class__.__name__)
2527
self._client = http_client
2628
self._apikey = apikey
2729

@@ -50,6 +52,9 @@ def fetch_segment(self, segment_name, change_number):
5052
else:
5153
raise APIException(response.body, response.status_code)
5254
except HttpClientException as exc:
53-
self._logger.error('Http client is throwing exceptions')
54-
self._logger.debug('Error: ', exc_info=True)
55+
_LOGGER.error(
56+
'Error fetching %s because an exception was raised by the HTTPClient',
57+
segment_name
58+
)
59+
_LOGGER.debug('Error: ', exc_info=True)
5560
raise_from(APIException('Segments not fetched properly.'), exc)

splitio/api/splits.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@
99
from splitio.api.client import HttpClientException
1010

1111

12-
class SplitsAPI(object): #pylint: disable=too-few-public-methods
12+
_LOGGER = logging.getLogger(__name__)
13+
14+
15+
class SplitsAPI(object): # pylint: disable=too-few-public-methods
1316
"""Class that uses an httpClient to communicate with the splits API."""
1417

1518
def __init__(self, client, apikey):
@@ -21,7 +24,6 @@ def __init__(self, client, apikey):
2124
:param apikey: User apikey token.
2225
:type apikey: string
2326
"""
24-
self._logger = logging.getLogger(self.__class__.__name__)
2527
self._client = client
2628
self._apikey = apikey
2729

@@ -47,6 +49,6 @@ def fetch_splits(self, change_number):
4749
else:
4850
raise APIException(response.body, response.status_code)
4951
except HttpClientException as exc:
50-
self._logger.error('Http client is throwing exceptions')
51-
self._logger.debug('Error: ', exc_info=True)
52+
_LOGGER.error('Error fetching splits because an exception was raised by the HTTPClient')
53+
_LOGGER.debug('Error: ', exc_info=True)
5254
raise_from(APIException('Splits not fetched correctly.'), exc)

0 commit comments

Comments
 (0)