Skip to content

Commit

Permalink
Merge pull request #190 from splitio/development
Browse files Browse the repository at this point in the history
Development
  • Loading branch information
mredolatti committed Nov 4, 2020
2 parents 69892f6 + bbeebc0 commit 8ab97c1
Show file tree
Hide file tree
Showing 110 changed files with 109,570 additions and 1,533 deletions.
10 changes: 8 additions & 2 deletions CHANGES.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
8.3.0 (Nov 4, 2020)
- Added local impressions deduping. Defaulting to optimized
- 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.
- Updated logging structure so that it's built in terms of a hierarchy with the root at 'splitio'
- Fixed timing issue which caused factory.ready to return False if called immediately after .block_until_ready()

8.2.1 (Aug 25, 2020)
- Use mmh3cffi=0.1.5 which fixes xcode12 issue
- Updated mmh3cffi to version 0.1.5 which fixes xcode12 issue

8.2.0 (Mar 27, 2020)
- Support enabling in-memory cache via config options
- Added support for enabling in-memory cache via config options

8.1.7 (Jan 23, 2020)
- Removed enum34 dependency for python versions > 3.4
Expand Down
5 changes: 2 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@
'futures>=3.0.5;python_version<"3"'
]

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

setup(
Expand All @@ -42,7 +41,7 @@
'test': TESTS_REQUIRES,
'redis': ['redis>=2.10.5'],
'uwsgi': ['uwsgi>=2.0.0'],
'cpphash': ['mmh3cffi>=0.1.5']
'cpphash': ['mmh3cffi==0.2.0'],
},
setup_requires=['pytest-runner'],
classifiers=[
Expand Down
56 changes: 56 additions & 0 deletions splitio/api/auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
"""Auth API module."""

import logging
import json

from future.utils import raise_from

from splitio.api import APIException, headers_from_metadata
from splitio.api.client import HttpClientException
from splitio.models.token import from_raw


_LOGGER = logging.getLogger(__name__)


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

def __init__(self, client, apikey, sdk_metadata):
"""
Class constructor.
:param client: HTTP Client responsble for issuing calls to the backend.
:type client: HttpClient
:param apikey: User apikey token.
:type apikey: string
:param sdk_metadata: SDK version & machine name & IP.
:type sdk_metadata: splitio.client.util.SdkMetadata
"""
self._client = client
self._apikey = apikey
self._metadata = headers_from_metadata(sdk_metadata)

def authenticate(self):
"""
Perform authentication.
:return: Json representation of an authentication.
:rtype: splitio.models.token.Token
"""
try:
response = self._client.get(
'auth',
'/auth',
self._apikey,
extra_headers=self._metadata
)
if 200 <= response.status_code < 300:
payload = json.loads(response.body)
return from_raw(payload)
else:
raise APIException(response.body, response.status_code)
except HttpClientException as exc:
_LOGGER.error('Exception raised while authenticating')
_LOGGER.debug('Exception information: ', exc_info=True)
raise_from(APIException('Could not perform authentication.'), exc)
19 changes: 11 additions & 8 deletions splitio/api/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ class HttpClient(object):

SDK_URL = 'https://sdk.split.io/api'
EVENTS_URL = 'https://events.split.io/api'
AUTH_URL = 'https://auth.split.io/api'

def __init__(self, timeout=None, sdk_url=None, events_url=None):
def __init__(self, timeout=None, sdk_url=None, events_url=None, auth_url=None):
"""
Class constructor.
Expand All @@ -38,11 +39,14 @@ def __init__(self, timeout=None, sdk_url=None, events_url=None):
:type sdk_url: str
:param events_url: Optional alternative events URL.
:type events_url: str
:param auth_url: Optional alternative auth URL.
:type auth_url: str
"""
self._timeout = timeout / 1000 if timeout else None # Convert ms to seconds.
self._timeout = timeout/1000 if timeout else None # Convert ms to seconds.
self._urls = {
'sdk': sdk_url if sdk_url is not None else self.SDK_URL,
'events': events_url if events_url is not None else self.EVENTS_URL,
'auth': auth_url if auth_url is not None else self.AUTH_URL,
}

def _build_url(self, server, path):
Expand Down Expand Up @@ -72,11 +76,11 @@ def _build_basic_headers(apikey):
'Authorization': "Bearer %s" % apikey
}

