Skip to content

Commit bcd6b2c

Browse files
committed
Added full support for Admin API: (Users, Groups, Splits, Split Definitions, Segments, Segments Definitions, Rollout dashboard, Change requests and API Keys)
1 parent 9340517 commit bcd6b2c

78 files changed

Lines changed: 4668 additions & 119 deletions

File tree

Some content is hidden

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

.DS_Store

10 KB
Binary file not shown.

CHANGES.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,6 @@
11
1.0 (July 10th, 2017)
22
- Initial release
3+
2.0 (June 23rd, 2019)
4+
- Added Workspace and update base URL to use v2
5+
3.0 (June 23rd, 2019)
6+
- Added support for Users, Groups, Splits, Split Definitions, Segments, Segments Definitions, Rollout dashboard, Change requests and API Keys

README.md

Lines changed: 160 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,166 @@ This API wrapper is designed to work with [Split](https://www.split.io), the pla
66

77
For specific instructions on how to use this API refer to our [official API documentation](https://docs.split.io/reference).
88

9+
Install the split-admin-api:
10+
`pip install split-admin-api`
11+
12+
Import the client object and initialize connection using an Admin API Key:
13+
`from splitapiclient.main import get_client
14+
client = get_client({'apikey': 'ADMIN API KEY'})
15+
`
16+
Enable optional logging:
17+
`import logging
18+
logging.basicConfig(level=logging.DEBUG)
19+
`
20+
**Workspaces**
21+
Fetch all workspaces:
22+
`for ws in client.workspaces.list():
23+
print ("\nWorkspace:"+ws.name+", Id: "+ws.id)
24+
`
25+
Find a specific workspaces:
26+
`ws = client.workspaces.find("Defaults")
27+
print ("\nWorkspace:"+ws.name+", Id: "+ws.id)
28+
`
29+
**Environments**
30+
Fetch all Environments:
31+
`ws = client.workspaces.find("Defaults")
32+
for env in client.environments.list(ws.id):
33+
print ("\nEnvironment: "+env.name+", Id: "+env.id)
34+
Add new environment:
35+
`ws = client.workspaces.find("Defaults")
36+
env = ws.add_environment({'name':'new_environment', 'production':False})
37+
`
38+
39+
**Splits**
40+
Fetch all Splits:
41+
`ws = client.workspaces.find("Defaults")
42+
for split in client.splits.list(ws.id):
43+
print ("\nSplit: "+split.name+", "+split._workspace_id)
44+
`
45+
Add new Split:
46+
`
47+
split = ws.add_split({'name':'split_name', 'description':'new UI feature'}, "user")
48+
print(split.name)
49+
`
50+
Add tags:
51+
`
52+
split.associate_tags(['my_new_tag', 'another_new_tag'])
53+
`
54+
Add Split to environment:
55+
`
56+
ws = client.workspaces.find("Defaults")
57+
split = client.splits.find("new_feature", ws.id)
58+
env = client.environments.find("Production", ws.id)
59+
tr1 = treatment.Treatment({"name":"on","configurations":""})
60+
tr2 = treatment.Treatment({"name":"off","configurations":""})
61+
bk1 = bucket.Bucket({"treatment":"on","size":50})
62+
bk2 = bucket.Bucket({"treatment":"off","size":50})
63+
match = matcher.Matcher({"attribute":"group","type":"IN_LIST_STRING","strings":["employees"]})
64+
cond = condition.Condition({'matchers':[match.export_dict()]})
65+
rl = rule.Rule({'condition':cond.export_dict(), 'buckets':[bk1.export_dict(), bk2.export_dict()]})
66+
defrl = default_rule.DefaultRule({"treatment":"off","size":100})
67+
data={"treatments":[tr1.export_dict() ,tr2.export_dict()],"defaultTreatment":"off", "baselineTreatment": "off","rules":[rl.export_dict()],"defaultRule":[defrl.export_dict()], "comment": "adding split to production"}
68+
splitdef = split.add_to_environment(env.id, data)
69+
`
70+
Kill Split:
71+
`
72+
ws = client.workspaces.find("Defaults")
73+
env = client.environments.find("Production", ws.id)
74+
splitDef = client.split_definitions.find("new_feature", env.id, ws.id)
75+
splitDef.kill()
76+
`
77+
Restore Split:
78+
`
79+
splitDef.restore()
80+
`
81+
Fetch all Splits in an Environment:
82+
`
83+
ws = client.workspaces.find("Defaults")
84+
env = client.environments.find("Production", ws.id)
85+
for spDef in client.split_definitions.list(env.id, ws.id):
86+
print(spDef.name+str(spDef._default_rule))
87+
`
88+
Submit a Change request to update a Split definition:
89+
`
90+
splitDef = client.split_definitions.find("new_feature", env.id, ws.id)
91+
definition= {"treatments":[ {"name":"on"},{"name":"off"}],
92+
"defaultTreatment":"off", "baselineTreatment": "off",
93+
"rules": [],
94+
"defaultRule":[{"treatment":"off","size":100}],"comment": "updating default rule"
95+
}
96+
splitDef.submit_change_request(definition, 'UPDATE', 'updating default rule', 'comment', ['user@email.com'], '')
97+
`
98+
List all change requests:
99+
`
100+
for cr in client.change_requests.list():
101+
if cr._split is not None:
102+
print (cr._id+", "+cr._split['name']+", "+cr._title+", "+str(cr._split['environment']['id']))
103+
if cr._segment is not None:
104+
print (cr._id+", "+cr._segment['name']+", "+cr._title)
105+
`
106+
Approve specific change request:
107+
`
108+
for cr in client.change_requests.list():
109+
if cr._split['name']=='new_feature':
110+
cr.update_status("APPROVED", "done")
111+
`
112+
**Users and Groups**
113+
Fetch all Active users:
114+
`
115+
for user in client.users.list('ACTIVE'):
116+
print(user.email+", "+user._id)
117+
`
118+
Invite new user:
119+
`
120+
group = client.groups.find('Administrators')
121+
userData = {'email':'user@email.com', 'groups':[{'id':'', 'type':'group'}]}
122+
userData['groups'][0]['id'] = group._id
123+
client.users.invite_user(userData)
124+
`
125+
Delete a pending invite:
126+
`
127+
for user in client.users.list('PENDING'):
128+
print(user.email+", "+user._id+", "+user._status)
129+
if user.email == 'user@email.com':
130+
client.users.delete(user._id)
131+
`
132+
Update user info:
133+
`
134+
data = {'name':'new_name', 'email':'user@email.com', '2fa':False, 'status':'ACTIVE'}
135+
user = client.users.find('user@email.com')
136+
user.update_user(user._id, data)
137+
`
138+
Fetch all Groups:
139+
`
140+
for group in client.groups.list():
141+
print (group._id+", "+group._name)
142+
`
143+
Create Group:
144+
`
145+
client.groups.create_group({'name':'new_group', 'description':''})
146+
`
147+
Delete Group:
148+
`
149+
group = client.groups.find('new_group')
150+
client.groups.delete_group(group._id)
151+
`
152+
Replace existing user group:
153+
`
154+
user = client.users.find('user@email.com')
155+
group = client.groups.find('Administrators')
156+
data = [{'op': 'replace', 'path': '/groups/0', 'value': {'id': '<groupId>', 'type':'group'}}]
157+
data[0]['value']['id'] = group._id
158+
user.update_user_group(data)
159+
`
160+
Add user to new group
161+
`
162+
user = client.users.find('user@email.com')
163+
group = client.groups.find('Administrators')
164+
data = [{'op': 'add', 'path': '/groups/-', 'value': {'id': '<groupId>', 'type':'group'}}]
165+
data[0]['value']['id'] = group._id
166+
user.update_user_group(data)
167+
`
168+
9169
### Commitment to Quality:
10170

11171
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.
@@ -14,17 +174,6 @@ Split’s APIs are in active development and are constantly tested for quality.
14174

15175
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.
16176

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-
28177
**Try Split for Free:**
29178

30179
Split is available as a 14-day free trial. To create an account, visit [split.io/trial](https://www.split.io/trial).

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ def run(self):
9090
tests_requires = [
9191
'mock==2.0.0',
9292
'pytest-mock==1.6.0',
93-
'pytest==3.1.3',
93+
'pytest==6.2.4',
9494
]
9595

9696

splitapiclient/.DS_Store

18 KB
Binary file not shown.

splitapiclient/http_clients/base_client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ def make_request(self, method, body=None, **kwargs):
4545
@staticmethod
4646
def get_params_from_url_template(url):
4747
'''
48-
Retuns a list of tempated variables that must be replaced when
48+
Retuns a list of templated variables that must be replaced when
4949
instantiating the url template.
5050
5151
:param url: url template

splitapiclient/http_clients/sync_client.py

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import absolute_import, division, print_function, \
22
unicode_literals
33
import json
4+
import time
45
from functools import partial
56
import requests
67
from splitapiclient.http_clients import base_client
@@ -95,15 +96,23 @@ def make_request(self, endpoint, body=None, **kwargs):
9596
LOGGER.debug('BODY: ' + json.dumps(body))
9697

9798
# Make the actual HTTP call!
98-
try:
99-
response = method(url, headers=headers)
100-
LOGGER.debug('RESPONSE: ' + response.text)
101-
except Exception as e:
102-
return self._handle_connection_error(e)
103-
104-
if not response.status_code == 200:
99+
while True:
100+
try:
101+
response = method(url, headers=headers)
102+
LOGGER.debug('RESPONSE: ' + response.text)
103+
except Exception as e:
104+
return self._handle_connection_error(e)
105+
if response.status_code==429:
106+
LOGGER.warning('RESPONSE CODE: %s, retrying in 5 seconds' % response.status_code)
107+
time.sleep(5)
108+
continue
109+
else:
110+
break
111+
112+
if not (response.status_code == 200 or response.status_code == 204):
105113
LOGGER.warning('RESPONSE CODE: %s' % response.status_code)
106114
self._handle_invalid_response(response)
107115

108116
if endpoint.get('response', False):
109-
return json.loads(response.text)
117+
if response.status_code != 204:
118+
return json.loads(response.text)

splitapiclient/main/.DS_Store

6 KB
Binary file not shown.

splitapiclient/main/apiclient.py

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,27 @@ def traffic_types(self):
1717
pass
1818

1919
@abc.abstractproperty
20-
def environments(sself):
20+
def environments(self):
2121
pass
2222

2323
@abc.abstractproperty
24-
def workspaces(sself):
24+
def splits(self):
25+
pass
26+
27+
@abc.abstractproperty
28+
def split_definitions(self):
29+
pass
30+
31+
@abc.abstractproperty
32+
def segments(self):
33+
pass
34+
35+
@abc.abstractproperty
36+
def segment_definitions(self):
37+
pass
38+
39+
@abc.abstractproperty
40+
def workspaces(self):
2541
pass
2642

2743

@@ -32,3 +48,19 @@ def attributes(self):
3248
@abc.abstractproperty
3349
def identities(self):
3450
pass
51+
52+
@abc.abstractproperty
53+
def change_requests(self):
54+
pass
55+
56+
@abc.abstractproperty
57+
def users(self):
58+
pass
59+
60+
@abc.abstractproperty
61+
def groups(self):
62+
pass
63+
64+
@abc.abstractproperty
65+
def apikeys(self):
66+
pass

splitapiclient/main/sync_apiclient.py

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,17 @@
55
from splitapiclient.util.exceptions import InsufficientConfigArgumentsException
66
from splitapiclient.microclients import TrafficTypeMicroClient
77
from splitapiclient.microclients import EnvironmentMicroClient
8+
from splitapiclient.microclients import SplitMicroClient
9+
from splitapiclient.microclients import SplitDefinitionMicroClient
10+
from splitapiclient.microclients import SegmentMicroClient
11+
from splitapiclient.microclients import SegmentDefinitionMicroClient
812
from splitapiclient.microclients import WorkspaceMicroClient
913
from splitapiclient.microclients import IdentityMicroClient
1014
from splitapiclient.microclients import AttributeMicroClient
15+
from splitapiclient.microclients import ChangeRequestMicroClient
16+
from splitapiclient.microclients import UserMicroClient
17+
from splitapiclient.microclients import GroupMicroClient
18+
from splitapiclient.microclients import APIKeyMicroClient
1119

1220

1321
class SyncApiClient(BaseApiClient):
@@ -42,14 +50,20 @@ def __init__(self, config):
4250

4351
self._apikey = config['apikey']
4452

45-
http_client = SyncHttpClient(self._base_url_old, self._apikey)
46-
self._traffic_type_client = TrafficTypeMicroClient(http_client)
47-
4853
http_client = SyncHttpClient(self._base_url, self._apikey)
4954
self._environment_client = EnvironmentMicroClient(http_client)
55+
self._split_client = SplitMicroClient(http_client)
56+
self._split_definition_client = SplitDefinitionMicroClient(http_client)
57+
self._segment_client = SegmentMicroClient(http_client)
58+
self._segment_definition_client = SegmentDefinitionMicroClient(http_client)
5059
self._workspace_client = WorkspaceMicroClient(http_client)
60+
self._traffic_type_client = TrafficTypeMicroClient(http_client)
5161
self._attribute_client = AttributeMicroClient(http_client)
5262
self._identity_client = IdentityMicroClient(http_client)
63+
self._change_request_client = ChangeRequestMicroClient(http_client)
64+
self._user_client = UserMicroClient(http_client)
65+
self._group_client = GroupMicroClient(http_client)
66+
self._apikey_client = APIKeyMicroClient(http_client)
5367

5468
@property
5569
def traffic_types(self):
@@ -59,6 +73,22 @@ def traffic_types(self):
5973
def environments(self):
6074
return self._environment_client
6175

76+
@property
77+
def splits(self):
78+
return self._split_client
79+
80+
@property
81+
def split_definitions(self):
82+
return self._split_definition_client
83+
84+
@property
85+
def segments(self):
86+
return self._segment_client
87+
88+
@property
89+
def segment_definitions(self):
90+
return self._segment_definition_client
91+
6292
@property
6393
def workspaces(self):
6494
return self._workspace_client
@@ -70,3 +100,19 @@ def attributes(self):
70100
@property
71101
def identities(self):
72102
return self._identity_client
103+
104+
@property
105+
def change_requests(self):
106+
return self._change_request_client
107+
108+
@property
109+
def users(self):
110+
return self._user_client
111+
112+
@property
113+
def groups(self):
114+
return self._group_client
115+
116+
@property
117+
def apikeys(self):
118+
return self._apikey_client

0 commit comments

Comments
 (0)