Skip to content

Commit 21096d8

Browse files
committed
Merge branch 'master' into dev-edition
2 parents 50b81fd + 9f8c975 commit 21096d8

File tree

15 files changed

+804
-88
lines changed

15 files changed

+804
-88
lines changed

HISTORY.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ Release History
66
Upcoming
77
++++++++
88

9+
1.4.1 (2016-02-11)
10+
++++++++++++++++++
11+
12+
- Files now support getting a direct download url.
13+
914
1.4.0 (2016-01-05)
1015
++++++++++++++++++
1116

README.rst

Lines changed: 64 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ box-python-sdk
1818
:target: https://pypi.python.org/pypi/boxsdk
1919

2020

21+
22+
.. contents:: :depth: 1
23+
24+
25+
2126
Installing
2227
----------
2328

@@ -123,13 +128,20 @@ Create subfolder
123128
# creates folder structure /L1/L2/L3
124129
client.folder(folder_id='0').create_subfolder('L1').create_subfolder('L2').create_subfolder('L3')
125130
126-
Get shared link
127-
~~~~~~~~~~~~~~~
131+
Get shared link (file or folder)
132+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
128133

129134
.. code-block:: python
130135
131136
shared_link = client.folder(folder_id='SOME_FOLDER_ID').get_shared_link()
132137
138+
Get shared link direct download URL (files only)
139+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
140+
141+
.. code-block:: python
142+
143+
download_url = client.file(file_id='SOME_FILE_ID').get_shared_link_download_url()
144+
133145
Get file name
134146
~~~~~~~~~~~~~
135147

@@ -173,6 +185,22 @@ Search
173185
174186
client.search('some_query', limit=100, offset=0)
175187
188+
Metadata Search
189+
~~~~~~~~~~~~~~~
190+
191+
.. code-block:: python
192+
193+
from boxsdk.object.search import MetadataSearchFilter, MetadataSearchFilters
194+
195+
metadata_search_filter = MetadataSearchFilter(template_key='marketingCollateral', scope='enterprise')
196+
metadata_search_filter.add_value_based_filter(field_key='documentType', value='datasheet')
197+
metadata_search_filter.add_value_based_filter(field_key='clientNumber', value='a123')
198+
199+
metadata_search_filters = MetadataSearchFilters()
200+
metadata_search_filters.add_filter(metadata_search_filter)
201+
202+
client.search('some_query', limit=100, offset=0, metadata_filters=metadata_search_filters)
203+
176204
Events
177205
~~~~~~
178206

@@ -208,9 +236,9 @@ Metadata
208236
As-User
209237
~~~~~~~
210238

211-
The `Client` class and all Box objects also have an `as_user` method.
239+
The ``Client`` class and all Box objects also have an ``as_user`` method.
212240

213-
`as-user` returns a copy of the object on which it was called that will make Box API requests
241+
``as-user`` returns a copy of the object on which it was called that will make Box API requests
214242
as though the specified user was making it.
215243

216244
See https://box-content.readme.io/#as-user-1 for more information about how this works via the Box API.
@@ -225,6 +253,30 @@ See https://box-content.readme.io/#as-user-1 for more information about how this
225253
# Same thing, but using file's as_user method
226254
client.file(file_id='SOME_FILE_ID').as_user(user).rename('bar-2.txt')
227255
256+
Other Requests
257+
~~~~~~~~~~~~~~
258+
259+
The Box API is continually evolving. As such, there are API endpoints available that are not specifically
260+
supported by the SDK. You can still use these endpoints by using the ``make_request`` method of the ``Client``.
261+
262+
.. code-block:: python
263+
264+
# https://box-content.readme.io/reference#get-metadata-schema
265+
from boxsdk.config import API
266+
# Returns a Python dictionary containing the result of the API request
267+
json_response = client.make_request(
268+
'GET',
269+
'{0}/metadata_templates/enterprise/customer/schema'.format(API.BASE_API_URL),
270+
).json()
271+
272+
``make_request()`` takes two parameters:
273+
274+
- ``method`` -an HTTP verb like ``GET`` or ``POST``
275+
- ``url`` - the URL of the requested API endpoint
276+
277+
``boxsdk.config.API`` is an object specifying which URLs to use in order to access the Box API. It can be used for
278+
formatting the URLs to use with ``make_request``. Box objects also have a ``get_url`` method. Pass it an endpoint
279+
to get the correct URL for use with that object and endpoint.
228280