def get(self, server, path, apikey, query=None, extra_headers=None): #pylint: disable=too-many-arguments
def get(self, server, path, apikey, query=None, extra_headers=None): # pylint: disable=too-many-arguments
"""
Issue a get request.
:param server: Whether the request is for SDK server or Events server.
:param server: Whether the request is for SDK server, Events server or Auth server.
:typee server: str
:param path: path to append to the host url.
:type path: str
Expand All @@ -91,7 +95,6 @@ def get(self, server, path, apikey, query=None, extra_headers=None): #pylint: d
:rtype: HttpResponse
"""
headers = self._build_basic_headers(apikey)

if extra_headers is not None:
headers.update(extra_headers)

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

def post(self, server, path, apikey, body, query=None, extra_headers=None): #pylint: disable=too-many-arguments
def post(self, server, path, apikey, body, query=None, extra_headers=None): # pylint: disable=too-many-arguments
"""
Issue a POST request.
Expand Down Expand Up @@ -140,5 +143,5 @@ def post(self, server, path, apikey, body, query=None, extra_headers=None): #py
timeout=self._timeout
)
return HttpResponse(response.status_code, response.text)
except Exception as exc: #pylint: disable=broad-except
except Exception as exc: # pylint: disable=broad-except
raise_from(HttpClientException('requests library is throwing exceptions'), exc)
8 changes: 5 additions & 3 deletions splitio/api/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
from splitio.api.client import HttpClientException


_LOGGER = logging.getLogger(__name__)


class EventsAPI(object): # pylint: disable=too-few-public-methods
"""Class that uses an httpClient to communicate with the events API."""

Expand All @@ -21,7 +24,6 @@ def __init__(self, http_client, apikey, sdk_metadata):
:param sdk_metadata: SDK version & machine name & IP.
:type sdk_metadata: splitio.client.util.SdkMetadata
"""
self._logger = logging.getLogger(self.__class__.__name__)
self._client = http_client
self._apikey = apikey
self._metadata = headers_from_metadata(sdk_metadata)
Expand Down Expand Up @@ -71,6 +73,6 @@ def flush_events(self, events):
if not 200 <= response.status_code < 300:
raise APIException(response.body, response.status_code)
except HttpClientException as exc:
self._logger.error('Http client is throwing exceptions')
self._logger.debug('Error: ', exc_info=True)
_LOGGER.error('Error posting events because an exception was raised by the HTTPClient')
_LOGGER.debug('Error: ', exc_info=True)
raise_from(APIException('Events not flushed properly.'), exc)
80 changes: 67 additions & 13 deletions splitio/api/impressions.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,16 @@

from splitio.api import APIException, headers_from_metadata
from splitio.api.client import HttpClientException
from splitio.engine.impressions import ImpressionsMode


_LOGGER = logging.getLogger(__name__)


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

def __init__(self, client, apikey, sdk_metadata):
def __init__(self, client, apikey, sdk_metadata, mode=ImpressionsMode.OPTIMIZED):
"""
Class constructor.
Expand All @@ -21,10 +25,10 @@ def __init__(self, client, apikey, sdk_metadata):
:param apikey: User apikey token.
:type apikey: string
"""
self._logger = logging.getLogger(self.__class__.__name__)
self._client = client
self._apikey = apikey
self._metadata = headers_from_metadata(sdk_metadata)
self._metadata['SplitSDKImpressionsMode'] = mode.name

