Skip to content

Commit

Permalink
Webhooks v2 (#324)
Browse files Browse the repository at this point in the history
  • Loading branch information
carycheng committed Oct 3, 2018
1 parent ccd5c3d commit 567ea52
Show file tree
Hide file tree
Showing 7 changed files with 362 additions and 0 deletions.
82 changes: 82 additions & 0 deletions boxsdk/client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,88 @@ def get_groups(self, name=None, limit=None, offset=None, fields=None):
return_full_pages=False,
)

def webhook(self, webhook_id):
"""
Initialize a :class:`Webhook` object, whose box id is webhook_id.
:param webhook_id:
The box ID of the :class: `Webhook` object.
:type webhook_id:
`unicode`
:return:
A :class:`Webhook` object with the given entry ID.
:rtype:
:class:`Webhook`
"""
return self.translator.get('webhook')(session=self._session, object_id=webhook_id)

def create_webhook(self, target, triggers, address):
"""
Create a webhook on the given file.
:param target:
Either a :class:`File` or :class:`Folder` to assign a webhook to.
:type target:
:class:`File` or :class`Folder`
:param triggers:
Event types that trigger notifications for the target.
:type triggers:
`list` of `unicode`
:param address:
The url to send the notification to.
:type address:
`unicode`
:return:
A :class:`Webhook` object with the given entry ID.
:rtype:
:class:`Webhook`
"""
url = self.get_url('webhooks')
webhook_attributes = {
'target': {
'type': target.object_type,
'id': target.object_id,
},
'triggers': triggers,
'address': address,
}
box_response = self._session.post(url, data=json.dumps(webhook_attributes))
response = box_response.json()
return self.translator.translate(response['type'])(
session=self._session,
object_id=response['id'],
response_object=response,
)

def get_webhooks(self, limit=None, marker=None, fields=None):
"""
Get all webhooks in an enterprise.
:param limit:
The maximum number of entries to return.
:type limit:
`int` or None
:param marker:
The position marker at which to begin the response.
:type marker:
`unicode` or None
:param fields:
List of fields to request on the file or folder which the `RecentItem` references.
:type fields:
`Iterable` of `unicode`
:returns:
An iterator of the entries in the webhook
:rtype:
:class:`BoxObjectCollection`
"""
return MarkerBasedObjectCollection(
session=self._session,
url=self.get_url('webhooks'),
limit=limit,
marker=marker,
fields=fields,
)

@api_call
def create_group(
self,
Expand Down
1 change: 1 addition & 0 deletions boxsdk/object/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
'task',
'task_assignment',
'user',
'webhook',
'watermark',
'web_link',
]))
10 changes: 10 additions & 0 deletions boxsdk/object/webhook.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# coding: utf-8
from __future__ import unicode_literals

from .base_object import BaseObject


class Webhook(BaseObject):
"""Represents a Box Webhook."""

_item_type = 'webhook'
70 changes: 70 additions & 0 deletions docs/webhook.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
Webhooks
========

Webhooks enable you to attach event triggers to Box files and folders. Event triggers monitor events on Box objects and notify your application when they occur. A webhook notifies your application by sending HTTP requests to a URL of your choosing.

<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->


