Skip to content

Commit 13ca81b

Browse files
authored
Merge pull request #2 from splitio/development
Merge to master to make release
2 parents c7c697d + a37954d commit 13ca81b

56 files changed

Lines changed: 3755 additions & 3 deletions

Some content is hidden

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

.gitignore

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
venv
12
# Byte-compiled / optimized / DLL files
23
__pycache__/
34
*.py[cod]
@@ -8,7 +9,6 @@ __pycache__/
89

910
# Distribution / packaging
1011
.Python
11-
env/
1212
build/
1313
develop-eggs/
1414
dist/
@@ -87,3 +87,9 @@ ENV/
8787

8888
# Rope project settings
8989
.ropeproject
90+
91+
#vim backup files
92+
*.swp
93+
94+
#IDE
95+
.idea

CHANGES.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
1.0 (July 10th, 2017)
2+
- Initial release

README.md

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,38 @@
1-
# python-api
2-
Split public API for Python
1+
# Split Python API
2+
3+
This API wrapper is designed to work with [Split](https://www.split.io), the platform for controlled rollouts, serving features to your users via the Split feature flag to manage your complete customer experience.
4+
5+
### Quick setup
6+
7+
For specific instructions on how to use this API refer to our [official API documentation](https://docs.split.io/reference).
8+
9+
### Commitment to Quality:
10+
11+
Split’s APIs are in active development and are constantly tested for quality. Unit tests are developed for each wrapper based on the unique needs of that language, and integration tests, load and performance tests, and behavior consistency tests are running 24/7 via automated bots. In addition, monitoring instrumentation ensures that these wrappers behave under the expected parameters of memory, CPU, and I/O.
12+
13+
### About Split:
14+
15+
Split is the leading platform for intelligent software delivery, helping businesses of all sizes deliver exceptional user experiences, and mitigate risk, by providing an easy, secure way to target features to customers. Companies like WePay, LendingTree and thredUP rely on Split to safely launch and test new features and derive insights on their use. Founded in 2015, Split's team comes from some of the most innovative enterprises in Silicon Valley, including Google, LinkedIn, Salesforce and Splunk. Split is based in Redwood City, California and backed by Accel Partners and Lightspeed Venture Partners. To learn more about Split, contact hello@split.io, or start a 14-day free trial at www.split.io/trial.
16+
17+
Split has built and maintains a API wrappers for:
18+
19+
* Java [Github](https://github.com/splitio/java-api)
20+
* Node [Github](https://github.com/splitio/javascript-api)
21+
* .NET [Github](https://github.com/splitio/net-api)
22+
* Ruby [Github](https://github.com/splitio/ruby-api)
23+
* PHP [Github](https://github.com/splitio/php-api)
24+
* Python [Github](https://github.com/splitio/python-api)
25+
26+
For a comprehensive list of opensource projects visit our [Github page](https://github.com/splitio?utf8=%E2%9C%93&query=%20only%3Apublic%20).
27+
28+
**Try Split for Free:**
29+
30+
Split is available as a 14-day free trial. To create an account, visit [split.io/trial](https://www.split.io/trial).
31+
32+
**Learn more about Split:**
33+
34+
Visit [split.io/product](https://www.split.io/product) for an overview of Split, or visit our documentation at [docs.split.io](http://docs.split.io) for more detailed information.
35+
36+
**System Status:**
37+
38+
We use a status page to monitor the availability of Split’s various services. You can check the current status at [status.split.io](http://status.split.io).

setup.cfg

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[aliases]
2+
test=pytest
3+
4+
[tool:pytest]
5+
addopts = --verbose

setup.py

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
#!/usr/bin/env python
2+
3+
import subprocess
4+
import contextlib
5+
import sys
6+
import pkg_resources
7+
from setuptools import setup, find_packages
8+
from os import path
9+
from distutils.cmd import Command
10+
11+
12+
@contextlib.contextmanager
13+
def _save_argv(repl=None):
14+
saved = sys.argv[:]
15+
if repl is not None:
16+
sys.argv[:] = repl
17+
try:
18+
yield saved
19+
finally:
20+
sys.argv[:] = saved
21+
22+
23+
class E2ETests(Command):
24+
'''
25+
Custom command to run End to End tests with locally mocked
26+
tornado webserver.
27+
'''
28+
description = 'Run end to end tests with mocked tornado backend'
29+
user_options = [
30+
('addopts=', None, "Additional options to be passed verbatim to the "
31+
'pytest runner')
32+
]
33+
34+
def initialize_options(self):
35+
'''
36+
'''
37+
self.addopts = []
38+
39+
def finalize_options(self):
40+
'''
41+
'''
42+
pass
43+
44+
def _setup_tornado(self):
45+
'''
46+
'''
47+
try:
48+
import tornado
49+
except ImportError:
50+
subprocess.check_call(['pip', 'install'] + locale2e_requires)
51+
52+
def _setup_pytest(self):
53+
'''
54+
'''
55+
current_dir = path.dirname(path.realpath(__file__))
56+
egg_path = path.join(current_dir, '.eggs')
57+
pkg_resources.working_set.add_entry(egg_path)
58+
pkg_resources.require('pytest')
59+
60+
def _setup_args(self):
61+
'''
62+
'''
63+
le2e_test_file = path.join(
64+
path.dirname(path.realpath(__file__)),
65+
'splitapiclient',
66+
'tests',
67+
'e2e',
68+
'e2etests.py'
69+
)
70+
return ['pytest', '-v'] + [le2e_test_file] + self.addopts
71+
72+
def run(self):
73+
'''
74+
'''
75+
self._setup_tornado()
76+
self._setup_pytest()
77+
import pytest
78+
with _save_argv(self._setup_args()):
79+
__import__('pytest').main()
80+
81+
# Project requirements
82+
install_requires = [
83+
'argparse>=1.1',
84+
'requests>=2.14.2',
85+
'six>=1.10.0',
86+
]
87+
88+
89+
# Standard unit test requirements
90+
tests_requires = [
91+
'mock==2.0.0',
92+
'pytest-mock==1.6.0',
93+
'pytest==3.1.3',
94+
]
95+
96+
97+
# Local End to End test requirements
98+
locale2e_requires = [
99+
'tornado'
100+
]
101+
102+
103+
# Get version number
104+
with open(path.join(path.abspath(path.dirname(__file__)),
105+
'splitapiclient', 'version.py')) as f:
106+
exec(f.read())
107+
# Run setup!
108+
setup(
109+
name='splitapiclient',
110+
version=__version__, # noqa
111+
description='Split.io Identify Python Client',
112+
author='Patricio Echague, Sebastian Arrubia, Martin Redolatti',
113+
author_email='pato@split.io, sebastian@split.io, martin@split.io',
114+
url='https://github.com/splitio/python-api',
115+
download_url=(
116+
'https://github.com/splitio/python-api/tarball/' + __version__
117+
),
118+
license='Apache License 2.0',
119+
install_requires=install_requires,
120+
setup_requires=['pytest-runner'],
121+
tests_require=tests_requires,
122+
classifiers=[
123+
'Development Status :: 3 - Alpha',
124+
'Environment :: Console',
125+
'Intended Audience :: Developers',
126+
'Programming Language :: Python',
127+
'Programming Language :: Python :: 2',
128+
'Programming Language :: Python :: 3',
129+
'Topic :: Software Development :: Libraries'
130+
],
131+
packages=find_packages(),
132+
cmdclass={
133+
'e2etests': E2ETests,
134+
}
135+
)

splitapiclient/__init__.py

Whitespace-only changes.

splitapiclient/http_clients/__init__.py

Whitespace-only changes.
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
from __future__ import absolute_import, division, print_function, \
2+
unicode_literals
3+
import abc
4+
import re
5+
import six
6+
from splitapiclient.util.exceptions import MissingParametersException
7+
8+
9+
class BaseHttpClient(six.with_metaclass(abc.ABCMeta)):
10+
'''
11+
Abstrac class providing an interface and generic methods of the HTTP client
12+
responsible for interacting with the Split APIs
13+
'''
14+
15+
def __init__(self, baseurl, auth_token):
16+
'''
17+
Class constructor. Sotores basic connection information.
18+
19+
:param baseurl: string. Split host and base url.
20+
:param auth_token: string. Authentication token needed to make API
21+
calls.
22+
'''
23+
self.config = {
24+
'base_url': baseurl,
25+
'base_args': {
26+
'Authorization': auth_token
27+
}
28+
}
29+
30+
@abc.abstractmethod
31+
def make_request(self, method, body=None, **kwargs):
32+
'''
33+
Method responsible for executing an actual requesting, retrievieng an
34+
appropiarte response and raising an exception if the HTTP call fails.
35+
36+
:param endpoint: dict. Endpoint description (method, url, etc)
37+
:param body: dict/list. POST/PUT/PATCH request body (optional)
38+
:param kwargs: dict. Extra parameters required for headers, querystring,
39+
url template paramters, etc.
40+
41+
:rtype: dict/list/None
42+
'''
43+
pass
44+
45+
@staticmethod
46+
def get_params_from_url_template(url):
47+
'''
48+
Retuns a list of tempated variables that must be replaced when
49+
instantiating the url template.
50+
51+
:param url: url template
52+
53+
:rtype: list.
54+
'''
55+
regex = '{([\w-]+)}*'
56+
url_params = re.findall(regex, url)
57+
return list(set(url_params)) if url_params else []
58+
59+
@staticmethod
60+
def _process_single_header(header, value):
61+
'''
62+
Checks if the header's value is a templated string. If that is the case,
63+
the template is instantiated with the correct value. Otherwise the
64+
single value is returned.
65+
66+
:param header: dict. Header description from endpoint.
67+
:param value: string. Header value
68+
69+
rtype: string.
70+
'''
71+
template = header.get('template')
72+
if template:
73+
return template.format(value=value)
74+
else:
75+
return value
76+
77+
@staticmethod
78+
def _setup_headers(endpoint, params):
79+
'''
80+
Returns a dictionary with header_name: value format.
81+
Mandatory headers are always added and an exception will be thrown
82+
if the value is not available.
83+
Optional headers are only added if the value is available in params.
84+
85+
:param endpoint: dict. Endpoint description
86+
:param params: dict. List of parameter values
87+
88+
:rtype: dict.
89+
'''
90+
headers = {
91+
header['name']: BaseHttpClient._process_single_header(
92+
header, params[header['name']]
93+
)
94+
for header in endpoint['headers']
95+
if header.get('required', False)
96+
}
97+
98+
# add optional headers
99+
headers.update({
100+
header['name']: BaseHttpClient._process_single_header(
101+
header, params[header['name']]
102+
)
103+
for header in endpoint['headers']
104+
if (not header.get('required', False)) and header['name'] in params
105+
})
106+
107+
return headers
108+
109+
def _setup_url(self, endpoint, params):
110+
'''
111+
Instantiates the url template with values supplied in params.
112+
113+
:param endpoint: dict. Endpoint description.
114+
:param params: dict. Parameter values.
115+
116+
:rtype: string.
117+
'''
118+
base_template = '{base}/{endpoint}'.format(
119+
base=self.config['base_url'],
120+
endpoint=endpoint['url_template']
121+
)
122+
123+
params_for_url = BaseHttpClient.get_params_from_url_template(
124+
base_template
125+
)
126+
127+
parameter_values = {
128+
param: params[param]
129+
for param in params
130+
if param in params_for_url
131+
}
132+
133+
return base_template.format(**parameter_values)
134+
135+
@staticmethod
136+
def validate_params(endpoint, all_arguments):
137+
'''
138+
Checks that all the required parameters are supplied. Raises an
139+
exception otherwise.
140+
141+
:param endpoint: dict. Endpoint description
142+
:param all_arguments: Parameter values
143+
144+
:rtype: None
145+
'''
146+
required_params = (
147+
BaseHttpClient.get_params_from_url_template(endpoint['url_template']) +
148+
[i['name'] for i in endpoint['headers'] if i['required']] +
149+
[i['name'] for i in endpoint['query_string'] if i['required']]
150+
)
151+
152+
missing = [p for p in required_params if p not in all_arguments]
153+
154+
if missing:
155+
raise MissingParametersException(
156+
'The following required parameters are missing: {missing}'
157+
.format(missing=', '.join(missing))
158+
)

0 commit comments

Comments
 (0)