@staticmethod
def _build_bulk(impressions):
Expand All @@ -35,19 +39,20 @@ def _build_bulk(impressions):
:type impressions: list(splitio.models.impressions.Impression)
:return: Dictionary of lists of impressions.
:rtype: dict
:rtype: list
"""
return [
{
'testName': test_name,
'keyImpressions': [
'f': test_name,
'i': [
{
'keyName': impression.matching_key,
'treatment': impression.treatment,
'time': impression.time,
'changeNumber': impression.change_number,
'label': impression.label,
'bucketingKey': impression.bucketing_key
'k': impression.matching_key,
't': impression.treatment,
'm': impression.time,
'c': impression.change_number,
'r': impression.label,
'b': impression.bucketing_key,
'pt': impression.previous_time
}
for impression in imps
]
Expand All @@ -58,6 +63,27 @@ def _build_bulk(impressions):
)
]

@staticmethod
def _build_counters(counters):
"""
Build an impression bulk formatted as the API expects it.
:param counters: List of impression counters per feature.
:type counters: list[splitio.engine.impressions.Counter.CountPerFeature]
:return: dict with list of impression count dtos
:rtype: dict
"""
return {
'pf': [
{
'f': pf_count.feature,
'm': pf_count.timeframe,
'rc': pf_count.count
} for pf_count in counters
]
}

def flush_impressions(self, impressions):
"""
Send impressions to the backend.
Expand All @@ -77,6 +103,34 @@ def flush_impressions(self, impressions):
if not 200 <= response.status_code < 300:
raise APIException(response.body, response.status_code)
except HttpClientException as exc:
self._logger.error('Http client is throwing exceptions')
self._logger.debug('Error: ', exc_info=True)
_LOGGER.error(
'Error posting impressions because an exception was raised by the HTTPClient'
)
_LOGGER.debug('Error: ', exc_info=True)
raise_from(APIException('Impressions not flushed properly.'), exc)

def flush_counters(self, counters):
"""
Send impressions to the backend.
:param impressions: Impressions bulk
:type impressions: list
"""
bulk = self._build_counters(counters)
try:
response = self._client.post(
'events',
'/testImpressions/count',
self._apikey,
body=bulk,
extra_headers=self._metadata
)
if not 200 <= response.status_code < 300:
raise APIException(response.body, response.status_code)
except HttpClientException as exc:
_LOGGER.error(
'Error posting impressions counters because an exception was raised by the '
'HTTPClient'
)
_LOGGER.debug('Error: ', exc_info=True)
raise_from(APIException('Impressions not flushed properly.'), exc)
13 changes: 9 additions & 4 deletions splitio/api/segments.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@
from splitio.api.client import HttpClientException


class SegmentsAPI(object): #pylint: disable=too-few-public-methods
_LOGGER = logging.getLogger(__name__)


class SegmentsAPI(object): # pylint: disable=too-few-public-methods
"""Class that uses an httpClient to communicate with the segments API."""

def __init__(self, http_client, apikey):
Expand All @@ -21,7 +24,6 @@ def __init__(self, http_client, apikey):
:param apikey: User apikey token.
:type apikey: string
"""
self._logger = logging.getLogger(self.__class__.__name__)
self._client = http_client
self._apikey = apikey

Expand Down Expand Up @@ -50,6 +52,9 @@ def fetch_segment(self, segment_name, change_number):
else:
raise APIException(response.body, response.status_code)
except HttpClientException as exc:
self._logger.error('Http client is throwing exceptions')
self._logger.debug('Error: ', exc_info=True)
_LOGGER.error(
'Error fetching %s because an exception was raised by the HTTPClient',
segment_name
)
_LOGGER.debug('Error: ', exc_info=True)
raise_from(APIException('Segments not fetched properly.'), exc)
10 changes: 6 additions & 4 deletions splitio/api/splits.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@
from splitio.api.client import HttpClientException


class SplitsAPI(object): #pylint: disable=too-few-public-methods
_LOGGER = logging.getLogger(__name__)


class SplitsAPI(object): # pylint: disable=too-few-public-methods
"""Class that uses an httpClient to communicate with the splits API."""

def __init__(self, client, apikey):
Expand All @@ -21,7 +24,6 @@ def __init__(self, client, apikey):
:param apikey: User apikey token.
:type apikey: string
"""
self._logger = logging.getLogger(self.__class__.__name__)
self._client = client
self._apikey = apikey

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

0 comments on commit 8ab97c1

Please sign in to comment.