Skip to content

Commit 8b6afa5

Browse files
committed
Merge pull request #109 from box/long_poll_stream_type
Add stream_type param for events long polling
2 parents 5f151f8 + a6441cb commit 8b6afa5

File tree

6 files changed

+552
-39
lines changed

6 files changed

+552
-39
lines changed

README.rst

Lines changed: 15 additions & 10 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

@@ -224,9 +229,9 @@ Metadata
224229
As-User
225230
~~~~~~~
226231

227-
The `Client` class and all Box objects also have an `as_user` method.
232+
The ``Client`` class and all Box objects also have an ``as_user`` method.
228233

229-
`as-user` returns a copy of the object on which it was called that will make Box API requests
234+
``as-user`` returns a copy of the object on which it was called that will make Box API requests
230235
as though the specified user was making it.
231236

232237
See https://box-content.readme.io/#as-user-1 for more information about how this works via the Box API.
@@ -254,8 +259,8 @@ Developer Edition support requires some extra dependencies. To get them, simply
254259
255260
pip install boxsdk[jwt]
256261
257-
Instead of instantiating your `Client` with an instance of `OAuth2`,
258-
instead use an instance of `JWTAuth`.
262+
Instead of instantiating your ``Client`` with an instance of ``OAuth2``,
263+
instead use an instance of ``JWTAuth``.
259264

260265
.. code-block:: python
261266
@@ -295,26 +300,26 @@ These users can then be authenticated:
295300
ned_auth.authenticate_app_user(ned_stark_user)
296301
ned_client = Client(ned_auth)
297302
298-
Requests made with `ned_client` (or objects returned from `ned_client`'s methods)
303+
Requests made with ``ned_client`` (or objects returned from ``ned_client``'s methods)
299304
will be performed on behalf of the newly created app user.
300305

301306
Other Auth Options
302307
------------------
303308

304309
For advanced uses of the SDK, two additional auth classes are provided:
305310

306-
- `CooperativelyManagedOAuth2`: Allows multiple auth instances to share tokens.
307-
- `RemoteOAuth2`: Allows use of the SDK on clients without access to your application's client secret. Instead, you
308-
provide a `retrieve_access_token` callback. That callback should perform the token refresh, perhaps on your server
311+
- ``CooperativelyManagedOAuth2``: Allows multiple auth instances to share tokens.
312+
- ``RemoteOAuth2``: Allows use of the SDK on clients without access to your application's client secret. Instead, you
313+
provide a ``retrieve_access_token`` callback. That callback should perform the token refresh, perhaps on your server
309314
that does have access to the client secret.
310-
- `RedisManagedOAuth2`: Stores access and refresh tokens in Redis. This allows multiple processes (possibly spanning
315+
- ``RedisManagedOAuth2``: Stores access and refresh tokens in Redis. This allows multiple processes (possibly spanning
311316
multiple machines) to share access tokens while synchronizing token refresh. This could be useful for a multiprocess
312317
web server, for example.
313318

314319
Other Network Options
315320
---------------------
316321

317-
For more insight into the network calls the SDK is making, you can use the `LoggingNetwork` class. This class logs
322+
For more insight into the network calls the SDK is making, you can use the ``LoggingNetwork`` class. This class logs
318323
information about network requests and responses made to the Box API.
319324

320325
.. code-block:: python

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)