Skip to content

Commit 046aca4

Browse files
authored
Merge pull request #22 from adobe-apiplatform/v2
Release 2.0rc1
2 parents 5d4264e + b34e8d4 commit 046aca4

22 files changed

+219
-25
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# platform artifacts
22
.*
3+
!.travis.yml
34

45
# build and scratch areas
56
local/

.travis.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
language: python
2+
python:
3+
- 2.7
4+
- 3.4
5+
- 3.5
6+
install:
7+
- pip install --upgrade pip setuptools wheel
8+
- pip install -r requirements.txt
9+
script:
10+
- python setup.py test

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2016 Adobe Systems Incorporated. All rights reserved.
3+
Copyright (c) 2016-2017 Adobe Systems Incorporated. All rights reserved.
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ which provides Adobe Enterprise customers the ability to manage their users. Th
88
client makes it easy to access the UMAPI from a local Python application.
99

1010
This client is open source, maintained by Adobe, and distributed under the terms
11-
of the OSI-approved MIT license. Copyright (c) 2016 Adobe Systems Incorporated.
11+
of the OSI-approved MIT license. Copyright (c) 2016-2017 Adobe Systems Incorporated.
1212

1313
# Installation
1414

docs/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ which provides Adobe Enterprise customers the ability to manage their users. Th
88
client makes it easy to access the UMAPI from a local Python application.
99

1010
This client is open source, maintained by Adobe, and distributed under the terms
11-
of the OSI-approved MIT license. Copyright (c) 2016 Adobe Systems Incorporated.
11+
of the OSI-approved MIT license. Copyright (c) 2016-2017 Adobe Systems Incorporated.
1212

1313
# Installation
1414

docs/usage-instructions-v2.md

Lines changed: 164 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,14 @@ a Python dictionary of its attributes.
9393

9494
## Get a List of Users
9595

96+
The following code enumerates the first 5 users, printing the email of each.
97+
This will only have fetched the first page of results. Then, after the loop,
98+
the `all_results` call will force the fetch of all remaining pages so the
99+
list of all results can be constructed. ()Once the `all_results` call has been made,
100+
you cannot enumerate again without first calling `reload`.)
101+
96102
```python
97-
users = QueryUsers(conn)
103+
users = umapi_client.QueryUsers(conn)
98104
# print first 5 users
99105
for i, user in enumerate(users):
100106
if i == 5: break
@@ -109,7 +115,7 @@ This list of groups will contain both user groups and product license
109115
configuration groups.
110116

111117
```python
112-
groups = QueryGroups(conn)
118+
groups = umapi_client.QueryGroups(conn)
113119
# print all the group details
114120
for group in groups:
115121
print(group)
@@ -122,5 +128,160 @@ for group in groups:
122128

123129
# Performing Operations on Users
124130

