Skip to content

Commit 2228ad0

Browse files
authored
Add BoxSign support (#617)
1 parent 4daff33 commit 2228ad0

File tree

9 files changed

+634
-1
lines changed

9 files changed

+634
-1
lines changed

HISTORY.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Next release
99
**New Features and Enhancements:**
1010

1111
- Sensitive language replacement (`#609 <https://github.com/box/box-python-sdk/pull/609>`_)
12+
- Add BoxSign support (`#617 <https://github.com/box/box-python-sdk/pull/617>`_)
1213

1314
2.12.1 (2021-06-16)
1415
++++++++

boxsdk/client/client.py

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1797,3 +1797,144 @@ def folder_lock(self, folder_lock_id):
17971797
:class:`FolderLock`
17981798
"""
17991799
return self.translator.get('folder_lock')(session=self._session, object_id=folder_lock_id)
1800+
1801+
def sign_request(self, sign_request_id):
1802+
"""
1803+
Initialize a :class:`SignRequest` object, whose box id is sign_request_id.
1804+
1805+
:param sign_request_id:
1806+
The box id of the :class:`SignRequest` object.
1807+
:type sign_request_id:
1808+
`unicode`
1809+
:return:
1810+
A :class:`SignRequest` object with the given file id.
1811+
:rtype:
1812+
:class:`SignRequest`
1813+
"""
1814+
return self.translator.get('sign_request')(session=self._session, object_id=sign_request_id)
1815+
1816+
@api_call
1817+
def create_sign_request(self, files, signers, parent_folder_id, prefill_tags=None, are_reminders_enabled=None, are_text_signatures_enabled=None,
1818+
days_valid=None, email_message=None, email_subject=None, external_id=None, is_document_preparation_needed=None):
1819+
"""
1820+
Used to create a new sign request.
1821+
1822+
:param files:
1823+
List of files to create a signing document from.
1824+
:type files:
1825+
`Iterable`
1826+
:param signers:
1827+
List of signers for the sign request. 35 is the max number of signers permitted.
1828+
:type signers:
1829+
`Iterable`
1830+
:param parent_folder_id:
1831+
The id of the destination folder to place sign request specific data in.
1832+
:type parent_folder_id:
1833+
`unicode`
1834+
:param prefill_tags:
1835+
When a document contains sign related tags in the content,
1836+
you can prefill them using this prefill_tags by referencing the 'id' of the tag as the external_id field of the prefill tag.
1837+
:type prefill_tags:
1838+
`Iterable` or None
1839+
:param are_reminders_enabled:
1840+
Reminds signers to sign a document on day 3, 8, 13 and 18. Reminders are only sent to outstanding signers.
1841+
:type are_reminders_enabled:
1842+
`bool` or None
1843+
:param are_text_signatures_enabled:
1844+
Disables the usage of signatures generated by typing (text).
1845+
:type are_text_signatures_enabled:
1846+
`bool` or None
1847+
:param days_valid:
1848+
Number of days after which this request will automatically expire if not completed.
1849+
:type days_valid:
1850+
`unicode` or None
1851+
:param email_message:
1852+
Message to include in sign request email. The field is cleaned through sanitization of specific characters.
1853+
However, some html tags are allowed. Links included in the message are also converted to hyperlinks in the email.
1854+
The message may contain the following html tags including a, abbr, acronym, b, blockquote, code, em, i, ul, li, ol, and strong.
1855+
Be aware that when the text to html ratio is too high, the email may end up in spam filters. Custom styles on these tags are not allowed.
1856+
If this field is not passed, a default message will be used.
1857+
:type email_message:
1858+
`Iterable` or None
1859+
:param email_subject:
1860+
Subject of sign request email. This is cleaned by sign request. If this field is not passed, a default subject will be used.
1861+
:type email_subject:
1862+
`unicode` or None
1863+
:param external_id:
1864+
This can be used to reference an ID in an external system that the sign request is related to.
1865+
:type external_id:
1866+
`unicode` or None
1867+
:param is_document_preparation_needed:
1868+
Indicates if the sender should receive a prepare_url in the response to complete document preparation via UI.
1869+
:type is_document_preparation_needed:
1870+
`bool` or None
1871+
:returns:
1872+
A dictionary representing a created SignRequest
1873+
:rtype:
1874+
:class:`dict`
1875+
"""
1876+
url = self._session.get_url('sign_requests')
1877+
1878+
body = {
1879+
'source_files': files,
1880+
'signers': signers,
1881+
'parent_folder': {
1882+
'id': parent_folder_id,
1883+
'type': 'folder'
1884+
}
1885+
}
1886+
1887+
if prefill_tags:
1888+
body['prefill_tags'] = prefill_tags
1889+
if are_reminders_enabled:
1890+
body['are_reminders_enabled'] = are_reminders_enabled
1891+
if are_text_signatures_enabled:
1892+
body['are_text_signatures_enabled'] = are_text_signatures_enabled
1893+
if days_valid:
1894+
body['days_valid'] = days_valid
1895+
if email_message:
1896+
body['email_message'] = email_message
1897+
if email_subject:
1898+
body['email_subject'] = email_subject
1899+
if external_id:
1900+
body['external_id'] = external_id
1901+
if is_document_preparation_needed:
1902+
body['is_document_preparation_needed'] = is_document_preparation_needed
1903+
1904+
box_response = self._session.post(url, data=json.dumps(body))
1905+
response = box_response.json()
1906+
return self.translator.translate(
1907+
session=self._session,
1908+
response_object=response,
1909+
)
1910+
1911+
@api_call
1912+
def get_sign_requests(self, limit=None, marker=None, fields=None):
1913+
"""
1914+
Returns all the sign requests.
1915+
1916+
:param limit:
1917+
The maximum number of entries to return per page. If not specified, then will use the server-side default.
1918+
:type limit:
1919+
`int` or None
1920+
:param marker:
1921+
The paging marker to start paging from.
1922+
:type marker:
1923+
`unicode` or None
1924+
:param fields:
1925+
List of fields to request.
1926+
:type fields:
1927+
`Iterable` of `unicode`
1928+
:returns:
1929+
An iterator of the entries in the device pins.
1930+
:rtype:
1931+
:class:`BoxObjectCollection`
1932+
"""
1933+
return MarkerBasedObjectCollection(
1934+
session=self._session,
1935+
url=self.get_url('sign_requests'),
1936+
limit=limit,
1937+
marker=marker,
1938+
fields=fields,
1939+
return_full_pages=False,
1940+
)

boxsdk/object/base_api_json_object.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ def __init__(cls, name, bases, attrs):
5555
item_type = attrs.get('_item_type', None)
5656
if item_type is not None:
5757
Translator._default_translator.register(item_type, cls) # pylint:disable=protected-access
58+
# Some types have - in them instead of _ in the API.
59+
if "-" in item_type:
60+
Translator._default_translator.register(item_type.replace("-", "_"), cls) # pylint:disable=protected-access
5861

5962

6063
@six.add_metaclass(BaseAPIJSONObjectMeta)

boxsdk/object/sign_request.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# coding: utf-8
2+
from __future__ import unicode_literals, absolute_import
3+
4+
from .base_object import BaseObject
5+
from ..util.api_call_decorator import api_call
6+
7+
8+
class SignRequest(BaseObject):
9+
"""
10+
Represents a Sign Request used by Box Sign
11+
Sign Requests are used to request e-signatures on documents from signers.
12+
A Sign Request can refer to one or more Box Files and can be sent to one or more Box Sign Request Signers.
13+
"""
14+
_item_type = 'sign-request'
15+
16+
def get_url(self, *args):
17+
"""
18+
Returns the url for this sign request.
19+
"""
20+
return self._session.get_url('sign_requests', self._object_id, *args)
21+
22+
@api_call
23+
def cancel(self):
24+
"""
25+
Cancels a sign request if it has not yet been signed or declined.
26+
Any outstanding signers will no longer be able to sign the document.
27+
28+
:returns:
29+
The cancelled SignRequest object.
30+
:rtype:
31+
:class:`SignRequest`
32+
"""
33+
url = self.get_url('cancel')
34+
response = self._session.post(url).json()
35+
return self.translator.translate(
36+
session=self._session,
37+
response_object=response,
38+
)
39+
40+
@api_call
41+
def resend(self):
42+
"""
43+
Attempts to resend a Sign Request to all signers that have not signed yet.
44+
There is a 10 minute cooling-off period between each resend request.
45+
46+
:returns:
47+
Whether the operation succeeded.
48+
:rtype:
49+
`boolean`
50+
"""
51+
url = self.get_url('resend')
52+
response = self._session.post(url, skip_retry_codes={202}, expect_json_response=False)
53+
return response.ok

boxsdk/session/session.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -391,12 +391,13 @@ def _prepare_and_send_request(
391391
expect_json_response=expect_json_response,
392392
)
393393

394+
skip_retry_codes = kwargs.pop('skip_retry_codes', set())
394395
network_response = self._send_request(request, **kwargs)
395396

396397
while True:
397398
retry = self._get_retry_request_callable(network_response, attempt_number, request, **kwargs)
398399

399-
if retry is None or attempt_number >= API.MAX_RETRY_ATTEMPTS:
400+
if retry is None or attempt_number >= API.MAX_RETRY_ATTEMPTS or network_response.status_code in skip_retry_codes:
400401
break
401402

402403
attempt_number += 1

docs/usage/sign_requests.md

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
Sign Requests
2+
==================
3+
4+
Sign Requests are used to request e-signatures on documents from signers.
5+
A Sign Request can refer to one or more Box Files and can be sent to one or more Box Sign Request Signers.
6+
7+
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
8+
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
9+
10+
11+
- [Create Sign Request](#create-sign-request)
12+
- [Get all Sign Requests](#get-all-sign-requests)
13+
- [Get Sign Request by ID](#get-sign-request-by-id)
14+
- [Cancel Sign Request](#cancel-sign-request)
15+
- [Resend Sign Request](#resend-sign-request)
16+
17+
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
18+
19+
Create Sign Request
20+
------------------------
21+
22+
The [`client.create_sign_request(files, signers, parent_folder_id, prefill_tags=None, are_reminders_enabled=None, are_text_signatures_enabled=None, days_valid=None, email_message=None, email_subject=None, external_id=None, is_document_preparation_needed=None)`][create-sign-request]
23+
method will create a Sign Request. You need to provide at least one file (from which the signing document will be created) and at least one signer to receive the Sign Request.
24+
25+
<!-- sample post_sign_requests -->
26+
```python
27+
source_file = {
28+
'id': '12345',
29+
'type': 'file'
30+
}
31+
files = [source_file]
32+
33+
signer = {
34+
'name': 'John Doe',
35+
'email': '[email protected]'
36+
}
37+
signers = [signer]
38+
39+
parent_folder_id = '123456789'
40+
new_sign_request = client.create_sign_request(files, signers, parent_folder_id)
41+
print('(Sign Request ID: {0})'.format(new_sign_request.id))
42+
```
43+
44+
If you set ```isDocumentPreparationNeeded``` flag to true, you need to visit ```prepareUrl``` before the Sign Request will be sent.
45+
For more information on ```isDocumentPreparationNeeded``` and the other parameters available, please refer to the [developer documentation](https://developer.box.com/guides/sign-request/).
46+
47+
[create-sign-request]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.client.html#boxsdk.client.client.Client.create_sign_request
48+
49+
Get All Sign Requests
50+
------------------------
51+
52+
Calling the [`client.get_sign_requests()`][get-all-sign-requests]
53+
will return an iterable that will page through all the Sign Requests. This method offers `limit` and `fields` parameters. The `limit` parameter specifies the maximum number of items to be returned in a single response. The `fields` parameter is used to specify what additional properties should be returned on the return object. For more information on what `fields` are available, please refer to the [developer documentation](https://developer.box.com/guides/sign-request/).
54+
55+
<!-- sample get_sign_requests -->
56+
```python
57+
sign_requests = client.get_sign_requests()
58+
for sign_request in sign_requests:
59+
print('(Sign Request ID: {0})'.format(sign_request.id))
60+
```
61+
62+
[get-all-sign-requests]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.client.html#boxsdk.client.client.Client.get_sign_requests
63+
64+
Get Sign Request by ID
65+
------------------------
66+
67+
Calling [`client.sign_request(sign_request_id)`][get-sign-request-by-id] will return an object
68+
containing information about the Sign Request.
69+
The `fields` parameter is used to specify what additional properties should be returned in the return object.
70+
71+
<!-- sample get_sign_requests_id -->
72+
```python
73+
sign_request = client.sign_request(sign_request_id='12345').get()
74+
print('Sign Request ID is {0}'.format(sign_request.id))
75+
```
76+
77+
[get-sign-request-by-id]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.client.html#boxsdk.client.client.Client.sign_request
78+
79+
Cancel Sign Request
80+
------------------------
81+
82+
Calling [`sign_requests.cancel()`][cancel-sign-request] will cancel a created Sign Request.
83+
84+
<!-- sample post_sign_requests_id_cancel -->
85+
```python
86+
sign_request = client.sign_request(sign_request_id='12345')
87+
cancelled_sign_request = sign_request.cancel()
88+
print('Cancelled Sign Request status is {0}'.format(cancelled_sign_request.status))
89+
```
90+
91+
[cancel-sign-request]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.retention_policy.SignRequest.cancel
92+
93+
Resend Sign Request
94+
------------------------
95+
96+
Calling [`sign_requests.resend()`][resend-sign-request] will resend a Sign Request to all signers that have not signed it yet.
97+
There is an 10-minute cooling-off period between re-sending reminder emails.
98+
99+
<!-- sample post_sign_requests_id_resend -->
100+
```python
101+
sign_request = client.sign_request(sign_request_id='12345')
102+
sign_request.resend()
103+
```
104+
105+
[resend-sign-request]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.retention_policy.SignRequest.resend

0 commit comments

Comments
 (0)