- [Get Information about Webhook](#get-information-about-webhook)
- [List all Webhooks](#list-all-webhooks)
- [Create Webhook](#create-webhook)
- [Delete Webhook](#delete-webhook)
- [Update Webhook](#update-webhook)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->

Get Information about Webhook
-----------------------------

To retrieve information about a webhook, use `webhook.get(fields=None)`

```python
webhook_info = client.webhook('1234').get()
```

List all Webhooks
-----------------

To retrieve an iterable of all webhooks in the enterprise, use `client.get_webhooks(limit=None, marker=None, fields=None)`

```python
webhooks = client.get_webhooks()
for webhook in webhooks:
# Do something
```

Create Webhook
--------------

To create a webhook on a specified target, use `client.create_webhook(target, triggers, address)`

You can create a webhook on either a `file` or a `folder`. For a full list of triggers, see [here](https://developer.box.com/v2.0/reference#webhooks-v2)

```python
folder = client.folder('1111')
created_webhook = client.create_webhook(folder, ['FILE.UPLOADED', 'FILE.PREVIEWED'], 'https://example.com')
```

Delete Webhook
--------------

To delete a webhook, use `webhook.delete()`

```python
client.webhook('1234').delete()
```

Update Webhook
--------------

To update a webhook, use `webhook.update_info(data)`

```python
update_object = {
triggers: ['FILE.COPIED'],
address: 'https://newexample.com',
}
updated_webhook = client.webhook('1234').update_info(update_object)
```
123 changes: 123 additions & 0 deletions test/unit/client/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from boxsdk.object.retention_policy import RetentionPolicy
from boxsdk.object.file_version_retention import FileVersionRetention
from boxsdk.object.legal_hold_policy import LegalHoldPolicy
from boxsdk.object.webhook import Webhook
from boxsdk.pagination.marker_based_object_collection import MarkerBasedObjectCollection


Expand Down Expand Up @@ -65,6 +66,34 @@ def folder_id():
return '1022'


@pytest.fixture()
def test_folder(mock_box_session, mock_object_id):
return Folder(mock_box_session, mock_object_id)


@pytest.fixture()
def test_webhook(mock_box_session, mock_object_id):
return Webhook(mock_box_session, mock_object_id)


@pytest.fixture(scope='function')
def mock_file_response(mock_object_id, make_mock_box_request):
# pylint:disable=redefined-outer-name
mock_box_response, _ = make_mock_box_request(
response={'type': 'file', 'id': mock_object_id},
)
return mock_box_response


@pytest.fixture(scope='function')
def mock_folder_response(mock_object_id, make_mock_box_request):
# pylint:disable=redefined-outer-name
mock_box_response, _ = make_mock_box_request(
response={'type': 'folder', 'id': mock_object_id},
)
return mock_box_response


@pytest.fixture(scope='module')
def marker_id():
return 'marker_1'
Expand Down Expand Up @@ -207,6 +236,35 @@ def create_user_response():
return mock_network_response


@pytest.fixture(params=('file', 'folder'))
def test_item_and_response(mock_file, test_folder, mock_file_response, mock_folder_response, request):
if request.param == 'file':
return mock_file, mock_file_response
return test_folder, mock_folder_response


@pytest.fixture()
def create_webhook_response(test_item_and_response, test_webhook):
# pylint:disable=redefined-outer-name
test_item, _ = test_item_and_response
mock_network_response = Mock(DefaultNetworkResponse)
mock_network_response.json.return_value = {
'type': test_webhook.object_type,
'id': test_webhook.object_id,
'target': {
'type': test_item.object_type,
'id': test_item.object_id,
},
'created_at': '2016-05-09T17:41:27-07:00',
'address': 'https://test.com',
'triggers': [
'FILE.UPLOADED',
'FOLDER.CREATED',
],
}
return mock_network_response


@pytest.fixture(scope='module', params=('file', 'folder'))
def shared_item_response(request):
# pylint:disable=redefined-outer-name
Expand Down Expand Up @@ -526,6 +584,71 @@ def test_create_enterprise_user_returns_the_correct_user_object(mock_client, moc
assert new_user.name == test_user_name


def test_webhook_initializer(mock_client):
expected_id = '1234'
webhook = mock_client.webhook(expected_id)
assert isinstance(webhook, Webhook)
assert webhook.object_id == expected_id


def test_create_webhook_returns_the_correct_policy_object(
test_item_and_response,
test_webhook,
mock_client,
mock_box_session,
create_webhook_response,
):
# pylint:disable=redefined-outer-name
test_item, _ = test_item_and_response
expected_url = "{0}/webhooks".format(API.BASE_API_URL)
expected_body = {
'target': {
'type': test_item.object_type,
'id': test_item.object_id,
},
'triggers': ['FILE.UPLOADED', 'FOLDER.CREATED'],
'address': 'https://test.com',
}
value = json.dumps(expected_body)
mock_box_session.post.return_value = create_webhook_response
new_webhook = mock_client.create_webhook(test_item, ['FILE.UPLOADED', 'FOLDER.CREATED'], 'https://test.com')
mock_box_session.post.assert_called_once_with(
expected_url,
data=value,
)
assert isinstance(new_webhook, Webhook)
assert new_webhook.id == test_webhook.object_id
assert new_webhook.type == test_webhook.object_type
assert new_webhook.target['type'] == test_item.object_type
assert new_webhook.target['id'] == test_item.object_id
assert new_webhook.triggers == ['FILE.UPLOADED', 'FOLDER.CREATED']
assert new_webhook.address == 'https://test.com'


def test_get_webhooks(mock_client, mock_box_session):
expected_url = "{0}/webhooks".format(API.BASE_API_URL)
webhook_body = {
'type': 'webhook',
'id': '12345',
'target': {
'type': 'folder',
'id': '11111',
},
}
mock_box_session.get.return_value.json.return_value = {
'limit': 100,
'entries': [webhook_body],
}
webhooks = mock_client.get_webhooks()
webhook = webhooks.next()
mock_box_session.get.assert_called_once_with(expected_url, params={})
assert isinstance(webhook, Webhook)
assert webhook.object_id == webhook_body['id']
assert webhook.object_type == webhook_body['type']
assert webhook.target['id'] == webhook_body['target']['id']
assert webhook.target['type'] == webhook_body['target']['type']


def test_create_retention_policy(mock_client, mock_box_session, mock_user_list):
policy_name = 'Test Retention Policy'
policy_type = 'finite'
Expand Down
6 changes: 6 additions & 0 deletions test/unit/object/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from boxsdk.object.retention_policy import RetentionPolicy
from boxsdk.object.retention_policy_assignment import RetentionPolicyAssignment
from boxsdk.object.search import Search
from boxsdk.object.webhook import Webhook
from boxsdk.object.task import Task
from boxsdk.object.task_assignment import TaskAssignment
from boxsdk.object.web_link import WebLink
Expand Down Expand Up @@ -141,6 +142,11 @@ def test_search(mock_box_session):
return Search(mock_box_session)


@pytest.fixture()
def test_webhook(mock_box_session, mock_object_id):
return Webhook(mock_box_session, mock_object_id)


@pytest.fixture()
def test_task(mock_box_session, mock_object_id):
return Task(mock_box_session, mock_object_id)
Expand Down

0 comments on commit 567ea52

Please sign in to comment.