125-
_...under construction..._
131+
User operations in the UMAPI are performed in three steps:
132+
133+
1. You specify the user to be operated on.
134+
2. You specify the operations to be performed on the user.
135+
3. You submit the user and operations to the UMAPI server.
136+
137+
The combined specification of the user identity and the operations to be performed
138+
is called an _action_ in the UMAPI documentation, while the individual operations are called
139+
_commands_. If you read the documentation carefully, you will see that there
140+
are limits to how many actions can be submitted to the UMAPI
141+
service in a single call, how many commands there can be in a single action, and
142+
how many calls can be submitted in a given period of time. However,
143+
the `umapi_client` implementation has been design to insulate
144+
your application from these limits
145+
by
146+
packing as many commands as allowed into each action,
147+
batching up as many actions as possible into a call,
148+
and spacing calls out when required to by the server. Thus, from an
149+
application perspective, you can simply follow the three steps above for each
150+
user and not worry about the mechanics of server communication limits.
151+
152+
## Step 1: Specify the User
153+
154+
To operate on a user, you first create a `UserAction` object that
155+
specifies the user's identity type, domain, and unique ID in the domain.
156+
157+
In most cases,
158+
the user's email ID will be his unique ID and will itself contain his domain,
159+
as in these examples:
160+
161+
```python
162+
from umapi_client import IdentityTypes
163+
user1 = UserAction(id_type=IdentityTypes.adobeID, email="[email protected]")
164+
user2 = UserAction(id_type=IdentityTypes.enterpriseID, email="[email protected]")
165+
```
166+
167+
But when Federated ID is being used, and a non-email username is being
168+
used to identify users across the SAML connection, both the username
169+
and the domain must be specified separately, as in these examples:
170+
171+
```python
172+
user3 = UserAction(id_type=IdentityTypes.federatedID,
173+
username="user347", domain="division.conglomerate.com")
174+
user4 = UserAction(id_type=IdentityTypes.federatedID,
175+
username="user348", domain="division.conglomerate.com",
176+
177+
```
178+
179+
Note that, as in the last example, it's OK to specify the email when
180+
creating a user object even if the email is not the unique ID or
181+
doesn't use the same domain. If
182+
you later perform an operations on a user which requires the email
183+
(such as user creation on the Adobe side), the email will be remembered
184+
and supplied from the UserAction object.
185+
186+
## Step 2: Specify the Operations
187+
188+
Once you have a `UserAction` object for a user, you can specify
189+
operations (called _commands_) to perform on that user. For
190+
example, to create a new user on the Adobe side, for the users
191+
that were specified in the last section, we could do:
192+
193+
```python
194+
user1.create()
195+
user2.create(first_name="Geoffrey", last_name="Giraffe")
196+
user3.create(email="[email protected]", country="US")
197+
user4.create(first_name="John", last_name="User", country="US")
198+
```
199+
200+
When creating users, the email address is mandatory if not already specified
201+
when creating the user action. First and last name and country can be optionally
202+
specified ()except for Adobe ID users),
203+
and country _must_ be specified for Federated ID users.
204+
205+
If a user has already been created, but you want to update attributes,
206+
you can use the `update` rather than the `create` command:
207+
208+
```python
209+
user2.update(first_name="Jeff", country="AU")
210+
user4.update(username="user0347")
211+
```
212+
213+
You can also specify to create if necessary, but update if already created:
214+
215+
```python
216+
from umapi_client import IfAlreadyExistsOptions
217+
user4.create(first_name="John", last_name="User", country="US",
218+
on_conflict=IfAlreadyExistsOptions.updateIfAlreadyExists)
219+
```
220+
221+
There are many other operations you can perform, such as adding and removing
222+
users from user groups and product configuration groups. Because each
223+
operation specifier returns the user, it's easy to chain the together:
224+
225+
```python
226+
user2.add_group(groups=["Photoshop", "Illustrator"]).remove_group(groups=["CC All Apps"])
227+
```
228+
229+
The details of all the possible commands are specified in the code,
230+
and more user documentation will be forthcoming. In general, commands
231+
are performed in the order they are specified, except for certain special
232+
commands such as ```create``` which are always performed first regardless
233+
of when they were specified.
234+
235+
## Step 3: Submit to the UMAPI server
236+
237+
Once you have specified all the desired operations on a given `UserAction`,
238+
you can submit it to the server as follows (recall that `conn` is an authorized
239+
connection to the UMAPI server, as created above):
240+
241+
```python
242+
result = conn.execute_single(user1)
243+
result = conn.execute_multiple([user2, user3, user4])
244+
```
245+
246+
By default, `execute_single` queues the action for sending to the server
247+
when a "full batch" (of 10) actions has been accumulated, but
248+
`execute_multiple` forces a batch to be sent (including any
249+
previously queued actions as well as the specified ones). You can
250+
override these defaults with the `immediate` argument, as in:
251+
252+
```python
253+
result = conn.execute_single(user1, immediate=True)
254+
result = conn.execute_multiple([user2, user3, user4], immediate=False)
255+
```
256+
257+
The result of either execute operation is a tuple of three numbers
258+
`(queued, sent, succeeded)` which tell you how many actions were
259+
queued, how many were sent, and how many of those sent succeeded
260+
without errors. So, for example, in the following code:
261+
262+
```python
263+
queued, _, _ = conn.execute_single(user1)
264+
_, sent, succeeded = conn.execute_multiple(user2, user3, user4)
265+
```
266+
267+
we would likely see `queued = 1`, `sent = 4`, and `succeeded = 4`.
268+
269+
If, for some reason, the succeeded number is not equal to the sent
270+
number for any call, it means that not all of the actions were
271+
executed successfully on the server-side: one or more of the commands
272+
failed to execute. In such cases, the server will have sent back
273+
error information which the `umapi_client` implementation records
274+
against the commands that failed, you can call the `execution_errors`
275+
method on the user actions to get a list of the failed commands
276+
and the server error information. For example, if only three
277+
of the four actions sent had succeeded, then we could execute
278+
this code:
279+
280+
```python
281+
actions = (user1, user2, user3, user4)
282+
errors = [info for action in actions for info in action.execution_errors()]
283+
```
126284

