Skip to content

Commit a6441cb

Browse files
committed
Add stream_type param for events long polling
Add a `stream_type` parameter to the `generate_events_with_long_polling()` method and its helper methods. This allows users to listen to other event stream types. The default is still to listen to the 'all' stream. In order to differentiate between user and enterprise event streams, the following enum hierarchy was created, with the help of a new `ExtendableEnumMeta` metaclass: +-- `EventsStreamType` | +-- `UserEventsStreamType` | +-- `EnterpriseEventsStreamType` Unrelatedly, fix up the RST for the README. Add a table of contents, and fix up backticks (double-backticks are needed to create code spans, single-backticks aren't enough).
1 parent 5f151f8 commit a6441cb

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)