229281
Box Developer Edition
230282
---------------------
@@ -238,8 +290,8 @@ Developer Edition support requires some extra dependencies. To get them, simply
238290
239291
pip install boxsdk[jwt]
240292
241-
Instead of instantiating your `Client` with an instance of `OAuth2`,
242-
instead use an instance of `JWTAuth`.
293+
Instead of instantiating your ``Client`` with an instance of ``OAuth2``,
294+
instead use an instance of ``JWTAuth``.
243295

244296
.. code-block:: python
245297
@@ -279,26 +331,26 @@ These users can then be authenticated:
279331
ned_auth.authenticate_app_user(ned_stark_user)
280332
ned_client = Client(ned_auth)
281333
282-
Requests made with `ned_client` (or objects returned from `ned_client`'s methods)
334+
Requests made with ``ned_client`` (or objects returned from ``ned_client``'s methods)
283335
will be performed on behalf of the newly created app user.
284336

285337
Other Auth Options
286338
------------------
287339

288340
For advanced uses of the SDK, two additional auth classes are provided:
289341

290-
- `CooperativelyManagedOAuth2`: Allows multiple auth instances to share tokens.
291-
- `RemoteOAuth2`: Allows use of the SDK on clients without access to your application's client secret. Instead, you
292-
provide a `retrieve_access_token` callback. That callback should perform the token refresh, perhaps on your server
342+
- ``CooperativelyManagedOAuth2``: Allows multiple auth instances to share tokens.
343+
- ``RemoteOAuth2``: Allows use of the SDK on clients without access to your application's client secret. Instead, you
344+
provide a ``retrieve_access_token`` callback. That callback should perform the token refresh, perhaps on your server
293345
that does have access to the client secret.
294-
- `RedisManagedOAuth2`: Stores access and refresh tokens in Redis. This allows multiple processes (possibly spanning
346+
- ``RedisManagedOAuth2``: Stores access and refresh tokens in Redis. This allows multiple processes (possibly spanning
295347
multiple machines) to share access tokens while synchronizing token refresh. This could be useful for a multiprocess
296348
web server, for example.
297349

298350
Other Network Options
299351
---------------------
300352

301-
For more insight into the network calls the SDK is making, you can use the `LoggingNetwork` class. This class logs
353+
For more insight into the network calls the SDK is making, you can use the ``LoggingNetwork`` class. This class logs
302354
information about network requests and responses made to the Box API.
303355

304356
.. code-block:: python

boxsdk/object/__init__.py

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,7 @@
22

33
from __future__ import unicode_literals
44

5-
import six
5+
from six.moves import map # pylint:disable=redefined-builtin
66

77

8-
__all__ = [
9-
'collaboration',
10-
'events',
11-
'file',
12-
'folder',
13-
'group',
14-
'group_membership',
15-
'search',
16-
'user',
17-
]
18-
19-
if six.PY2:
20-
__all__ = [unicode.encode(x, 'utf-8') for x in __all__]
8+
__all__ = list(map(str, ['collaboration', 'events', 'file', 'folder', 'group', 'group_membership', 'search', 'user']))

boxsdk/object/events.py

Lines changed: 91 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,53 @@
11
# coding: utf-8
22

33
from __future__ import unicode_literals
4+
45
from requests.exceptions import Timeout
6+
from six import with_metaclass
57

68
from boxsdk.object.base_endpoint import BaseEndpoint
9+
from boxsdk.util.enum import ExtendableEnumMeta
710
from boxsdk.util.lru_cache import LRUCache
11+
from boxsdk.util.text_enum import TextEnum
12+
13+
14+
# pylint:disable=too-many-ancestors
15+
class EventsStreamType(with_metaclass(ExtendableEnumMeta, TextEnum)):
16+
"""An enum of all possible values of the `stream_type` parameter for user events.
17+
18+
The value of the `stream_type` parameter determines the type of events
19+
returned by the endpoint.
20+
21+
<https://box-content.readme.io/reference#events>
22+
"""
23+
24+
25+
class UserEventsStreamType(EventsStreamType):
26+
"""An enum of all possible values of the `stream_type` parameter for user events.
27+
28+
- ALL: Returns all user events.
29+
- CHANGES: Returns tree changes.
30+
- SYNC: Returns tree changes only for sync folders.
31+
32+
<https://box-content.readme.io/reference#standard-user-events>
33+
"""
34+
ALL = 'all'
35+
CHANGES = 'changes'
36+
SYNC = 'sync'
37+
38+
39+
class EnterpriseEventsStreamType(EventsStreamType):
40+
"""An enum of all possible values of the `stream_type` parameter for enterprise events.
41+
42+
- ADMIN_LOGS: Retrieves up to a year's events for all users in the enterprise.
43+
44+
NOTE: Requires Admin: These stream types will only work with an auth token
45+
from an enterprise admin account.
46+
47+
<https://box-content.readme.io/reference#enterprise-events>
48+
"""
49+
ADMIN_LOGS = 'admin_logs'
50+
# pylint:enable=too-many-ancestors
851

952

1053
class Events(BaseEndpoint):
@@ -14,7 +57,7 @@ def get_url(self, *args):
1457
"""Base class override."""
1558
return super(Events, self).get_url('events', *args)
1659

17-
def get_events(self, limit=100, stream_position=0, stream_type='all'):
60+
def get_events(self, limit=100, stream_position=0, stream_type=UserEventsStreamType.ALL):
1861
"""
1962
Get Box events from a given stream position for a given stream type.
2063
@@ -25,12 +68,16 @@ def get_events(self, limit=100, stream_position=0, stream_type='all'):
2568
:param stream_position:
2669
The location in the stream from which to start getting events. 0 is the beginning of time. 'now' will
2770
return no events and just current stream position.
71+
72+
NOTE: Currently, 'now' is only valid for user events stream types. The request will fail if an
73+
enterprise events stream type is passed.
2874
:type stream_position:
2975
`unicode`
3076
:param stream_type:
31-
Which type of events to return. Can be 'all', 'tree', or 'sync'.
77+
(optional) Which type of events to return.
78+
Defaults to `UserEventsStreamType.ALL`.
3279
:type stream_type:
33-
`unicode`
80+
:enum:`EventsStreamType`
3481
:returns:
3582
JSON response from the Box /events endpoint. Contains the next stream position to use for the next call,
3683
along with some number of events.
@@ -46,26 +93,38 @@ def get_events(self, limit=100, stream_position=0, stream_type='all'):
4693
box_response = self._session.get(url, params=params)
4794
return box_response.json()
4895

49-
def get_latest_stream_position(self):
96+
def get_latest_stream_position(self, stream_type=UserEventsStreamType.ALL):
5097
"""
5198
Get the latest stream position. The return value can be used with :meth:`get_events` or
5299
:meth:`generate_events_with_long_polling`.
53100
101+
:param stream_type:
102+
(optional) Which events stream to query.
103+
Defaults to `UserEventsStreamType.ALL`.
104+
105+
NOTE: Currently, the Box API requires this to be one of the user
106+
events stream types. The request will fail if an enterprise events
107+
stream type is passed.
108+
:type stream_type:
109+
:enum:`UserEventsStreamType`
54110
:returns:
55111
The latest stream position.
56112
:rtype:
57113
`unicode`
58114
"""
59-
url = self.get_url()
60-
params = {
61-
'stream_position': 'now',
62-
}
63-
return self._session.get(url, params=params).json()['next_stream_position']
115+
return self.get_events(limit=0, stream_position='now', stream_type=stream_type)['next_stream_position']
64116

65-
def _get_all_events_since(self, stream_position):
117+
def _get_all_events_since(self, stream_position, stream_type=UserEventsStreamType.ALL):
118+
"""
119+
:param stream_type:
120+
(optional) Which type of events to return.
121+
Defaults to `UserEventsStreamType.ALL`.
122+
:type stream_type:
123+
:enum:`EventsStreamType`
124+
"""
66125
next_stream_position = stream_position
67126
while True:
68-
events = self.get_events(stream_position=next_stream_position, limit=100)
127+
events = self.get_events(stream_position=next_stream_position, limit=100, stream_type=stream_type)
69128
next_stream_position = events['next_stream_position']
70129
events = events['entries']
71130
if not events:
@@ -102,7 +161,7 @@ def long_poll(self, options, stream_position):
102161
)
103162
return long_poll_response
104163

105-
def generate_events_with_long_polling(self, stream_position=None):
164+
def generate_events_with_long_polling(self, stream_position=None, stream_type=UserEventsStreamType.ALL):
106165
"""
107166
Subscribe to events from the given stream position.
108167
@@ -111,15 +170,24 @@ def generate_events_with_long_polling(self, stream_position=None):
111170
return no events and just current stream position.
112171
:type stream_position:
113172
`unicode`
173+
:param stream_type:
174+
(optional) Which type of events to return.
175+
Defaults to `UserEventsStreamType.ALL`.
176+
177+
NOTE: Currently, the Box API requires this to be one of the user
178+
events stream types. The request will fail if an enterprise events
179+
stream type is passed.
180+
:type stream_type:
181+
:enum:`UserEventsStreamType`
114182
:returns:
115183
Events corresponding to changes on Box in realtime, as they come in.
116184
:rtype:
117185
`generator` of :class:`Event`
118186
"""
119187
event_ids = LRUCache()
120-
stream_position = stream_position if stream_position is not None else self.get_latest_stream_position()
188+
stream_position = stream_position if stream_position is not None else self.get_latest_stream_position(stream_type=stream_type)
121189
while True:
122-
options = self.get_long_poll_options()
190+
options = self.get_long_poll_options(stream_type=stream_type)
123191
while True:
124192
try:
125193
long_poll_response = self.long_poll(options, stream_position)
@@ -129,7 +197,7 @@ def generate_events_with_long_polling(self, stream_position=None):
129197
message = long_poll_response.json()['message']
130198
if message == 'new_change':
131199
next_stream_position = stream_position
132-
for event, next_stream_position in self._get_all_events_since(stream_position):
200+
for event, next_stream_position in self._get_all_events_since(stream_position, stream_type=stream_type):
133201
try:
134202
event_ids.get(event['event_id'])
135203
except KeyError:
@@ -142,10 +210,15 @@ def generate_events_with_long_polling(self, stream_position=None):
142210
else:
143211
break
144212

145-
def get_long_poll_options(self):
213+
def get_long_poll_options(self, stream_type=UserEventsStreamType.ALL):
146214
"""
147215
Get the url and retry timeout for setting up a long polling connection.
148216
217+
:param stream_type:
218+
(optional) Which type of events to return.
219+
Defaults to `UserEventsStreamType.ALL`.
220+
:type stream_type:
221+
:enum:`EventsStreamType`
149222
:returns:
150223
A `dict` including a long poll url, retry timeout, etc.
151224
E.g.
@@ -160,5 +233,6 @@ def get_long_poll_options(self):
160233
`dict`
161234
"""
162235
url = self.get_url()
163-
box_response = self._session.options(url)
236+
params = {'stream_type': stream_type}
237+
box_response = self._session.options(url, params=params)
164238
return box_response.json()['entries'][0]

0 commit comments

Comments
 (0)