285+
Each entry in errors would then be a dictionary giving
286+
the command that failed, the target user it failed on,
287+
and server information about the reason for the failure.

requirements.txt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# running
2+
requests>=2.4.2
3+
cryptography
4+
PyJWT
5+
six
6+
enum34
7+
# testing
8+
pytest>=3.0.5
9+
mock
10+
PyYAML
11+
# setup.py testing
12+
pytest-runner

setup.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright (c) 2016 Adobe Systems Incorporated. All rights reserved.
1+
# Copyright (c) 2016-2017 Adobe Systems Incorporated. All rights reserved.
22
#
33
# Permission is hereby granted, free of charge, to any person obtaining a copy
44
# of this software and associated documentation files (the "Software"), to deal
@@ -21,13 +21,13 @@
2121
from setuptools import setup, find_packages
2222

2323
setup(name='umapi-client',
24-
version='2.0b2',
24+
version='2.0b3',
2525
description='Client for the User Management API (UMAPI) from Adobe - see https://adobe.ly/2h1pHgV',
2626
long_description=('The User Management API (aka the UMAPI) is an Adobe-hosted network service '
2727
'which provides Adobe Enterprise customers the ability to manage their users. This '
2828
'client makes it easy to access the UMAPI from a local Python application. '
2929
'This client is open source, maintained by Adobe, and distributed under the terms '
30-
'of the OSI-approved MIT license. Copyright (c) 2016 Adobe Systems Incorporated.'),
30+
'of the OSI-approved MIT license. Copyright (c) 2016-2017 Adobe Systems Incorporated.'),
3131
classifiers=[
3232
'Development Status :: 5 - Production/Stable',
3333
'Programming Language :: Python :: 2.7',

tests/conftest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright (c) 2016 Adobe Systems Incorporated. All rights reserved.
1+
# Copyright (c) 2016-2017 Adobe Systems Incorporated. All rights reserved.
22
#
33
# Permission is hereby granted, free of charge, to any person obtaining a copy
44
# of this software and associated documentation files (the "Software"), to deal

tests/test_actions.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright (c) 2016 Adobe Systems Incorporated. All rights reserved.
1+
# Copyright (c) 2016-2017 Adobe Systems Incorporated. All rights reserved.
22
#
33
# Permission is hereby granted, free of charge, to any person obtaining a copy
44
# of this software and associated documentation files (the "Software"), to deal
@@ -87,6 +87,7 @@ def test_execute_single_error_queued_throttled():
8787
action = Action(top="top").append(a="a").append(b="b").append(c="c").append(d="d")
8888
assert conn.execute_single(action) == (0, 4, 3)
8989
assert action.execution_errors() == [{"command": {"d": "d"},
90+
"target": {"top": "top"},
9091
"errorCode": "test.error",
9192
"message": "Test error message"}]
9293

@@ -100,7 +101,7 @@ def test_execute_single_error_immediate_throttled():
100101
conn = Connection(throttle_commands=2, **mock_connection_params)
101102
action = Action(top="top0").append(a="a0").append(a="a1").append(a="a2")
102103
assert conn.execute_single(action, immediate=True) == (0, 2, 1)
103-
assert action.execution_errors() == [{"command": {"a": "a2"}, "errorCode": "test"}]
104+
assert action.execution_errors() == [{"command": {"a": "a2"}, "target": {"top": "top0"}, "errorCode": "test"}]
104105

105106

106107
def test_execute_single_dofirst_success_immediate():
@@ -121,6 +122,7 @@ def test_execute_single_error_immediate():
121122
action = Action(top="top").append(a="a")
122123
assert conn.execute_single(action, immediate=True) == (0, 1, 0)
123124
assert action.execution_errors() == [{"command": {"a": "a"},
125+
"target": {"top": "top"},
124126
"errorCode": "test.error",
125127
"message": "Test error message"}]
126128

@@ -138,9 +140,11 @@ def test_execute_single_multi_error_immediate():
138140
action = Action(top="top").append(a="a")
139141
assert conn.execute_single(action, immediate=True) == (0, 1, 0)
140142
assert action.execution_errors() == [{"command": {"a": "a"},
143+
"target": {"top": "top"},
141144
"errorCode": "error1",
142145
"message": "message1"},
143146
{"command": {"a": "a"},
147+
"target": {"top": "top"},
144148
"errorCode": "error2",
145149
"message": "message2"}]
146150

@@ -155,6 +159,7 @@ def test_execute_single_dofirst_error_immediate():
155159
action = Action(top="top").insert(a="a")
156160
assert conn.execute_single(action, immediate=True) == (0, 1, 0)
157161
assert action.execution_errors() == [{"command": {"a": "a"},
162+
"target": {"top": "top"},
158163
"errorCode": "test.error",
159164
"message": "Test error message"}]
160165

@@ -201,6 +206,7 @@ def test_execute_multiple_error():
201206
assert conn.execute_multiple([action0, action1]) == (0, 2, 1)
202207
assert action0.execution_errors() == []
203208
assert action1.execution_errors() == [{"command": {"b": "b"},
209+
"target": {"top": "top1"},
204210
"errorCode": "test.error",
205211
"message": "Test error message"}]
206212

@@ -222,9 +228,11 @@ def test_execute_multiple_multi_error():
222228
assert conn.execute_multiple([action0, action1]) == (0, 2, 1)
223229
assert action0.execution_errors() == []
224230
assert action1.execution_errors() == [{"command": {"b": "b"},
231+
"target": {"top": "top1"},
225232
"errorCode": "error1",
226233
"message": "message1"},
227234
{"command": {"b": "b"},
235+
"target": {"top": "top1"},
228236
"errorCode": "error2",
229237
"message": "message2"}]
230238

@@ -243,6 +251,7 @@ def test_execute_multiple_dofirst_error():
243251
assert conn.execute_multiple([action0, action1]) == (0, 2, 1)
244252
assert action0.execution_errors() == []
245253
assert action1.execution_errors() == [{"command": {"a": "a1"},
254+
"target": {"top": "top1"},
246255
"errorCode": "test.error",
247256
"message": "Test error message"}]
248257

@@ -277,7 +286,7 @@ def test_execute_multiple_single_queued_throttle_actions():
277286
"actions-queued": 0}
278287
assert action0.execution_errors() == []
279288
assert action1.execution_errors() == []
280-
assert action2.execution_errors() == [{"command": {"a": "a2"}, "errorCode": "test"}]
289+
assert action2.execution_errors() == [{"command": {"a": "a2"}, "target": {"top": "top2"}, "errorCode": "test"}]
281290
assert action3.execution_errors() == []
282291

283292

0 commit comments

Comments
 (0)