Skip to content

Commit 76652d9

Browse files
kelseymorris95jmoldow
authored andcommitted
Add Event class (#139)
Added an `Event` class, edited `Events.get_event()` to return a list of them. This creates a new branch in the object class hierarchy, with a distinction between REST objects (instances of `BaseObject` and its subclasses) and non-REST (objects that are not addressable / queryable in the API) objects (instances of `APIJSONObject` and its subclasses). `BaseObject` and `APIJSONObject` inherit from a common baseclass, `BaseAPIJSONObject`, which is now the base for translation. To maintain backwards-compatibility with methods that used to return `dict`, `APIJSONObject` implements the `Mapping` interface. Fixes #15.
1 parent a1dba2d commit 76652d9

File tree

15 files changed

+216
-67
lines changed

15 files changed

+216
-67
lines changed

boxsdk/object/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@
55
from six.moves import map # pylint:disable=redefined-builtin
66

77

8-
__all__ = list(map(str, ['collaboration', 'events', 'file', 'folder', 'group', 'group_membership', 'search', 'user']))
8+
__all__ = list(map(str, ['collaboration', 'events', 'event', 'file', 'folder', 'group', 'group_membership', 'search', 'user']))

boxsdk/object/api_json_object.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# coding: utf-8
2+
3+
from __future__ import unicode_literals, absolute_import
4+
from collections import Mapping
5+
from abc import ABCMeta
6+
import six
7+
8+
from .base_api_json_object import BaseAPIJSONObject, BaseAPIJSONObjectMeta
9+
10+
11+
class APIJSONObjectMeta(BaseAPIJSONObjectMeta, ABCMeta):
12+
"""
13+
Avoid conflicting metaclass definitions for APIJSONObject.
14+
http://code.activestate.com/recipes/204197-solving-the-metaclass-conflict/
15+
"""
16+
pass
17+
18+
19+
class APIJSONObject(six.with_metaclass(APIJSONObjectMeta, BaseAPIJSONObject, Mapping)):
20+
"""Class representing objects that are not part of the REST API."""
21+
22+
def __len__(self):
23+
return len(self._response_object)
24+
25+
def __iter__(self):
26+
return iter(self._response_object)
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# coding: utf-8
2+
3+
from __future__ import unicode_literals, absolute_import
4+
import six
5+
6+
from ..util.translator import Translator
7+
8+
9+
class BaseAPIJSONObjectMeta(type):
10+
"""
11+
Metaclass for Box API objects. Registers classes so that API responses can be translated to the correct type.
12+
Relies on the _item_type field defined on the classes to match the type property of the response json.
13+
But the type-class mapping will only be registered if the module of the class is imported.
14+
So it's also important to add the module name to __all__ in object/__init__.py.
15+
"""
16+
def __init__(cls, name, bases, attrs):
17+
super(BaseAPIJSONObjectMeta, cls).__init__(name, bases, attrs)
18+
item_type = attrs.get('_item_type', None)
19+
if item_type is not None:
20+
Translator().register(item_type, cls)
21+
22+
23+
@six.add_metaclass(BaseAPIJSONObjectMeta)
24+
class BaseAPIJSONObject(object):
25+
"""Base class containing basic logic shared between true REST objects and other objects (such as an Event)"""
26+
27+
_item_type = None
28+
29+
def __init__(self, response_object=None, **kwargs):
30+
"""
31+
:param response_object:
32+
A JSON object representing the object returned from a Box API request.
33+
:type response_object:
34+
`dict`
35+
"""
36+
super(BaseAPIJSONObject, self).__init__(**kwargs)
37+
self._response_object = response_object or {}
38+
self.__dict__.update(self._response_object)
39+
40+
def __getitem__(self, item):
41+
"""
42+
Try to get the attribute from the API response object.
43+
44+
:param item:
45+
The attribute to retrieve from the API response object.
46+
:type item:
47+
`unicode`
48+
"""
49+
return self._response_object[item]
50+
51+
def __repr__(self):
52+
"""Base class override. Return a human-readable representation using the Box ID or name of the object."""
53+
extra_description = ' - {0}'.format(self._description) if self._description else ''
54+
description = '<Box {0}{1}>'.format(self.__class__.__name__, extra_description)
55+
if six.PY2:
56+
return description.encode('utf-8')
57+
else:
58+
return description
59+
60+
@property
61+
def _description(self):
62+
"""Return a description of the object if one exists."""
63+
return ""

boxsdk/object/base_endpoint.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,23 @@
11
# coding: utf-8
22

3-
from __future__ import unicode_literals
3+
from __future__ import unicode_literals, absolute_import
44

55

66
class BaseEndpoint(object):
77
"""A Box API endpoint."""
88

9-
def __init__(self, session):
9+
def __init__(self, session, **kwargs):
1010
"""
11-
1211
:param session:
1312
The Box session used to make requests.
1413
:type session:
1514
:class:`BoxSession`
15+
:param kwargs:
16+
Keyword arguments for base class constructors.
17+
:type kwargs:
18+
`dict`
1619
"""
20+
super(BaseEndpoint, self).__init__(**kwargs)
1721
self._session = session
1822

1923
def get_url(self, endpoint, *args):

boxsdk/object/base_object.py

Lines changed: 11 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,15 @@
11
# coding: utf-8
22

3-
from __future__ import unicode_literals
4-
from abc import ABCMeta
3+
from __future__ import unicode_literals, absolute_import
54
import json
65

7-
import six
6+
from .base_endpoint import BaseEndpoint
7+
from .base_api_json_object import BaseAPIJSONObject
8+
from ..util.translator import Translator
89

9-
from boxsdk.object.base_endpoint import BaseEndpoint
10-
from boxsdk.util.translator import Translator
1110

12-
13-
class ObjectMeta(ABCMeta):
14-
"""
15-
Metaclass for Box API objects. Registers classes so that API responses can be translated to the correct type.
16-
Relies on the _item_type field defined on the classes to match the type property of the response json.
17-
But the type-class mapping will only be registered if the module of the class is imported.
18-
So it's also important to add the module name to __all__ in object/__init__.py.
19-
"""
20-
def __init__(cls, name, bases, attrs):
21-
super(ObjectMeta, cls).__init__(name, bases, attrs)
22-
item_type = attrs.get('_item_type', None)
23-
if item_type is not None:
24-
Translator().register(item_type, cls)
25-
26-
27-
@six.add_metaclass(ObjectMeta)
28-
class BaseObject(BaseEndpoint):
29-
"""
30-
A Box API endpoint for interacting with a Box object.
31-
"""
32-
_item_type = None
11+
class BaseObject(BaseEndpoint, BaseAPIJSONObject):
12+
"""A Box API endpoint for interacting with a Box object."""
3313

3414
def __init__(self, session, object_id, response_object=None):
3515
"""
@@ -42,29 +22,16 @@ def __init__(self, session, object_id, response_object=None):
4222
:type object_id:
4323
`unicode`
4424
:param response_object:
45-
The Box API response representing the object.
25+
A JSON object representing the object returned from a Box API request.
4626
:type response_object:
47-
:class:`BoxResponse`
27+
`dict`
4828
"""
49-
super(BaseObject, self).__init__(session)
29+
super(BaseObject, self).__init__(session=session, response_object=response_object)
5030
self._object_id = object_id
51-
self._response_object = response_object or {}
52-
self.__dict__.update(self._response_object)
53-
54-
def __getitem__(self, item):
55-
"""Base class override. Try to get the attribute from the API response object."""
56-
return self._response_object[item]
57-
58-
def __repr__(self):
59-
"""Base class override. Return a human-readable representation using the Box ID or name of the object."""
60-
description = '<Box {0} - {1}>'.format(self.__class__.__name__, self._description)
61-
if six.PY2:
62-
return description.encode('utf-8')
63-
else:
64-
return description
6531

6632
@property
6733
def _description(self):
34+
"""Base class override. Return a description for the object."""
6835
if 'name' in self._response_object:
6936
return '{0} ({1})'.format(self._object_id, self.name) # pylint:disable=no-member
7037
else:
@@ -185,7 +152,7 @@ def delete(self, params=None, headers=None):
185152
return box_response.ok
186153

187154
def __eq__(self, other):
188-
"""Base class override. Equality is determined by object id."""
155+
"""Equality as determined by object id"""
189156
return self._object_id == other.object_id
190157

191158
def _paging_wrapper(self, url, starting_index, limit, factory=None):

boxsdk/object/event.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# coding: utf-8
2+
3+
from __future__ import unicode_literals, absolute_import
4+
5+
from .api_json_object import APIJSONObject
6+
7+
8+
class Event(APIJSONObject):
9+
"""Represents a single Box event."""
10+
11+
_item_type = 'event'

boxsdk/object/events.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
# coding: utf-8
22

3-
from __future__ import unicode_literals
4-
3+
from __future__ import unicode_literals, absolute_import
54
from requests.exceptions import Timeout
65
from six import with_metaclass
76

8-
from boxsdk.object.base_endpoint import BaseEndpoint
9-
from boxsdk.util.enum import ExtendableEnumMeta
10-
from boxsdk.util.lru_cache import LRUCache
11-
from boxsdk.util.text_enum import TextEnum
7+
from .base_endpoint import BaseEndpoint
8+
from ..util.enum import ExtendableEnumMeta
9+
from ..util.lru_cache import LRUCache
10+
from ..util.text_enum import TextEnum
11+
from ..util.translator import Translator
1212

1313

1414
# pylint:disable=too-many-ancestors
@@ -79,8 +79,7 @@ def get_events(self, limit=100, stream_position=0, stream_type=UserEventsStreamT
7979
:type stream_type:
8080
:enum:`EventsStreamType`
8181
:returns:
82-
JSON response from the Box /events endpoint. Contains the next stream position to use for the next call,
83-
along with some number of events.
82+
Dictionary containing the next stream position along with a list of some number of events.
8483
:rtype:
8584
`dict`
8685
"""
@@ -91,7 +90,10 @@ def get_events(self, limit=100, stream_position=0, stream_type=UserEventsStreamT
9190
'stream_type': stream_type,
9291
}
9392
box_response = self._session.get(url, params=params)
94-
return box_response.json()
93+
response = box_response.json().copy()
94+
if 'entries' in response:
95+
response['entries'] = [Translator().translate(item['type'])(item) for item in response['entries']]
96+
return response
9597

9698
def get_latest_stream_position(self, stream_type=UserEventsStreamType.ALL):
9799
"""

boxsdk/object/folder.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import json
55
import os
66
from six import text_type
7+
78
from boxsdk.config import API
89
from boxsdk.object.collaboration import Collaboration
910
from boxsdk.object.file import File

boxsdk/object/item.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
# coding: utf-8
22

3-
from __future__ import unicode_literals
4-
3+
from __future__ import unicode_literals, absolute_import
54
import json
65

76
from .base_object import BaseObject
@@ -111,6 +110,10 @@ def rename(self, name):
111110
def get(self, fields=None, etag=None):
112111
"""Base class override.
113112
113+
:param fields:
114+
List of fields to request.
115+
:type fields:
116+
`Iterable` of `unicode`
114117
:param etag:
115118
If specified, instruct the Box API to get the info only if the current version's etag doesn't match.
116119
:type etag:

boxsdk/object/metadata.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# coding: utf-8
22

3-
from __future__ import unicode_literals
3+
from __future__ import unicode_literals, absolute_import
44
import json
55
from boxsdk.object.base_endpoint import BaseEndpoint
66

0 commit comments

Comments
 (0)