diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4265e83..8667f74 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,7 +6,7 @@ We're excited you're considering contributing to our project! Your patches are e * Make sure you have a [GitHub account](https://github.com/signup/free) * Make sure you have a [HelloSign account](http://www.hellosign.com) -* Submit a new [issue ticket](https://github.com/HelloFax/hellosign-java-sdk/issues), assuming one does not already exist. +* Submit a new [issue ticket](https://github.com/hellosign/hellosign-python-sdk), assuming one does not already exist. * Clearly describe the issue including steps to reproduce when it is a bug. * Make sure to include the version you are using which has the issue (and confirm that the issue you are having has not been fixed in a later version) * Fork the repository on GitHub @@ -27,7 +27,7 @@ always necessary to create a new issue ticket. ## Submitting Changes * Push your changes to your fork of the repository. -* Submit a pull request to the repository in the HelloFax organization. +* Submit a pull request to the repository in the HelloSign organization. * Update your issue ticket to mark that you have submitted code and are ready for it to be reviewed (Status: Ready for Merge). * Include a link to the pull request in the ticket. * The HelloSign dev team looks at Pull Requests on a regular basis. diff --git a/README.md b/README.md index c624191..e6eb0b6 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ pip install hellosign-python-sdk Install from code: ````sh -git clone https://github.com/HelloFax/hellosign-python-sdk.git +git clone https://github.com/HelloSign/hellosign-python-sdk.git cd hellosign-python-sdk python setup.py install ```` @@ -213,7 +213,7 @@ draft = client.create_embedded_unclaimed_draft( claim_url = draft.claim_url ```` -More information about the asscociated front-end code can be found [here](https://www.hellosign.com/api/embeddedRequestingWalkthrough#ClientSideRequesting) +More information about the associated front-end code can be found [here](https://app.hellosign.com/api/embeddedRequestingWalkthrough#EmbeddedRequestingClientSide) Once the user edits the draft in the embedded iFrame and sends the signature request your app callback will receive a `signature_request_sent` event containing a `SignatureRequest` object. If we had used `is_for_embedded_signing=True`, we would want to get the signature ids out of the `SignatureRequest` from that event and fetch the signature urls at this point. In your event callback handler, you will need to do something like this: diff --git a/hellosign_sdk/hsclient.py b/hellosign_sdk/hsclient.py index 8006c69..67b85b3 100644 --- a/hellosign_sdk/hsclient.py +++ b/hellosign_sdk/hsclient.py @@ -1,5 +1,5 @@ from hellosign_sdk.utils import HSRequest, HSException, NoAuthMethod, HSAccessTokenAuth, HSFormat, api_resource, api_resource_list -from hellosign_sdk.resource import Account, SignatureRequest, Template, Team, Embedded, UnclaimedDraft +from hellosign_sdk.resource import Account, ApiApp, SignatureRequest, Template, Team, Embedded, UnclaimedDraft from requests.auth import HTTPBasicAuth import json @@ -36,7 +36,7 @@ class HSClient(object): ''' - version = '3.8.7' # SDK version + version = '3.8.8' # SDK version API_VERSION = 'v3' # API version API_URL = '' @@ -61,6 +61,7 @@ class HSClient(object): UNCLAIMED_DRAFT_CREATE_URL = '' UNCLAIMED_DRAFT_CREATE_EMBEDDED_URL = '' UNCLAIMED_DRAFT_CREATE_EMBEDDED_WITH_TEMPLATE_URL = '' + UNCLAIMED_DRAFT_EDIT_AND_RESEND_URL = '' TEMPLATE_GET_URL = '' TEMPLATE_GET_LIST_URL = '' @@ -77,24 +78,30 @@ class HSClient(object): TEAM_ADD_MEMBER_URL = '' TEAM_REMOVE_MEMBER_URL = '' + API_APP_INFO_URL = '' + API_APP_LIST_URL = '' + API_APP_CREATE_URL = '' + API_APP_UPDATE_URL = '' + API_APP_DELETE_URL = '' + OAUTH_TOKEN_URL = '' request = None response_callback = None - def __init__(self, email_address=None, password=None, api_key=None, access_token=None, access_token_type="Bearer", env='production'): + def __init__(self, email_address=None, password=None, api_key=None, access_token=None, access_token_type='Bearer', env='production'): '''Initialize the client object with authentication information to send requests Args: - email_address (str): E-mail of the account to make the requests + email_address (str): E-mail of the account to make the requests - password (str): Password of the account used with email address + password (str): Password of the account used with email address - api_key (str): API Key. You can find your API key in https://www.hellosign.com/home/myAccount/current_tab/integrations + api_key (str): API Key. You can find your API key in https://app.hellosign.com/home/myAccount/current_tab/integrations#api - access_token (str): OAuth access token to use + access_token (str): OAuth access token to use - access_token_type (str): Type of OAuth token (defaults to Bearer, which is the only value supported for now) + access_token_type (str): Type of OAuth token (defaults to Bearer, which is the only value supported for now) ''' @@ -141,7 +148,9 @@ def _init_endpoints(self): self.SIGNATURE_REQUEST_CREATE_URL = self.API_URL + '/signature_request/send' self.SIGNATURE_REQUEST_CREATE_WITH_TEMPLATE_URL = self.API_URL + '/signature_request/send_with_template' self.SIGNATURE_REQUEST_REMIND_URL = self.API_URL + '/signature_request/remind/' + self.SIGNATURE_REQUEST_UPDATE_URL = self.API_URL + '/signature_request/update/' self.SIGNATURE_REQUEST_CANCEL_URL = self.API_URL + '/signature_request/cancel/' + self.SIGNATURE_REQUEST_REMOVE_ACCESS_URL = self.API_URL + '/signature_request/remove/' self.SIGNATURE_REQUEST_CREATE_EMBEDDED_URL = self.API_URL + '/signature_request/create_embedded' self.SIGNATURE_REQUEST_CREATE_EMBEDDED_WITH_TEMPLATE_URL = self.API_URL + '/signature_request/create_embedded_with_template' @@ -151,6 +160,7 @@ def _init_endpoints(self): self.UNCLAIMED_DRAFT_CREATE_URL = self.API_URL + '/unclaimed_draft/create' self.UNCLAIMED_DRAFT_CREATE_EMBEDDED_URL = self.API_URL + '/unclaimed_draft/create_embedded' self.UNCLAIMED_DRAFT_CREATE_EMBEDDED_WITH_TEMPLATE_URL = self.API_URL + '/unclaimed_draft/create_embedded_with_template' + self.UNCLAIMED_DRAFT_EDIT_AND_RESEND_URL = self.API_URL + '/unclaimed_draft/edit_and_resend/' self.TEMPLATE_GET_URL = self.API_URL + '/template/' self.TEMPLATE_GET_LIST_URL = self.API_URL + '/template/list' @@ -159,6 +169,7 @@ def _init_endpoints(self): self.TEMPLATE_ADD_USER_URL = self.API_URL + '/template/add_user/' self.TEMPLATE_REMOVE_USER_URL = self.API_URL + '/template/remove_user/' self.TEMPLATE_CREATE_EMBEDDED_DRAFT_URL = self.API_URL + '/template/create_embedded_draft' + self.TEMPLATE_UPDATE_FILES_URL = self.API_URL + '/template/update_files/' self.TEAM_INFO_URL = self.API_URL + '/team' self.TEAM_UPDATE_URL = self.TEAM_INFO_URL @@ -167,6 +178,12 @@ def _init_endpoints(self): self.TEAM_ADD_MEMBER_URL = self.API_URL + '/team/add_member' self.TEAM_REMOVE_MEMBER_URL = self.API_URL + '/team/remove_member' + self.API_APP_INFO_URL = self.API_URL + '/api_app/' + self.API_APP_LIST_URL = self.API_URL + '/api_app/list' + self.API_APP_CREATE_URL = self.API_URL + '/api_app' + self.API_APP_UPDATE_URL = self.API_APP_INFO_URL + self.API_APP_DELETE_URL = self.API_APP_INFO_URL + # ---- ACCOUNT METHODS ----------------------------- @api_resource(Account) @@ -177,13 +194,13 @@ def create_account(self, email_address, password=None, client_id=None, client_se OAuth data that can be used to execute actions on behalf of the newly created account. Args: - email_address (str): Email address of the new account to create + email_address (str): Email address of the new account to create - password (str): [DEPRECATED] This parameter will be ignored + password (str): [DEPRECATED] This parameter will be ignored - client_id (str, optional): Client id of the app to use to create this account + client_id (str, optional): Client id of the app to use to create this account - client_secret (str, optional): Secret of the app to use to create this account + client_secret (str, optional): Secret of the app to use to create this account Returns: The new Account object @@ -245,12 +262,12 @@ def update_account_info(self): def verify_account(self, email_address): ''' Verify whether a HelloSign Account exists - Args: + Args: - email_address (str): Email address for the account to verify + email_address (str): Email address of the new account to create - Returns: - True or False + Returns: + True or False ''' request = self._get_request() resp = request.post(self.ACCOUNT_VERIFY_URL, { @@ -261,14 +278,12 @@ def verify_account(self, email_address): # ---- SIGNATURE REQUEST METHODS ------------------- @api_resource(SignatureRequest) - def get_signature_request(self, signature_request_id, ux_version=None): + def get_signature_request(self, signature_request_id): ''' Get a signature request by its ID Args: - signature_request_id (str): The id of the SignatureRequest to retrieve - - ux_version (int): UX version, either 1 (default) or 2. + signature_request_id (str): The id of the SignatureRequest to retrieve Returns: A SignatureRequest object @@ -278,15 +293,10 @@ def get_signature_request(self, signature_request_id, ux_version=None): request = self._get_request() parameters = None - if ux_version is not None: - parameters = { - 'ux_version': ux_version - } - return request.get(self.SIGNATURE_REQUEST_INFO_URL + signature_request_id, parameters=parameters) @api_resource_list(SignatureRequest) - def get_signature_request_list(self, page=1, ux_version=None): + def get_signature_request_list(self, page=1): ''' Get a list of SignatureRequest that you can access This includes SignatureRequests you have sent as well as received, but @@ -294,9 +304,7 @@ def get_signature_request_list(self, page=1, ux_version=None): Args: - page (int, optional): Which page number of the SignatureRequest list to return. Defaults to 1. - - ux_version (int): UX version, either 1 (default) or 2. + page (int, optional): Which page number of the SignatureRequest list to return. Defaults to 1. Returns: A ResourceList object @@ -308,12 +316,9 @@ def get_signature_request_list(self, page=1, ux_version=None): "page": page } - if ux_version is not None: - parameters['ux_version'] = ux_version - return request.get(self.SIGNATURE_REQUEST_LIST_URL, parameters=parameters) - def get_signature_request_file(self, signature_request_id, path_or_file=None, file_type=None, filename=None): + def get_signature_request_file(self, signature_request_id, path_or_file=None, file_type=None, filename=None, response_type=None): ''' Download the PDF copy of the current documents Args: @@ -322,21 +327,35 @@ def get_signature_request_file(self, signature_request_id, path_or_file=None, fi path_or_file (str or file): A writable File-like object or a full path to save the PDF file to. - filename (str): [DEPRECATED] Filename to save the PDF file to. This should be a full path. + filename (str): [DEPRECATED] Filename to save the PDF file to. This should be a full path. + + file_type (str): Type of file to return. Either "pdf" for a single merged document or "zip" + for a collection of individual documents. Defaults to "pdf" if not specified. - file_type (str): Type of file to return. Either "pdf" for a single merged document or "zip" for a collection of individual documents. Defaults to "pdf" if not specified. + response_type (str): File type of response to return. Either "url" to return a URL link to the file + or "data_uri" to return the file as a base64 encoded string. Only applicable to the "pdf" file_type. Returns: - True if file is downloaded and successfully written, False otherwise. + Returns a PDF file, URL link to file, or base64 encoded file ''' request = self._get_request() url = self.SIGNATURE_REQUEST_DOWNLOAD_PDF_URL + signature_request_id if file_type: url += '?file_type=%s' % file_type - return request.get_file(url, path_or_file or filename) - - def send_signature_request(self, test_mode=False, files=None, file_urls=None, title=None, subject=None, message=None, signing_redirect_url=None, signers=None, cc_email_addresses=None, form_fields_per_document=None, use_text_tags=False, hide_text_tags=False, metadata=None, ux_version=None, allow_decline=False): + return request.get_file(url, path_or_file or filename) + + if response_type == 'url': + url += '?get_url=1' + elif response_type == 'data_uri': + url += '?get_data_uri=1' + return request.get(url) + + def send_signature_request(self, test_mode=False, files=None, file_urls=None, + title=None, subject=None, message=None, signing_redirect_url=None, + signers=None, cc_email_addresses=None, form_fields_per_document=None, + use_text_tags=False, hide_text_tags=False, custom_fields=None, + metadata=None, allow_decline=False, allow_reassign=False, signing_options=None): ''' Creates and sends a new SignatureRequest with the submitted documents Creates and sends a new SignatureRequest with the submitted documents. @@ -346,40 +365,47 @@ def send_signature_request(self, test_mode=False, files=None, file_urls=None, ti Args: - test_mode (bool, optional): Whether this is a test, the signature request will not be legally binding if set to True. Defaults to False. + test_mode (bool, optional): Whether this is a test, the signature request will not be legally binding + if set to True. Defaults to False. + + files (list of str): The uploaded file(s) to send for signature. + + file_urls (list of str): URLs of the file for HelloSign to download to send for signature. Use either `files` or `file_urls` - files (list of str): The uploaded file(s) to send for signature + title (str, optional): The title you want to assign to the SignatureRequest. - file_urls (list of str): URLs of the file for HelloSign to download to send for signature. Use either `files` or `file_urls` + subject (str, optional): The subject in the email that will be sent to the signers. - title (str, optional): The title you want to assign to the SignatureRequest + message (str, optional): The custom message in the email that will be sent to the signers. - subject (str, optional): The subject in the email that will be sent to the signers + signing_redirect_url (str, optional): The URL you want the signer redirected to after they successfully sign. - message (str, optional): The custom message in the email that will be sent to the signers + signers (list of dict): A list of signers, which each has the following attributes: - signing_redirect_url (str, optional): The URL you want the signer redirected to after they successfully sign. + name (str): The name of the signer + email_address (str): Email address of the signer + order (str, optional): The order the signer is required to sign in + pin (str, optional): The 4- to 12-character access code that will secure this signer's signature page - signers (list of dict): A list of signers, which each has the following attributes: + cc_email_addresses (list, optional): A list of email addresses that should be CC'd on the request. - name (str): The name of the signer - email_address (str): Email address of the signer - order (str, optional): The order the signer is required to sign in - pin (str, optional): The 4- to 12-character access code that will secure this signer's signature page + form_fields_per_document (str): The signer components that should appear on the document, expressed as a serialized + JSON data structure which is a list of lists of the form fields. Please refer to the API reference of HelloSign for more details (https://app.hellosign.com/api/reference#SignatureRequest). - cc_email_addresses (list, optional): A list of email addresses that should be CC'd + use_text_tags (bool, optional): Use text tags in the provided file(s) to specify signer components. - form_fields_per_document (str): The fields that should appear on the document, expressed as a serialized JSON data structure which is a list of lists of the form fields. Please refer to the API reference of HelloSign for more details (https://www.hellosign.com/api/reference#SignatureRequest) + hide_text_tags (bool, optional): Hide text tag areas. - use_text_tags (bool, optional): Use text tags in the provided file(s) to create form fields + custom_fields (list of dict, optional): A list of custom fields defined by Text Tags for Form Fields per Document. + An item of the list should look like this: `{'name: value'}` - hide_text_tags (bool, optional): Hide text tag areas + metadata (dict, optional): Metadata associated with the signature request. - metadata (dict, optional): Metadata to associate with the signature request + allow_decline (bool, optional): Allows signers to decline to sign a document if set to True. Defaults to False. - ux_version (int): UX version, either 1 (default) or 2. + allow_reassign (bool, optional): Allows signers to reassign their signature requests to other signers if set to True. Defaults to False. - allow_decline(bool, optional): Allows signers to decline to sign a document if set to 1. Defaults to 0. + signing_options (dict, optional): Allows the requester to specify the types allowed for creating a signature. Defaults to account settings. Returns: A SignatureRequest object @@ -408,15 +434,17 @@ def send_signature_request(self, test_mode=False, files=None, file_urls=None, ti 'use_text_tags': use_text_tags, 'hide_text_tags': hide_text_tags, 'metadata': metadata, - 'allow_decline': allow_decline + 'allow_decline': allow_decline, + 'allow_reassign': allow_reassign, + 'signing_options': signing_options } - if ux_version is not None: - params['ux_version'] = ux_version - return self._send_signature_request(**params) - def send_signature_request_with_template(self, test_mode=False, template_id=None, template_ids=None, title=None, subject=None, message=None, signing_redirect_url=None, signers=None, ccs=None, custom_fields=None, metadata=None, ux_version=None, allow_decline=False): + def send_signature_request_with_template(self, test_mode=False, template_id=None, + template_ids=None, title=None, subject=None, message=None, + signing_redirect_url=None, signers=None, ccs=None, custom_fields=None, + metadata=None, allow_decline=False, files=None, file_urls=None, signing_options=None): ''' Creates and sends a new SignatureRequest based off of a Template Creates and sends a new SignatureRequest based off of the Template @@ -424,39 +452,47 @@ def send_signature_request_with_template(self, test_mode=False, template_id=None Args: - test_mode (bool, optional): Whether this is a test, the signature request will not be legally binding if set to True. Defaults to False. + test_mode (bool, optional): Whether this is a test, the signature request will not be legally binding if set to True. Defaults to False. - template_id (str): The id of the Template to use when creating the SignatureRequest. Mutually exclusive with template_ids. + template_id (str): The id of the Template to use when creating the SignatureRequest. Mutually exclusive with template_ids. - template_ids (list): The ids of the Templates to use when creating the SignatureRequest. Mutually exclusive with template_id. + template_ids (list): The ids of the Templates to use when creating the SignatureRequest. Mutually exclusive with template_id. - title (str, optional): The title you want to assign to the SignatureRequest + title (str, optional): The title you want to assign to the SignatureRequest - subject (str, optional): The subject in the email that will be sent to the signers + subject (str, optional): The subject in the email that will be sent to the signers - message (str, optional): The custom message in the email that will be sent to the signers + message (str, optional): The custom message in the email that will be sent to the signers - signing_redirect_url (str, optional): The URL you want the signer redirected to after they successfully sign. + signing_redirect_url (str, optional): The URL you want the signer redirected to after they successfully sign. - signers (list of dict): A list of signers, which each has the following attributes: + signers (list of dict): A list of signers, which each has the following attributes: - role_name (str): Signer role - name (str): The name of the signer - email_address (str): Email address of the signer - pin (str, optional): The 4- to 12-character access code that will secure this signer's signature page + role_name (str): Signer role + name (str): The name of the signer + email_address (str): Email address of the signer + pin (str, optional): The 4- to 12-character access code that will secure this signer's signature page - ccs (list of str, optional): The email address of the CC filling the role of RoleName. Required when a CC role exists for the Template. Each dict has the following attributes: + ccs (list of str, optional): The email address of the CC filling the role of RoleName. + Required when a CC role exists for the Template. Each dict has the following attributes: - role_name (str): CC role name - email_address (str): CC email address + role_name (str): CC role name + email_address (str): CC email address - custom_fields (list of dict, optional): A list of custom fields. Required when a CustomField exists in the Template. An item of the list should look like this: `{'name: value'}` + custom_fields (list of dict, optional): A list of custom fields. + Required when a CustomField exists in the Template. An item of the list should look like this: `{'name: value'}` + + metadata (dict, optional): Metadata to associate with the signature request - metadata (dict, optional): Metadata to associate with the signature request + allow_decline (bool, optional): Allows signers to decline to sign a document if set to 1. Defaults to 0. - ux_version (int): UX version, either 1 (default) or 2. + files (list of str): The uploaded file(s) to append to the Signature Request. - allow_decline (bool, optional): Allows signers to decline to sign a document if set to 1. Defaults to 0. + file_urls (list of str): URLs of the file for HelloSign to download to append to the Signature Request. + Use either `files` or `file_urls` + + signing_options (dict, optional): Allows the requester to specify the types allowed for creating a signature. + Defaults to account settings. Returns: A SignatureRequest object @@ -467,7 +503,9 @@ def send_signature_request_with_template(self, test_mode=False, template_id=None "signers": signers }, [{ "template_id": template_id, - "template_ids": template_ids + "template_ids": template_ids, + "files": files, + "file_urls": file_urls }] ) @@ -483,16 +521,16 @@ def send_signature_request_with_template(self, test_mode=False, template_id=None 'ccs': ccs, 'custom_fields': custom_fields, 'metadata': metadata, - 'allow_decline': allow_decline + 'allow_decline': allow_decline, + 'files': files, + 'file_urls': file_urls, + 'signing_options': signing_options } - if ux_version is not None: - params['ux_version'] = ux_version - return self._send_signature_request_with_template(**params) @api_resource(SignatureRequest) - def remind_signature_request(self, signature_request_id, email_address): + def remind_signature_request(self, signature_request_id, email_address, name=None): ''' Sends an email to the signer reminding them to sign the signature request Sends an email to the signer reminding them to sign the signature @@ -503,7 +541,9 @@ def remind_signature_request(self, signature_request_id, email_address): signature_request_id (str): The id of the SignatureRequest to send a reminder for - email_address (str): The email address of the signer to send a reminder to + email_address (str): The email address of the signer to send a reminder to + + name (str, optional): The name of the signer to send a reminder to Returns: A SignatureRequest object @@ -511,6 +551,29 @@ def remind_signature_request(self, signature_request_id, email_address): ''' request = self._get_request() return request.post(self.SIGNATURE_REQUEST_REMIND_URL + signature_request_id, data={ + "email_address": email_address, + "name": name + }) + + @api_resource(SignatureRequest) + def update_signature_request(self, signature_request_id, signature_id, email_address): + ''' Updates the email address for a given signer on a signature request. + + Args: + + signature_request_id (str): The id of the SignatureRequest to update + + signature_id (str): The signature id for the recipient + + email_address (str): The new email address of the recipient + + Returns: + A SignatureRequest object + + ''' + request = self._get_request() + return request.post(self.SIGNATURE_REQUEST_UPDATE_URL + signature_request_id, data={ + "signature_id": signature_id, "email_address": email_address }) @@ -523,7 +586,7 @@ def cancel_signature_request(self, signature_request_id): Args: - signing_request_id (str): The id of the signature request to cancel + signature_request_id (str): The id of the signature request to cancel Returns: None @@ -532,11 +595,32 @@ def cancel_signature_request(self, signature_request_id): request = self._get_request() request.post(url=self.SIGNATURE_REQUEST_CANCEL_URL + signature_request_id, get_json=False) - def send_signature_request_embedded(self, test_mode=False, client_id=None, files=None, file_urls=None, title=None, subject=None, message=None, signing_redirect_url=None, signers=None, cc_email_addresses=None, form_fields_per_document=None, use_text_tags=False, hide_text_tags=False, metadata=None, ux_version=None, allow_decline=False): + def remove_signature_request_access(self, signature_request_id): + ''' Removes your access to a completed SignatureRequest + + The SignatureRequest must be fully executed by all parties (signed or declined to sign). + Other parties will continue to maintain access to the completed signature request document(s). + + Args: + + signature_request_id (str): The id of the signature request to remove + + Returns: + None + + ''' + request = self._get_request() + request.post(url=self.SIGNATURE_REQUEST_REMOVE_ACCESS_URL + signature_request_id, get_json=False) + + def send_signature_request_embedded(self, test_mode=False, client_id=None, + files=None, file_urls=None, title=None, subject=None, message=None, + signing_redirect_url=None, signers=None, cc_email_addresses=None, + form_fields_per_document=None, use_text_tags=False, hide_text_tags=False, + metadata=None, allow_decline=False, allow_reassign=False, signing_options=None): ''' Creates and sends a new SignatureRequest with the submitted documents Creates a new SignatureRequest with the submitted documents to be signed - in an embedded iFrame . If form_fields_per_document is not specified, a + in an embedded iFrame. If form_fields_per_document or text tags are not specified, a signature page will be affixed where all signers will be required to add their signature, signifying their agreement to all contained documents. Note that embedded signature requests can only be signed in embedded @@ -545,42 +629,46 @@ def send_signature_request_embedded(self, test_mode=False, client_id=None, files Args: - test_mode (bool, optional): Whether this is a test, the signature request will not be legally binding if set to True. Defaults to False. + test_mode (bool, optional): Whether this is a test, the signature request will not be legally binding if set to True. Defaults to False. + + client_id (str): Client id of the app you're using to create this embedded signature request. + Visit the embedded page to learn more about this parameter (https://www.hellosign.com/api/embeddedSigningWalkthrough) - client_id (str): Client id of the app you're using to create this embedded signature request. Visit the embedded page to learn more about this parameter (https://www.hellosign.com/api/embeddedSigningWalkthrough) + files (list of str): The uploaded file(s) to send for signature - files (list of str): The uploaded file(s) to send for signature + file_urls (list of str): URLs of the file for HelloSign to download to send for signature. Use either `files` or `file_urls` - file_urls (list of str): URLs of the file for HelloSign to download to send for signature. Use either `files` or `file_urls` + title (str, optional): The title you want to assign to the SignatureRequest - title (str, optional): The title you want to assign to the SignatureRequest + subject (str, optional): The subject in the email that will be sent to the signers - subject (str, optional): The subject in the email that will be sent to the signers + message (str, optional): The custom message in the email that will be sent to the signers - message (str, optional): The custom message in the email that will be sent to the signers + signing_redirect_url (str, optional): The URL you want the signer redirected to after they successfully sign. - signing_redirect_url (str, optional): The URL you want the signer redirected to after they successfully sign. + signers (list of dict): A list of signers, which each has the following attributes: - signers (list of dict): A list of signers, which each has the following attributes: + name (str): The name of the signer + email_address (str): Email address of the signer + order (str, optional): The order the signer is required to sign in + pin (str, optional): The 4- to 12-character access code that will secure this signer's signature page - name (str): The name of the signer - email_address (str): Email address of the signer - order (str, optional): The order the signer is required to sign in - pin (str, optional): The 4- to 12-character access code that will secure this signer's signature page + cc_email_addresses (list, optional): A list of email addresses that should be CCed - cc_email_addresses (list, optional): A list of email addresses that should be CCed + form_fields_per_document (str): The fields that should appear on the document, expressed as a serialized + JSON data structure which is a list of lists of the form fields. Please refer to the API reference of HelloSign for more details (https://www.hellosign.com/api/reference#SignatureRequest) - form_fields_per_document (str): The fields that should appear on the document, expressed as a serialized JSON data structure which is a list of lists of the form fields. Please refer to the API reference of HelloSign for more details (https://www.hellosign.com/api/reference#SignatureRequest) + use_text_tags (bool, optional): Use text tags in the provided file(s) to create form fields - use_text_tags (bool, optional): Use text tags in the provided file(s) to create form fields + hide_text_tags (bool, optional): Hide text tag areas - hide_text_tags (bool, optional): Hide text tag areas + metadata (dict, optional): Metadata to associate with the signature request - metadata (dict, optional): Metadata to associate with the signature request + allow_decline (bool, optional): Allows signers to decline to sign a document if set to 1. Defaults to 0. - ux_version (int): UX version, either 1 (default) or 2. + allow_reassign (bool, optional): Allows signers to reassign their signature requests to other signers if set to True. Defaults to False. - allow_decline (bool, optional): Allows signers to decline to sign a document if set to 1. Defaults to 0. + signing_options (dict, optional): Allows the requester to specify the types allowed for creating a signature. Defaults to account settings. Returns: A SignatureRequest object @@ -611,15 +699,18 @@ def send_signature_request_embedded(self, test_mode=False, client_id=None, files 'use_text_tags': use_text_tags, 'hide_text_tags': hide_text_tags, 'metadata': metadata, - 'allow_decline': allow_decline + 'allow_decline': allow_decline, + 'allow_reassign': allow_reassign, + 'signing_options': signing_options } - if ux_version is not None: - params['ux_version'] = ux_version - return self._send_signature_request(**params) - def send_signature_request_embedded_with_template(self, test_mode=False, client_id=None, template_id=None, template_ids=None, title=None, subject=None, message=None, signing_redirect_url=None, signers=None, ccs=None, custom_fields=None, metadata=None, ux_version=None, allow_decline=False): + def send_signature_request_embedded_with_template(self, test_mode=False, + client_id=None, template_id=None, template_ids=None, title=None, + subject=None, message=None, signing_redirect_url=None, signers=None, + ccs=None, custom_fields=None, metadata=None, allow_decline=False, + files=None, file_urls=None, signing_options=None): ''' Creates and sends a new SignatureRequest based off of a Template Creates a new SignatureRequest based on the given Template to be @@ -629,43 +720,50 @@ def send_signature_request_embedded_with_template(self, test_mode=False, client_ Args: - test_mode (bool, optional): Whether this is a test, the signature request will not be legally binding if set to True. Defaults to False. + test_mode (bool, optional): Whether this is a test, the signature request will not be legally binding if set to True. Defaults to False. - client_id (str): Client id of the app you're using to create this embedded signature request. Visit the embedded page to learn more about this parameter (https://www.hellosign.com/api/embeddedSigningWalkthrough) + client_id (str): Client id of the app you're using to create this embedded signature request. + Visit the embedded page to learn more about this parameter (https://app.hellosign.com/api/embeddedSigningWalkthrough) - template_id (str): The id of the Template to use when creating the SignatureRequest. Mutually exclusive with template_ids. + template_id (str): The id of the Template to use when creating the SignatureRequest. Mutually exclusive with template_ids. - template_ids (list): The ids of the Templates to use when creating the SignatureRequest. Mutually exclusive with template_id. + template_ids (list): The ids of the Templates to use when creating the SignatureRequest. Mutually exclusive with template_id. - title (str, optional): The title you want to assign to the SignatureRequest + title (str, optional): The title you want to assign to the SignatureRequest - subject (str, optional): The subject in the email that will be sent to the signers + subject (str, optional): The subject in the email that will be sent to the signers - message (str, optional): The custom message in the email that will be sent to the signers + message (str, optional): The custom message in the email that will be sent to the signers - signing_redirect_url (str, optional): The URL you want the signer redirected to after they successfully sign. + signing_redirect_url (str, optional): The URL you want the signer redirected to after they successfully sign. - signers (list of dict): A list of signers, which each has the following attributes: + signers (list of dict): A list of signers, which each has the following attributes: - name (str): The name of the signer - email_address (str): Email address of the signer - pin (str, optional): The 4- to 12-character access code that will secure this signer's signature page + name (str): The name of the signer + email_address (str): Email address of the signer + pin (str, optional): The 4- to 12-character access code that will secure this signer's signature page - ccs (list of dict, optional): The email address of the CC filling the role of RoleName. Required when a CC role exists for the Template. Each dict has the following attributes: + ccs (list of dict, optional): The email address of the CC filling the role of RoleName. + Required when a CC role exists for the Template. Each dict has the following attributes: - role_name (str): CC role name - email_address (str): CC email address + role_name (str): CC role name + email_address (str): CC email address - custom_fields (list of dict, optional): A list of custom fields. Required when a CustomField exists in the Template. An item of the list should look like this: `{'name: value'}` + custom_fields (list of dict, optional): A list of custom fields. Required when a CustomField exists in the Template. + An item of the list should look like this: `{'name: value'}` - metadata (dict, optional): Metadata to associate with the signature request + metadata (dict, optional): Metadata to associate with the signature request - ux_version (int): UX version, either 1 (default) or 2. + allow_decline (bool, optional): Allows signers to decline to sign a document if set to 1. Defaults to 0. - allow_decline (bool, optional): Allows signers to decline to sign a document if set to 1. Defaults to 0. + files (list of str): The uploaded file(s) to append to the Signature Request. + + file_urls (list of str): URLs of the file for HelloSign to download to append to the Signature Request. Use either `files` or `file_urls` + + signing_options (dict, optional): Allows the requester to specify the types allowed for creating a signature. Defaults to account settings. Returns: - A SignatureRequest object of the newly created Signature Request + A SignatureRequest object ''' @@ -674,7 +772,9 @@ def send_signature_request_embedded_with_template(self, test_mode=False, client_ "client_id": client_id }, [{ "template_id": template_id, - "template_ids": template_ids + "template_ids": template_ids, + "files": files, + "file_urls": file_urls }] ) @@ -691,15 +791,15 @@ def send_signature_request_embedded_with_template(self, test_mode=False, client_ 'ccs': ccs, 'custom_fields': custom_fields, 'metadata': metadata, - 'allow_decline': allow_decline + 'allow_decline': allow_decline, + 'files': files, + 'file_urls': file_urls, + 'signing_options': signing_options } - if ux_version is not None: - params['ux_version'] = ux_version - return self._send_signature_request_with_template(**params) - # ---- REUSABLE FORM METHODS ----------------------- + # ---- TEMPLATE METHODS ----------------------- @api_resource(Template) def get_template(self, template_id): @@ -722,10 +822,14 @@ def get_template_list(self, page=1, page_size=None, account_id=None, query=None) Args: - page (int, optional): Page number of the template List to return. Defaults to 1. - page_size (int, optional): Number of objects to be returned per page, must be between 1 and 100, default is 20. - account_id (str, optional): Which account to return Templates for. Must be a team member. Use "all" to indicate all team members. Defaults to your account. - query (str, optional): String that includes search terms and/or fields to be used to filter the Template objects. + page (int, optional): Page number of the template List to return. Defaults to 1. + + page_size (int, optional): Number of objects to be returned per page, must be between 1 and 100, default is 20. + + account_id (str, optional): Which account to return Templates for. Must be a team member. + Use "all" to indicate all team members. Defaults to your account. + + query (str, optional): String that includes search terms and/or fields to be used to filter the Template objects. Returns: A ResourceList object @@ -746,11 +850,11 @@ def add_user_to_template(self, template_id, account_id=None, email_address=None) Args: - template_id (str): The id of the template to give the account access to + template_id (str): The id of the template to give the account access to - account_id (str): The id of the account to give access to the template. The account id prevails if both account_id and email_address are provided. + account_id (str): The id of the account to give access to the template. The account id prevails if both account_id and email_address are provided. - email_address (str): The email address of the account to give access to. + email_address (str): The email address of the account to give access to. Returns: A Template object @@ -763,11 +867,12 @@ def remove_user_from_template(self, template_id, account_id=None, email_address= Args: - template_id (str): The id of the template to remove the account's access from. + template_id (str): The id of the template to remove the account's access from. - account_id (str): The id of the account to remove access from the template. The account id prevails if both account_id and email_address are provided. + account_id (str): The id of the account to remove access from the template. + The account id prevails if both account_id and email_address are provided. - email_address (str): The email address of the account to remove access from. + email_address (str): The email address of the account to remove access from. Returns: An Template object @@ -794,61 +899,128 @@ def delete_template(self, template_id): return response - # def get_template_files(self, template_id): - def get_template_files(self, template_id, filename): - ''' Download a PDF copy of a template's original files + def get_template_files(self, template_id, path_or_file=None, file_type=None, + filename=None, response_type=None): + ''' Downloads a copy of a template's original files Args: - template_id (str): The id of the template to retrieve. + template_id (str): id of the template to download + + path_or_file (str or file): A writable File-like object or a full path to save the PDF file to. + + filename (str): [DEPRECATED] Filename to save the PDF file to. This should be a full path. - filename (str): Filename to save the PDF file to. This should be a full path. + file_type (str): Type of file to return. Either "pdf" for a single merged document or + "zip" for a collection of individual documents. Defaults to "pdf" if not specified. + + response_type (str): File type of response to return. Either "url" to return a URL link to the file + or "data_uri" to return the file as a base64 encoded string. Only applicable to the "pdf" file_type. Returns: - Returns a PDF file + Returns a PDF file, URL link to file, or base64 encoded file ''' - - url = self.TEMPLATE_GET_FILES_URL + template_id request = self._get_request() + url = self.TEMPLATE_GET_FILES_URL + template_id + if file_type: + url += '?file_type=%s' % file_type + return request.get_file(url, path_or_file or filename) + + if response_type == 'url': + url += '?get_url=1' + elif response_type == 'data_uri': + url += '?get_data_uri=1' + return request.get(url) - return request.get_file(url, filename) + def update_template_files(self, template_id, files=None, file_urls=None, + subject=None, message=None, client_id=None, test_mode=False): + ''' Overlays a new file with the overlay of an existing template. + + Args: - def create_embedded_template_draft(self, client_id, signer_roles, test_mode=False, files=None, file_urls=None, title=None, subject=None, message=None, cc_roles=None, merge_fields=None, use_preexisting_fields=False): + template_id (str): The id of the template whose files to update + + files (list of str): The file(s) to use for the template. + + file_urls (list of str): URLs of the file for HelloSign to use for the template. + Use either `files` or `file_urls`, but not both. + + subject (str, optional): The default template email subject + + message (str, optional): The default template email message + + test_mode (bool, optional): Whether this is a test, the signature request created + from this Template will not be legally binding if set to 1. Defaults to 0. + + client_id (str): Client id of the app associated with the Template + + Returns: + A Template object + + ''' + request = self._get_request() + return request.post(self.TEMPLATE_UPDATE_FILES_URL + template_id, data={ + "files": files, + "file_urls": file_urls, + "subject": subject, + "message": message, + "test_mode": self._boolean(test_mode), + "client_id": client_id + }) + + def create_embedded_template_draft(self, client_id, signer_roles, test_mode=False, + files=None, file_urls=None, title=None, subject=None, message=None, + cc_roles=None, merge_fields=None, skip_me_now=False, use_preexisting_fields=False, + allow_reassign=False, metadata=None, allow_ccs=False): ''' Creates an embedded Template draft for further editing. Args: - test_mode (bool, optional): Whether this is a test, the signature request created from this draft will not be legally binding if set to 1. Defaults to 0. + test_mode (bool, optional): Whether this is a test, the signature request + created from this draft will not be legally binding if set to 1. Defaults to 0. + + client_id (str): Client id of the app you're using to create this draft. - client_id (str): Client id of the app you're using to create this draft. + files (list of str): The file(s) to use for the template. - files (list of str): The file(s) to use for the template. + file_urls (list of str): URLs of the file for HelloSign to use for the template. + Use either `files` or `file_urls`, but not both. - file_urls (list of str): URLs of the file for HelloSign to use for the template. Use either `files` or `file_urls`, but not both. + title (str, optional): The template title - title (str, optional): The template title + subject (str, optional): The default template email subject - subject (str, optional): The default template email subject + message (str, optional): The default template email message - message (str, optional): The default template email message + signer_roles (list of dict): A list of signer roles, each of which has the following attributes: - signer_roles (list of dict): A list of signer roles, each of which has the following attributes: + name (str): The role name of the signer that will be displayed when the + template is used to create a signature request. + order (str, optional): The order in which this signer role is required to sign. - name (str): The role name of the signer that will be displayed when the template is used to create a signature request. - order (str, optional): The order in which this signer role is required to sign. + cc_roles (list of str, optional): The CC roles that must be assigned when using the template to send a signature request - cc_roles (list of str, optional): The CC roles that must be assigned when using the template to send a signature request + merge_fields (list of dict, optional): The merge fields that can be placed on the template's + document(s) by the user claiming the template draft. Each must have the following two parameters: - merge_fields (list of dict, optional): The merge fields that can be placed on the template's document(s) by the user claiming the template draft. Each must have the following two parameters: + name (str): The name of the merge field. Must be unique. + type (str): Can only be "text" or "checkbox". - name (str): The name of the merge field. Must be unique. - type (str): Can only be "text" or "checkbox". + skip_me_now (bool, optional): Disables the "Me (Now)" option for the document's preparer. Defaults to 0. - use_preexisting_fields (bool): Whether to use preexisting PDF fields + use_preexisting_fields (bool, optional): Whether to use preexisting PDF fields + + metadata (dict, optional): Metadata to associate with the draft + + allow_reassign (bool, optional): Allows signers to reassign their signature + requests to other signers if set to True. Defaults to False. + + allow_ccs (bool, optional): Specifies whether the user is allowed to + provide email addresses to CC when creating a template. Defaults to False. Returns: - A Template object specifying the Id of the draft + A Template object specifying the id of the draft ''' params = { @@ -862,7 +1034,11 @@ def create_embedded_template_draft(self, client_id, signer_roles, test_mode=Fals 'signer_roles': signer_roles, 'cc_roles': cc_roles, 'merge_fields': merge_fields, - 'use_preexisting_fields': use_preexisting_fields + 'skip_me_now': skip_me_now, + 'use_preexisting_fields': use_preexisting_fields, + 'metadata': metadata, + 'allow_reassign': allow_reassign, + 'allow_ccs': allow_ccs } return self._create_embedded_template_draft(**params) @@ -934,9 +1110,10 @@ def add_team_member(self, account_id=None, email_address=None): Args: - account_id (str): The id of the account of the user to invite to your team. + account_id (str): The id of the account of the user to invite to your team. - email_address (str): The email address of the account to invite to your team. The account id prevails if both account_id and email_address are provided. + email_address (str): The email address of the account to invite to your team. + The account id prevails if both account_id and email_address are provided. Returns: A Team object @@ -950,9 +1127,10 @@ def remove_team_member(self, account_id=None, email_address=None): Args: - account_id (str): The id of the account of the user to remove from your team. + account_id (str): The id of the account of the user to remove from your team. - email_address (str): The email address of the account to remove from your team. The account id prevails if both account_id and email_address are provided. + email_address (str): The email address of the account to remove from your team. + The account id prevails if both account_id and email_address are provided. Returns: A Team object @@ -964,7 +1142,7 @@ def remove_team_member(self, account_id=None, email_address=None): @api_resource(Embedded) def get_embedded_object(self, signature_id): - ''' Retrieves a embedded signing object + ''' Retrieves an embedded signing object Retrieves an embedded object containing a signature url that can be opened in an iFrame. @@ -980,25 +1158,225 @@ def get_embedded_object(self, signature_id): return request.get(self.EMBEDDED_OBJECT_GET_URL + signature_id) @api_resource(Embedded) - def get_template_edit_url(self, template_id): + def get_template_edit_url(self, template_id, test_mode=False, cc_roles=None, + merge_fields=None, skip_signer_roles=False, skip_subject_message=False): ''' Retrieves a embedded template for editing - Retrieves an embedded object containing a template url that can be opened in an iFrame. + Retrieves an embedded object containing a template edit url that can be opened in an iFrame. Args: - template_id (str): The id of the template to get a signature url for + template_id (str): The id of the template to get an edit url for + + test_mode (bool, optional): Whether this is a test, the signature requests created + from this template will not be legally binding if set to True. Defaults to False. + + cc_roles (list of str, optional): The CC roles that must be assigned when using + the template to send a signature request + + merge_fields (list of dict, optional): The merge fields that can be placed on the template's document(s) + by the user claiming the template draft. Each must have the following two parameters: + + name (str): The name of the merge field. Must be unique. + type (str): Can only be "text" or "checkbox". + + skip_me_now (bool, optional): Disables the "Me (Now)" option for the document's preparer. + Defaults to False. + + skip_subject_message (bool, optional): Disables the option to edit the template's default + subject and message. Defaults to False. Returns: An Embedded object + ''' + + # Prep CCs + ccs_payload = HSFormat.format_param_list(cc_roles, 'cc_roles') + # Prep Merge Fields + merge_fields_payload = { + 'merge_fields': json.dumps(merge_fields) + } + + payload = { + "test_mode": self._boolean(test_mode), + "skip_signer_roles": self._boolean(skip_signer_roles), + "skip_subject_message": self._boolean(skip_subject_message) + } + + # remove attributes with none value + payload = HSFormat.strip_none_values(payload) + + url = self.EMBEDDED_TEMPLATE_EDIT_URL + template_id + + data = {} + data.update(payload) + data.update(ccs_payload) + data.update(merge_fields_payload) + + request = self._get_request() + response = request.post(url, data=data) + return response + + # ---- API APP METHODS -------------------------------- + + @api_resource(ApiApp) + def get_api_app_info(self, client_id): + ''' Gets an API App by its Client ID + + Returns information about the specified API App + + Returns: + An ApiApp object + + ''' + request = self._get_request() + return request.get(self.API_APP_INFO_URL + client_id) + + @api_resource_list(ApiApp) + def get_api_app_list(self, page=1, page_size=None): + ''' Lists your API Apps + + Args: + + page (int, optional): Page number of the API App List to return. Defaults to 1. + + page_size (int, optional): Number of objects to be returned per page, must be between 1 and 100, default is 20. + + Returns: + A ResourceList object + + ''' + request = self._get_request() + parameters = { + 'page': page, + 'page_size': page_size + } + return request.get(self.API_APP_LIST_URL, parameters=parameters) + + @api_resource(ApiApp) + def create_api_app(self, name, domain, callback_url=None, custom_logo_file=None, + oauth_callback_url=None, oauth_scopes=None, white_labeling_options=None, + option_insert_everywhere=False): + ''' Creates a new API App + + Creates a new API App with the specified settings. + + Args: + + name (str): The name of the API App + + domain (str): The domain name associated with the API App + + callback_url (str, optional): The URL that HelloSign events will be POSTed to + + custom_logo_file (str, optional): The image file to use as a custom logo + + oauth_callback_url (str, optional): The URL that HelloSign OAuth events will be POSTed to + + oauth_scopes (list of str, optional): List of the API App's OAuth scopes + + white_labeling_options (dict, optional): Customization options for the API App's signer page + + option_insert_everywhere (bool, optional): Denotes if signers can "Insert Everywhere" when + signing a document + + Returns: + An ApiApp object + + ''' + + # Prep custom logo + custom_logo_payload = HSFormat.format_logo_params(custom_logo_file) + + payload = { + "name": name, + "domain": domain, + "callback_url": callback_url, + "oauth[callback_url]": oauth_callback_url, + "oauth[scopes]": oauth_scopes, + "white_labeling_options": json.dumps(white_labeling_options), + "options[can_insert_everywhere]": self._boolean(option_insert_everywhere) + } + + # remove attributes with none value + payload = HSFormat.strip_none_values(payload) + + request = self._get_request() + return request.post(self.API_APP_CREATE_URL, data=payload, files=custom_logo_payload) + + @api_resource(ApiApp) + def update_api_app(self, client_id, name=None, domain=None, callback_url=None, + custom_logo_file=None, oauth_callback_url=None, oauth_scopes=None, + white_labeling_options=None, option_insert_everywhere=False): + ''' Updates the specified API App + + Updates an API App with the specified settings. + + Args: + + name (str): The name of the API App + + domain (str): The domain name associated with the API App + + callback_url (str, optional): The URL that HelloSign events will be POSTed to + + custom_logo_file (str, optional): The image file to use as a custom logo + + oauth_callback_url (str, optional): The URL that HelloSign OAuth events will be POSTed to + + oauth_scopes (list of str, optional): List of the API App's OAuth scopes + + white_labeling_options (dict, optional): Customization options for the API App's signer page + + option_insert_everywhere (bool, optional): Denotes if signers can "Insert Everywhere" when + signing a document + + Returns: + An ApiApp object + + ''' + + # Prep custom logo + custom_logo_payload = HSFormat.format_logo_params(custom_logo_file) + + payload = { + "name": name, + "domain": domain, + "callback_url": callback_url, + "oauth[callback_url]": oauth_callback_url, + "oauth[scopes]": oauth_scopes, + "white_labeling_options": json.dumps(white_labeling_options), + "options[can_insert_everywhere]": self._boolean(option_insert_everywhere) + } + + # remove attributes with none value + payload = HSFormat.strip_none_values(payload) + + request = self._get_request() + url = self.API_APP_UPDATE_URL + client_id + + return request.post(url, data=payload, files=custom_logo_payload) + + def delete_api_app(self, client_id): + ''' Deletes the specified API App + + Deletes an API App. Can only be involved for API Apps you own. + + Returns: + None + ''' request = self._get_request() - return request.get(self.EMBEDDED_TEMPLATE_EDIT_URL + template_id) + request.delete(url=self.API_APP_DELETE_URL + client_id) # ---- UNCLAIMED DRAFT METHODS --------------------- - def create_unclaimed_draft(self, test_mode=False, files=None, file_urls=None, draft_type=None, subject=None, message=None, signers=None, cc_email_addresses=None, signing_redirect_url=None, form_fields_per_document=None, metadata=None, use_preexisting_fields=False, allow_decline=False): + def create_unclaimed_draft(self, test_mode=False, files=None, file_urls=None, + draft_type=None, subject=None, message=None, signers=None, custom_fields=None, + cc_email_addresses=None, signing_redirect_url=None, form_fields_per_document=None, + metadata=None, use_preexisting_fields=False, use_text_tags=False, + hide_text_tags=False, allow_decline=False, signing_options=None): ''' Creates a new Draft that can be claimed using the claim URL Creates a new Draft that can be claimed using the claim URL. The first @@ -1012,35 +1390,47 @@ def create_unclaimed_draft(self, test_mode=False, files=None, file_urls=None, dr Args: - test_mode (bool, optional): Whether this is a test, the signature request created from this draft will not be legally binding if set to True. Defaults to False. + test_mode (bool, optional): Whether this is a test, the signature request created from this draft will not be legally binding if set to True. Defaults to False. - files (list of str): The uploaded file(s) to send for signature + files (list of str): The uploaded file(s) to send for signature - file_urls (list of str): URLs of the file for HelloSign to download to send for signature. Use either `files` or `file_urls` + file_urls (list of str): URLs of the file for HelloSign to download to send for signature. Use either `files` or `file_urls` - draft_type (str): The type of unclaimed draft to create. Use "send_document" to create a claimable file, and "request_signature" for a claimable signature request. If the type is "request_signature" then signers name and email_address are not optional. + draft_type (str): The type of unclaimed draft to create. Use "send_document" to create a claimable file, and "request_signature" + for a claimable signature request. If the type is "request_signature" then signers name and email_address are not optional. - subject (str, optional): The subject in the email that will be sent to the signers + subject (str, optional): The subject in the email that will be sent to the signers - message (str, optional): The custom message in the email that will be sent to the signers + message (str, optional): The custom message in the email that will be sent to the signers - signers (list of dict): A list of signers, which each has the following attributes: + signers (list of dict): A list of signers, which each has the following attributes: - name (str): The name of the signer - email_address (str): Email address of the signer - order (str, optional): The order the signer is required to sign in + name (str): The name of the signer + email_address (str): Email address of the signer + order (str, optional): The order the signer is required to sign in + + custom_fields (list of dict, optional): A list of custom fields. Required when a CustomField exists in the Template. + An item of the list should look like this: `{'name: value'}` cc_email_addresses (list of str, optional): A list of email addresses that should be CC'd - signing_redirect_url (str, optional): The URL you want the signer redirected to after they successfully sign. + signing_redirect_url (str, optional): The URL you want the signer redirected to after they successfully sign. + + form_fields_per_document (str, optional): The fields that should appear on the document, expressed as a serialized JSON + data structure which is a list of lists of the form fields. Please refer to the API reference of HelloSign for more details (https://www.hellosign.com/api/reference#SignatureRequest) - form_fields_per_document (str, optional): The fields that should appear on the document, expressed as a serialized JSON data structure which is a list of lists of the form fields. Please refer to the API reference of HelloSign for more details (https://www.hellosign.com/api/reference#SignatureRequest) + metadata (dict, optional): Metadata to associate with the draft - metadata (dict, optional): Metadata to associate with the draft + use_preexisting_fields (bool): Whether to use preexisting PDF fields - use_preexisting_fields (bool): Whether to use preexisting PDF fields + use_text_tags (bool, optional): Use text tags in the provided file(s) to create form fields - allow_decline (bool, optional): Allows signers to decline to sign a document if set to 1. Defaults to 0. + hide_text_tags (bool, optional): Hide text tag areas + + allow_decline (bool, optional): Allows signers to decline to sign a document if set to 1. Defaults to 0. + + signing_options (dict, optional): Allows the requester to specify the types allowed for creating a signature. + Defaults to account settings. Returns: An UnclaimedDraft object @@ -1064,57 +1454,80 @@ def create_unclaimed_draft(self, test_mode=False, files=None, file_urls=None, dr 'message': message, 'signing_redirect_url': signing_redirect_url, 'signers': signers, + 'custom_fields': custom_fields, 'cc_email_addresses': cc_email_addresses, 'form_fields_per_document': form_fields_per_document, 'metadata': metadata, 'use_preexisting_fields': use_preexisting_fields, - 'allow_decline': allow_decline + 'use_text_tags': use_text_tags, + 'hide_text_tags': hide_text_tags, + 'allow_decline': allow_decline, + 'signing_options': signing_options } return self._create_unclaimed_draft(**params) - def create_embedded_unclaimed_draft(self, test_mode=False, client_id=None, is_for_embedded_signing=False, requester_email_address=None, files=None, file_urls=None, draft_type=None, subject=None, message=None, signers=None, cc_email_addresses=None, signing_redirect_url=None, requesting_redirect_url=None, form_fields_per_document=None, metadata=None, use_preexisting_fields=False, allow_decline=False): + def create_embedded_unclaimed_draft(self, test_mode=False, client_id=None, + is_for_embedded_signing=False, requester_email_address=None, files=None, + file_urls=None, draft_type=None, subject=None, message=None, signers=None, + custom_fields=None, cc_email_addresses=None, signing_redirect_url=None, + requesting_redirect_url=None, form_fields_per_document=None, metadata=None, + use_preexisting_fields=False, use_text_tags=False, hide_text_tags=False, + skip_me_now=False, allow_decline=False, allow_reassign=False, + signing_options=None, allow_ccs=False): ''' Creates a new Draft to be used for embedded requesting Args: - test_mode (bool, optional): Whether this is a test, the signature request created from this draft will not be legally binding if set to True. Defaults to False. + test_mode (bool, optional): Whether this is a test, the signature request created from this draft will not be legally binding if set to True. Defaults to False. - client_id (str): Client id of the app used to create the embedded draft. + client_id (str): Client id of the app used to create the embedded draft. - is_for_embedded_signing (bool, optional): Whether this is also for embedded signing. Defaults to False. + is_for_embedded_signing (bool, optional): Whether this is also for embedded signing. Defaults to False. - requester_email_address (str): Email address of the requester. + requester_email_address (str): Email address of the requester. - files (list of str): The uploaded file(s) to send for signature. + files (list of str): The uploaded file(s) to send for signature. - file_urls (list of str): URLs of the file for HelloSign to download to send for signature. Use either `files` or `file_urls` + file_urls (list of str): URLs of the file for HelloSign to download to send for signature. Use either `files` or `file_urls` - draft_type (str): The type of unclaimed draft to create. Use "send_document" to create a claimable file, and "request_signature" for a claimable signature request. If the type is "request_signature" then signers name and email_address are not optional. + draft_type (str): The type of unclaimed draft to create. Use "send_document" to create a claimable file, and "request_signature" for a claimable signature request. If the type is "request_signature" then signers name and email_address are not optional. - subject (str, optional): The subject in the email that will be sent to the signers + subject (str, optional): The subject in the email that will be sent to the signers - message (str, optional): The custom message in the email that will be sent to the signers + message (str, optional): The custom message in the email that will be sent to the signers - signers (list of dict): A list of signers, which each has the following attributes: + signers (list of dict): A list of signers, which each has the following attributes: - name (str): The name of the signer - email_address (str): Email address of the signer - order (str, optional): The order the signer is required to sign in + name (str): The name of the signer + email_address (str): Email address of the signer + order (str, optional): The order the signer is required to sign in + + custom_fields (list of dict, optional): A list of custom fields. Required when a CustomField exists using text tags for form_fields_per_document. An item of the list should look like this: `{'name: value'}` cc_email_addresses (list of str, optional): A list of email addresses that should be CC'd - signing_redirect_url (str, optional): The URL you want the signer redirected to after they successfully sign. + signing_redirect_url (str, optional): The URL you want the signer redirected to after they successfully sign. + + requesting_redirect_url (str, optional): The URL you want the signer to be redirected to after the request has been sent. + + form_fields_per_document (str, optional): The fields that should appear on the document, expressed as a serialized JSON data structure which is a list of lists of the form fields. Please refer to the API reference of HelloSign for more details (https://www.hellosign.com/api/reference#SignatureRequest) + + metadata (dict, optional): Metadata to associate with the draft + + use_preexisting_fields (bool): Whether to use preexisting PDF fields - requesting_redirect_url (str, optional): The URL you want the signer to be redirected to after the request has been sent. + use_text_tags (bool, optional): Use text tags in the provided file(s) to create form fields - form_fields_per_document (str, optional): The fields that should appear on the document, expressed as a serialized JSON data structure which is a list of lists of the form fields. Please refer to the API reference of HelloSign for more details (https://www.hellosign.com/api/reference#SignatureRequest) + hide_text_tags (bool, optional): Hide text tag areas - metadata (dict, optional): Metadata to associate with the draft + skip_me_now (bool, optional): Disables the "Me (Now)" option for the document's preparer. Defaults to 0. - use_preexisting_fields (bool): Whether to use preexisting PDF fields + signing_options (dict, optional): Allows the requester to specify the types allowed for creating a signature. Defaults to account settings. - allow_decline (bool, optional): Allows signers to decline to sign a document if set to 1. Defaults to 0. + allow_decline (bool, optional): Allows signers to decline to sign a document if set to 1. Defaults to 0. + + allow_ccs (bool, optional): Specifies whether the user is allowed to provide email addresses to CC when sending the request. Defaults to False. Returns: An UnclaimedDraft object @@ -1144,54 +1557,80 @@ def create_embedded_unclaimed_draft(self, test_mode=False, client_id=None, is_fo 'signing_redirect_url': signing_redirect_url, 'requesting_redirect_url': requesting_redirect_url, 'signers': signers, + 'custom_fields': custom_fields, 'cc_email_addresses': cc_email_addresses, 'form_fields_per_document': form_fields_per_document, 'metadata': metadata, 'use_preexisting_fields': use_preexisting_fields, - 'allow_decline': allow_decline + 'use_text_tags': use_text_tags, + 'hide_text_tags': hide_text_tags, + 'skip_me_now': skip_me_now, + 'signing_options': signing_options, + 'allow_reassign': allow_reassign, + 'allow_decline': allow_decline, + 'allow_ccs': allow_ccs } return self._create_unclaimed_draft(**params) - def create_embedded_unclaimed_draft_with_template(self, test_mode=False, client_id=None, is_for_embedded_signing=False, template_id=None, template_ids=None, requester_email_address=None, title=None, subject=None, message=None, signers=None, ccs=None, signing_redirect_url=None, requesting_redirect_url=None, metadata=None, custom_fields=None, allow_decline=False): + def create_embedded_unclaimed_draft_with_template(self, test_mode=False, + client_id=None, is_for_embedded_signing=False, template_id=None, + template_ids=None, requester_email_address=None, title=None, + subject=None, message=None, signers=None, ccs=None, signing_redirect_url=None, + requesting_redirect_url=None, metadata=None, custom_fields=None, + files=None, file_urls=None, skip_me_now=False, allow_decline=False, + allow_reassign=False, signing_options=None): ''' Creates a new Draft to be used for embedded requesting Args: - test_mode (bool, optional): Whether this is a test, the signature request created from this draft will not be legally binding if set to True. Defaults to False. + test_mode (bool, optional): Whether this is a test, the signature request created from this draft will not be legally binding if set to True. Defaults to False. + + client_id (str): Client id of the app you're using to create this draft. Visit our embedded page to learn more about this parameter. + + template_id (str): The id of the Template to use when creating the Unclaimed Draft. Mutually exclusive with template_ids. + + template_ids (list of str): The ids of the Templates to use when creating the Unclaimed Draft. Mutually exclusive with template_id. + + requester_email_address (str): The email address of the user that should be designated as the requester of this draft, if the draft type is "request_signature." + + title (str, optional): The title you want to assign to the Unclaimed Draft - client_id (str): Client id of the app you're using to create this draft. Visit our embedded page to learn more about this parameter. + subject (str, optional): The subject in the email that will be sent to the signers - template_id (str): The id of the Template to use when creating the Unclaimed Draft. Mutually exclusive with template_ids. + message (str, optional): The custom message in the email that will be sent to the signers - template_ids (list of str): The ids of the Templates to use when creating the Unclaimed Draft. Mutually exclusive with template_id. + signers (list of dict): A list of signers, which each has the following attributes: - requester_email_address (str): The email address of the user that should be designated as the requester of this draft, if the draft type is "request_signature." + name (str): The name of the signer + email_address (str): Email address of the signer - title (str, optional): The title you want to assign to the Unclaimed Draft + ccs (list of str, optional): A list of email addresses that should be CC'd - subject (str, optional): The subject in the email that will be sent to the signers + signing_redirect_url (str, optional): The URL you want the signer redirected to after they successfully sign. - message (str, optional): The custom message in the email that will be sent to the signers + requesting_redirect_url (str, optional): The URL you want the signer to be redirected to after the request has been sent. - signers (list of dict): A list of signers, which each has the following attributes: + is_for_embedded_signing (bool, optional): The request created from this draft will also be signable in embedded mode if set to True. The default is False. - name (str): The name of the signer - email_address (str): Email address of the signer + metadata (dict, optional): Metadata to associate with the draft. Each request can include up to 10 metadata keys, with key names up to 40 characters long and values up to 500 characters long. - ccs (list of str, optional): A list of email addresses that should be CC'd + custom_fields (list of dict, optional): A list of custom fields. Required when a CustomField exists in the Template. An item of the list should look like this: `{'name: value'}` - signing_redirect_url (str, optional): The URL you want the signer redirected to after they successfully sign. + files (list of str): The uploaded file(s) to append to the Signature Request. - requesting_redirect_url (str, optional): The URL you want the signer to be redirected to after the request has been sent. + file_urls (list of str): URLs of the file for HelloSign to download to append to the Signature Request. Use either `files` or `file_urls` - is_for_embedded_signing (bool, optional): The request created from this draft will also be signable in embedded mode if set to True. The default is False. + skip_me_now (bool, optional): Disables the "Me (Now)" option for the document's preparer. Defaults to 0. - metadata (dict, optional): Metadata to associate with the draft. Each request can include up to 10 metadata keys, with key names up to 40 characters long and values up to 500 characters long. + allow_decline (bool, optional): Allows signers to decline to sign a document if set to 1. Defaults to 0. - custom_fields (list of dict, optional): A list of custom fields. Required when a CustomField exists in the Template. An item of the list should look like this: `{'name: value'}` + allow_reassign (bool, optional): Allows signers to reassign their signature requests to other signers if set to True. Defaults to False. - allow_decline (bool, optional): Allows signers to decline to sign a document if set to 1. Defaults to 0. + signing_options (dict, optional): Allows the requester to specify the types allowed for creating a signature. Defaults to account settings. + + Returns: + An UnclaimedDraft object ''' @@ -1218,13 +1657,67 @@ def create_embedded_unclaimed_draft_with_template(self, test_mode=False, client_ 'requesting_redirect_url': requesting_redirect_url, 'signers': signers, 'ccs': ccs, - 'custom_fields': custom_fields, 'metadata': metadata, - 'allow_decline': allow_decline + 'custom_fields': custom_fields, + 'files': files, + 'file_urls': file_urls, + 'skip_me_now': skip_me_now, + 'allow_decline': allow_decline, + 'allow_reassign': allow_reassign, + 'signing_options': signing_options } return self._create_embedded_unclaimed_draft_with_template(**params) + @api_resource(UnclaimedDraft) + def unclaimed_draft_edit_and_resend(self, signature_request_id, client_id, + test_mode=False, requesting_redirect_url=None, signing_redirect_url=None, + is_for_embedded_signing=False, requester_email_address=None): + ''' Updates a new signature request from an embedded request that can be edited prior to being sent. + + Args: + + signature_request_id (str): The id of the SignatureRequest to edit and resend + + client_id (str): Client id of the app you're using to create this draft. + + test_mode (bool, optional): Whether this is a test, the signature request created from this + draft will not be legally binding if set to True. Defaults to False. + + requesting_redirect_url (str, optional): The URL you want the signer to be redirected to after the request has been sent. + + signing_redirect_url (str, optional): The URL you want the signer redirected to after they successfully sign. + + is_for_embedded_signing (bool, optional): The request created from this draft will also be signable in + embedded mode if set to True. The default is False. + + requester_email_address (str, optional): The email address of the user that should be designated as the + requester of this draft, if the draft type is "request_signature." + + Returns: + A UnclaimedDraft object + + ''' + + self._check_required_fields({ + "client_id": client_id + } + ) + + data = { + 'client_id': client_id, + 'test_mode': self._boolean(test_mode), + 'requesting_redirect_url': requesting_redirect_url, + 'signing_redirect_url': signing_redirect_url, + 'is_for_embedded_signing': self._boolean(is_for_embedded_signing), + 'requester_email_address': requester_email_address + } + + data = HSFormat.strip_none_values(data) + + request = self._get_request() + return request.post(self.UNCLAIMED_DRAFT_EDIT_AND_RESEND_URL + signature_request_id, data=data) + # ---- OAUTH METHODS ------------------------------- def get_oauth_data(self, code, client_id, client_secret, state): @@ -1232,11 +1725,11 @@ def get_oauth_data(self, code, client_id, client_secret, state): Args: - code (str): Code returned by HelloSign for our callback url + code (str): Code returned by HelloSign for our callback url - client_id (str): Client id of the associated app + client_id (str): Client id of the associated app - client_secret (str): Secret token of the associated app + client_secret (str): Secret token of the associated app Returns: A HSAccessTokenAuth object @@ -1295,20 +1788,21 @@ def _get_request(self, auth=None): self.request.response_callback = self.response_callback return self.request - def _authenticate(self, email_address=None, password=None, api_key=None, access_token=None, access_token_type=None): + def _authenticate(self, email_address=None, password=None, api_key=None, + access_token=None, access_token_type=None): ''' Create authentication object to send requests Args: - email_address (str): Email address of the account to make the requests + email_address (str): Email address of the account to make the requests - password (str): Password of the account used with email address + password (str): Password of the account used with email address - api_key (str): API Key. You can find your API key in https://www.hellosign.com/home/myAccount/current_tab/integrations + api_key (str): API Key. You can find your API key in https://app.hellosign.com/home/myAccount/current_tab/integrations#api - access_token (str): OAuth access token + access_token (str): OAuth access token - access_token_type (str): Type of OAuth access token + access_token_type (str): Type of OAuth access token Raises: NoAuthMethod: If no authentication information found @@ -1353,49 +1847,56 @@ def _check_required_fields(self, fields=None, either_fields=None): raise HSException("One of the following fields is required: %s" % ", ".join(field.keys())) @api_resource(SignatureRequest) - def _send_signature_request(self, test_mode=False, client_id=None, files=None, file_urls=None, title=None, subject=None, message=None, signing_redirect_url=None, signers=None, cc_email_addresses=None, form_fields_per_document=None, use_text_tags=False, hide_text_tags=False, metadata=None, ux_version=None, allow_decline=False): + def _send_signature_request(self, test_mode=False, client_id=None, files=None, + file_urls=None, title=None, subject=None, message=None, + signing_redirect_url=None, signers=None, custom_fields=None, + cc_email_addresses=None, form_fields_per_document=None, use_text_tags=False, + hide_text_tags=False, metadata=None, allow_decline=False, allow_reassign=False, + signing_options=None): ''' To share the same logic between send_signature_request & send_signature_request_embedded functions Args: - test_mode (bool, optional): Whether this is a test, the signature request will not be legally binding if set to True. Defaults to False. - - client_id (str): Client id of the app you're using to create this embedded signature request. Visit the embedded page to learn more about this parameter (https://www.hellosign.com/api/embeddedSigningWalkthrough) + test_mode (bool, optional): Whether this is a test, the signature request will not be legally binding if set to True. Defaults to False. - files (list of str): The uploaded file(s) to send for signature + client_id (str): Client id of the app you're using to create this embedded signature request. Visit the embedded page to learn more about this parameter (https://www.hellosign.com/api/embeddedSigningWalkthrough) - file_urls (list of str): URLs of the file for HelloSign to download to send for signature. Use either `files` or `file_urls` + files (list of str): The uploaded file(s) to send for signature - title (str, optional): The title you want to assign to the SignatureRequest + file_urls (list of str): URLs of the file for HelloSign to download to send for signature. Use either `files` or `file_urls` - subject (str, optional): The subject in the email that will be sent to the signers + title (str, optional): The title you want to assign to the SignatureRequest - message (str, optional): The custom message in the email that will be sent to the signers + subject (str, optional): The subject in the email that will be sent to the signers - signing_redirect_url (str, optional): The URL you want the signer redirected to after they successfully sign + message (str, optional): The custom message in the email that will be sent to the signers - signers (list of dict): A list of signers, which each has the following attributes: + signing_redirect_url (str, optional): The URL you want the signer redirected to after they successfully sign - name (str): The name of the signer - email_address (str): Email address of the signer - order (str, optional): The order the signer is required to sign in - pin (str, optional): The 4- to 12-character access code that will secure this signer's signature page + signers (list of dict): A list of signers, which each has the following attributes: - cc_email_addresses (list, optional): A list of email addresses that should be CCed + name (str): The name of the signer + email_address (str): Email address of the signer + order (str, optional): The order the signer is required to sign in + pin (str, optional): The 4- to 12-character access code that will secure this signer's signature page - form_fields_per_document (str): The fields that should appear on the document, expressed as a serialized JSON data structure which is a list of lists of the form fields. Please refer to the API reference of HelloSign for more details (https://www.hellosign.com/api/reference#SignatureRequest) + cc_email_addresses (list, optional): A list of email addresses that should be CCed - use_text_tags (bool, optional): Use text tags in the provided file(s) to create form fields + form_fields_per_document (str): The fields that should appear on the document, expressed as a serialized JSON data structure which is a list of lists of the form fields. Please refer to the API reference of HelloSign for more details (https://www.hellosign.com/api/reference#SignatureRequest) - hide_text_tags (bool, optional): Hide text tag areas + use_text_tags (bool, optional): Use text tags in the provided file(s) to create form fields - metadata (dict, optional): Metadata to associate with the signature request + hide_text_tags (bool, optional): Hide text tag areas - ux_version (int): UX version, either 1 (default) or 2. + metadata (dict, optional): Metadata to associate with the signature request allow_decline (bool, optional); Allows signers to decline to sign a document if set to 1. Defaults to 0. + allow_reassign (bool, optional): Allows signers to reassign their signature requests to other signers if set to True. Defaults to False. + + signing_options (dict, optional): Allows the requester to specify the types allowed for creating a signature. Defaults to account settings. + Returns: A SignatureRequest object @@ -1426,12 +1927,11 @@ def _send_signature_request(self, test_mode=False, client_id=None, files=None, f "form_fields_per_document": form_fields_per_document, "use_text_tags": self._boolean(use_text_tags), "hide_text_tags": self._boolean(hide_text_tags), - "allow_decline": self._boolean(allow_decline) + "allow_decline": self._boolean(allow_decline), + "allow_reassign": self._boolean(allow_reassign), + "signing_options": json.dumps(signing_options) } - if ux_version is not None: - payload['ux_version'] = ux_version - # remove attributes with none value payload = HSFormat.strip_none_values(payload) @@ -1451,47 +1951,54 @@ def _send_signature_request(self, test_mode=False, client_id=None, files=None, f return response @api_resource(SignatureRequest) - def _send_signature_request_with_template(self, test_mode=False, client_id=None, template_id=None, template_ids=None, title=None, subject=None, message=None, signing_redirect_url=None, signers=None, ccs=None, custom_fields=None, metadata=None, ux_version=None, allow_decline=False): + def _send_signature_request_with_template(self, test_mode=False, client_id=None, + template_id=None, template_ids=None, title=None, subject=None, message=None, + signing_redirect_url=None, signers=None, ccs=None, custom_fields=None, + metadata=None, allow_decline=False, files=None, file_urls=None, signing_options=None): ''' To share the same logic between send_signature_request_with_template and send_signature_request_embedded_with_template Args: - test_mode (bool, optional): Whether this is a test, the signature request will not be legally binding if set to True. Defaults to False. + test_mode (bool, optional): Whether this is a test, the signature request will not be legally binding if set to True. Defaults to False. - client_id (str): Client id of the app you're using to create this embedded signature request. Visit the embedded page to learn more about this parameter (https://www.hellosign.com/api/embeddedSigningWalkthrough) + client_id (str): Client id of the app you're using to create this embedded signature request. Visit the embedded page to learn more about this parameter (https://app.hellosign.com/api/embeddedSigningWalkthrough) - template_id (str): The id of the Template to use when creating the SignatureRequest. Mutually exclusive with template_ids. + template_id (str): The id of the Template to use when creating the SignatureRequest. Mutually exclusive with template_ids. - template_ids (list): The ids of the Templates to use when creating the SignatureRequest. Mutually exclusive with template_id. + template_ids (list): The ids of the Templates to use when creating the SignatureRequest. Mutually exclusive with template_id. - title (str, optional): The title you want to assign to the SignatureRequest + title (str, optional): The title you want to assign to the SignatureRequest - subject (str, optional): The subject in the email that will be sent to the signers + subject (str, optional): The subject in the email that will be sent to the signers - message (str, optional): The custom message in the email that will be sent to the signers + message (str, optional): The custom message in the email that will be sent to the signers - signing_redirect_url (str, optional): The URL you want the signer redirected to after they successfully sign. + signing_redirect_url (str, optional): The URL you want the signer redirected to after they successfully sign. - signers (list of dict): A list of signers, which each has the following attributes: + signers (list of dict): A list of signers, which each has the following attributes: - role_name (str): Role the signer is assigned to - name (str): The name of the signer - email_address (str): Email address of the signer - pin (str, optional): The 4- to 12-character access code that will secure this signer's signature page + role_name (str): Role the signer is assigned to + name (str): The name of the signer + email_address (str): Email address of the signer + pin (str, optional): The 4- to 12-character access code that will secure this signer's signature page - ccs (list of dict, optional): The email address of the CC filling the role of RoleName. Required when a CC role exists for the Template. Each dict has the following attributes: + ccs (list of dict, optional): The email address of the CC filling the role of RoleName. Required when a CC role exists for the Template. Each dict has the following attributes: - role_name (str): CC role name - email_address (str): CC email address + role_name (str): CC role name + email_address (str): CC email address custom_fields (list of dict, optional): A list of custom fields. Required when a CustomField exists in the Template. An item of the list should look like this: `{'name: value'}` - metadata (dict, optional): Metadata to associate with the signature request + metadata (dict, optional): Metadata to associate with the signature request + + allow_decline (bool, optional): Allows signers to decline to sign a document if set to 1. Defaults to 0. + + files (list of str): The uploaded file(s) to append to the Signature Request. - ux_version (int): UX version, either 1 (default) or 2. + file_urls (list of str): URLs of the file for HelloSign to download to append to the Signature Request. Use either `files` or `file_urls` - allow_decline (bool, optional): Allows signers to decline to sign a document if set to 1. Defaults to 0. + signing_options (dict, optional): Allows the requester to specify the types allowed for creating a signature. Defaults to account settings. Returns: A SignatureRequest object @@ -1516,6 +2023,12 @@ def _send_signature_request_with_template(self, test_mode=False, client_id=None, for i in range(len(template_ids)): template_ids_payload["template_ids[%s]" % i] = template_ids[i] + # Files + files_payload = HSFormat.format_file_params(files) + + # File URLs + file_urls_payload = HSFormat.format_file_url_params(file_urls) + payload = { "test_mode": self._boolean(test_mode), "client_id": client_id, @@ -1524,12 +2037,10 @@ def _send_signature_request_with_template(self, test_mode=False, client_id=None, "subject": subject, "message": message, "signing_redirect_url": signing_redirect_url, - "allow_decline": self._boolean(allow_decline) + "allow_decline": self._boolean(allow_decline), + "signing_options": json.dumps(signing_options) } - if ux_version is not None: - payload['ux_version'] = ux_version - # remove attributes with empty value payload = HSFormat.strip_none_values(payload) @@ -1543,55 +2054,77 @@ def _send_signature_request_with_template(self, test_mode=False, client_id=None, data.update(custom_fields_payload) data.update(metadata_payload) data.update(template_ids_payload) + data.update(file_urls_payload) request = self._get_request() - response = request.post(url, data=data) + response = request.post(url, data=data, files=files_payload) return response @api_resource(UnclaimedDraft) - def _create_unclaimed_draft(self, test_mode=False, client_id=None, is_for_embedded_signing=False, requester_email_address=None, files=None, file_urls=None, draft_type=None, subject=None, message=None, signers=None, cc_email_addresses=None, signing_redirect_url=None, requesting_redirect_url=None, form_fields_per_document=None, metadata=None, use_preexisting_fields=False, allow_decline=False): + def _create_unclaimed_draft(self, test_mode=False, client_id=None, + is_for_embedded_signing=False, requester_email_address=None, files=None, + file_urls=None, draft_type=None, subject=None, message=None, signers=None, + custom_fields=None, cc_email_addresses=None, signing_redirect_url=None, + requesting_redirect_url=None, form_fields_per_document=None, metadata=None, + use_preexisting_fields=False, use_text_tags=False, hide_text_tags=False, + skip_me_now=False, allow_reassign=False, allow_decline=False, + signing_options=None, allow_ccs=False): ''' Creates a new Draft that can be claimed using the claim URL Args: - test_mode (bool, optional): Whether this is a test, the signature request created from this draft will not be legally binding if set to True. Defaults to False. + test_mode (bool, optional): Whether this is a test, the signature request created from this draft will not be legally binding if set to True. Defaults to False. + + client_id (str): Client id of the app used to create the embedded draft. + + is_for_embedded_signing (bool): Whether this is for embedded signing on top of being for embedded requesting. + + requester_email_address (str): Email address of the requester when creating a draft for embedded requesting. + + files (list of str): The uploaded file(s) to send for signature. + + file_urls (list of str): URLs of the file for HelloSign to download to send for signature. Use either `files` or `file_urls` + + draft_type (str): The type of unclaimed draft to create. Use "send_document" to create a claimable file, and "request_signature" for a claimable signature request. If the type is "request_signature" then signers name and email_address are not optional. - client_id (str): Client id of the app used to create the embedded draft. + subject (str, optional): The subject in the email that will be sent to the signers - is_for_embedded_signing (bool): Whether this is for embedded signing on top of being for embedded requesting. + message (str, optional): The custom message in the email that will be sent to the signers - requester_email_address (str): Email address of the requester when creating a draft for embedded requesting. + signers (list of dict): A list of signers, which each has the following attributes: - files (list of str): The uploaded file(s) to send for signature. + name (str): The name of the signer + email_address (str): Email address of the signer + order (str, optional): The order the signer is required to sign in - file_urls (list of str): URLs of the file for HelloSign to download to send for signature. Use either `files` or `file_urls` + custom_fields (list of dict, optional): A list of custom fields. Required when a CustomField exists using text tags or form_fields_per_document. An item of the list should look like this: `{'name: value'}` - draft_type (str): The type of unclaimed draft to create. Use "send_document" to create a claimable file, and "request_signature" for a claimable signature request. If the type is "request_signature" then signers name and email_address are not optional. + cc_email_addresses (list of str, optional): A list of email addresses that should be CC'd + + signing_redirect_url (str, optional): The URL you want the signer redirected to after they successfully sign. - subject (str, optional): The subject in the email that will be sent to the signers + requesting_redirect_url (str, optional): The URL you want the signer to be redirected to after the request has been sent. - message (str, optional): The custom message in the email that will be sent to the signers + form_fields_per_document (str): The fields that should appear on the document, expressed as a serialized JSON data structure which is a list of lists of the form fields. Please refer to the API reference of HelloSign for more details (https://www.hellosign.com/api/reference#SignatureRequest). - signers (list of dict): A list of signers, which each has the following attributes: + metadata (dict, optional): Metadata to associate with the draft - name (str): The name of the signer - email_address (str): Email address of the signer - order (str, optional): The order the signer is required to sign in + use_preexisting_fields (bool): Whether to use preexisting PDF fields - cc_email_addresses (list of str, optional): A list of email addresses that should be CC'd + use_text_tags (bool, optional): Use text tags in the provided file(s) to create form fields - signing_redirect_url (str, optional): The URL you want the signer redirected to after they successfully sign. + hide_text_tags (bool, optional): Hide text tag areas - requesting_redirect_url (str, optional): The URL you want the signer to be redirected to after the request has been sent. + skip_me_now (bool, optional): Disables the "Me (Now)" option for the document's preparer. Defaults to 0. - form_fields_per_document (str): The fields that should appear on the document, expressed as a serialized JSON data structure which is a list of lists of the form fields. Please refer to the API reference of HelloSign for more details (https://www.hellosign.com/api/reference#SignatureRequest). + allow_reassign (bool, optional): Allows signers to reassign their signature requests to other signers if set to True. Defaults to False. - metadata (dict, optional): Metadata to associate with the draft + allow_decline (bool, optional): Allows signers to decline to sign a document if set to 1. Defaults to 0. - use_preexisting_fields (bool): Whether to use preexisting PDF fields + signing_options (dict, optional): Allows the requester to specify the types allowed for creating a signature. Defaults to account settings. - allow_decline (bool, optional): Allows signers to decline to sign a document if set to 1. Defaults to 0. + allow_ccs (bool, optional): Specifies whether the user is allowed to provide email addresses to CC when sending the request. Defaults to False. Returns: An UnclaimedDraft object @@ -1616,6 +2149,9 @@ def _create_unclaimed_draft(self, test_mode=False, client_id=None, is_for_embedd # CCs cc_email_addresses_payload = HSFormat.format_param_list(cc_email_addresses, 'cc_email_addresses') + # Custom fields + custom_fields_payload = HSFormat.format_custom_fields(custom_fields) + # Metadata metadata_payload = HSFormat.format_single_dict(metadata, 'metadata') @@ -1627,7 +2163,13 @@ def _create_unclaimed_draft(self, test_mode=False, client_id=None, is_for_embedd "signing_redirect_url": signing_redirect_url, "form_fields_per_document": form_fields_per_document, "use_preexisting_fields": self._boolean(use_preexisting_fields), - "allow_decline": self._boolean(allow_decline) + "use_text_tags": self._boolean(use_text_tags), + "hide_text_tags": self._boolean(hide_text_tags), + "skip_me_now": self._boolean(skip_me_now), + "allow_reassign": self._boolean(allow_reassign), + "allow_decline": self._boolean(allow_decline), + "signing_options": json.dumps(signing_options), + "allow_ccs": self._boolean(allow_ccs) } url = self.UNCLAIMED_DRAFT_CREATE_URL @@ -1646,6 +2188,7 @@ def _create_unclaimed_draft(self, test_mode=False, client_id=None, is_for_embedd data = payload.copy() data.update(signers_payload) + data.update(custom_fields_payload) data.update(cc_email_addresses_payload) data.update(file_urls_payload) data.update(metadata_payload) @@ -1663,11 +2206,11 @@ def _add_remove_user_template(self, url, template_id, account_id=None, email_add Args: - template_id (str): The id of the template + template_id (str): The id of the template - account_id (str): ID of the account to add/remove access to/from + account_id (str): ID of the account to add/remove access to/from - email_address (str): The email_address of the account to add/remove access to/from + email_address (str): The email_address of the account to add/remove access to/from Raises: HSException: If no email address or account_id specified @@ -1704,9 +2247,9 @@ def _add_remove_team_member(self, url, email_address=None, account_id=None): Args: - email_address (str): Email address of the Account to add/remove + email_address (str): Email address of the Account to add/remove - account_id (str): ID of the Account to add/remove + account_id (str): ID of the Account to add/remove Returns: A Team object @@ -1732,7 +2275,10 @@ def _add_remove_team_member(self, url, email_address=None, account_id=None): return response @api_resource(Template) - def _create_embedded_template_draft(self, client_id, signer_roles, test_mode=False, files=None, file_urls=None, title=None, subject=None, message=None, cc_roles=None, merge_fields=None, use_preexisting_fields=False): + def _create_embedded_template_draft(self, client_id, signer_roles, test_mode=False, + files=None, file_urls=None, title=None, subject=None, message=None, + cc_roles=None, merge_fields=None, skip_me_now=False, + use_preexisting_fields=False, metadata=None, allow_reassign=False, allow_ccs=False): ''' Helper method for creating embedded template drafts. See public function for params. ''' @@ -1745,7 +2291,11 @@ def _create_embedded_template_draft(self, client_id, signer_roles, test_mode=Fal 'title': title, 'subject': subject, 'message': message, - 'use_preexisting_fields': self._boolean(use_preexisting_fields) + 'skip_me_now': self._boolean(skip_me_now), + 'use_preexisting_fields': self._boolean(use_preexisting_fields), + 'allow_reassign': self._boolean(allow_reassign), + 'allow_ccs': + self._boolean(allow_ccs) } # Prep files @@ -1760,6 +2310,8 @@ def _create_embedded_template_draft(self, client_id, signer_roles, test_mode=Fal merge_fields_payload = { 'merge_fields': json.dumps(merge_fields) } + # Prep Metadata + metadata_payload = HSFormat.format_single_dict(metadata, 'metadata') # Assemble data for sending data = {} @@ -1767,6 +2319,7 @@ def _create_embedded_template_draft(self, client_id, signer_roles, test_mode=Fal data.update(file_urls_payload) data.update(signer_roles_payload) data.update(ccs_payload) + data.update(metadata_payload) if (merge_fields is not None): data.update(merge_fields_payload) data = HSFormat.strip_none_values(data) @@ -1778,7 +2331,13 @@ def _create_embedded_template_draft(self, client_id, signer_roles, test_mode=Fal return response @api_resource(UnclaimedDraft) - def _create_embedded_unclaimed_draft_with_template(self, test_mode=False, client_id=None, is_for_embedded_signing=False, template_id=None, template_ids=None, requester_email_address=None, title=None, subject=None, message=None, signers=None, ccs=None, signing_redirect_url=None, requesting_redirect_url=None, metadata=None, custom_fields=None, allow_decline=False): + def _create_embedded_unclaimed_draft_with_template(self, test_mode=False, + client_id=None, is_for_embedded_signing=False, template_id=None, + template_ids=None, requester_email_address=None, title=None, + subject=None, message=None, signers=None, ccs=None, + signing_redirect_url=None, requesting_redirect_url=None, metadata=None, + custom_fields=None, files=None, file_urls=None, skip_me_now=False, + allow_decline=False, allow_reassign=False, signing_options=None): ''' Helper method for creating unclaimed drafts from templates See public function for params. ''' @@ -1795,7 +2354,10 @@ def _create_embedded_unclaimed_draft_with_template(self, test_mode=False, client "message": message, "signing_redirect_url": signing_redirect_url, "requesting_redirect_url": requesting_redirect_url, - "allow_decline": self._boolean(allow_decline) + "skip_me_now": self._boolean(skip_me_now), + "allow_decline": self._boolean(allow_decline), + "allow_reassign": self._boolean(allow_reassign), + "signing_options": json.dumps(signing_options) } #format multi params @@ -1805,6 +2367,12 @@ def _create_embedded_unclaimed_draft_with_template(self, test_mode=False, client metadata_payload = HSFormat.format_single_dict(metadata, 'metadata') custom_fields_payload = HSFormat.format_custom_fields(custom_fields) + # Files + files_payload = HSFormat.format_file_params(files) + + # File URLs + file_urls_payload = HSFormat.format_file_url_params(file_urls) + #assemble payload data = {} data.update(payload) @@ -1813,11 +2381,12 @@ def _create_embedded_unclaimed_draft_with_template(self, test_mode=False, client data.update(ccs_payload) data.update(metadata_payload) data.update(custom_fields_payload) + data.update(file_urls_payload) data = HSFormat.strip_none_values(data) #send call url = self.UNCLAIMED_DRAFT_CREATE_EMBEDDED_WITH_TEMPLATE_URL request = self._get_request() - response = request.post(url, data=data) + response = request.post(url, data=data, files=files_payload) return response diff --git a/hellosign_sdk/resource/__init__.py b/hellosign_sdk/resource/__init__.py index 11669bd..34a3bb0 100644 --- a/hellosign_sdk/resource/__init__.py +++ b/hellosign_sdk/resource/__init__.py @@ -1,4 +1,5 @@ from .account import Account +from .apiapp import ApiApp from .embedded import Embedded from .signature import Signature from .signature_request import SignatureRequest @@ -8,4 +9,4 @@ from .resource_list import ResourceList from .resource import Resource -__all__ = [Account, Embedded, Signature, SignatureRequest, Team, Template, UnclaimedDraft, Resource, ResourceList] +__all__ = [Account, ApiApp, Embedded, Signature, SignatureRequest, Team, Template, UnclaimedDraft, Resource, ResourceList] diff --git a/hellosign_sdk/resource/account.py b/hellosign_sdk/resource/account.py index d7abc28..2a564a4 100644 --- a/hellosign_sdk/resource/account.py +++ b/hellosign_sdk/resource/account.py @@ -3,9 +3,9 @@ # # The MIT License (MIT) -# +# # Copyright (C) 2014 hellosign.com -# +# # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights @@ -47,8 +47,8 @@ class Account(Resource): callback_url (str): The URL that HelloSign events will be POSTed to - role_code (str): The membership role for the team. - a = Admin, + role_code (str): The membership role for the team. + a = Admin, m = Member d = Developer diff --git a/hellosign_sdk/resource/apiapp.py b/hellosign_sdk/resource/apiapp.py new file mode 100644 index 0000000..2350881 --- /dev/null +++ b/hellosign_sdk/resource/apiapp.py @@ -0,0 +1,95 @@ +from .resource import Resource + +# +# The MIT License (MIT) +# +# Copyright (C) 2014 hellosign.com +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# + + +class ApiApp(Resource): + ''' Contains information about an API App. + + Attributes: + client_id (str): The API App's Client ID + + created_at (int): Unix timestamp of when the API App was created + + name (str): The name of the API App + + domain (str): The domain name associated with the API App + + callback_url (str) : The URL that HelloSign events will be POSTed to + + is_approved (bool): Indicates if the API App is approved + + owner_account (dict): Information about the API App owner + + account_id (str): The id of the Account + + email_address (str): The email address associated with the Account + + options (dict): Options that override the Account settings + + can_insert_everywhere (bool): Denotes if signers can "Insert Everywhere" when + signing a document + + oauth (dict): Information about the API App's OAuth properties. Null if OAuth is + not configured for the API App. + + callback_url (str): The URL that HelloSign OAuth events will be POSTed to + + secret (str): The API App's OAuth secret + + scopes (list): List of the API App's OAuth scopes + + charges_users (bool): Indicates whether the API App or the authorized user + is billed for OAuth requests. + + white_labeling_options (dict): Customization options for the API App's signer page + + Examples: + To print the client_id + + >>> from hsclient import HSClient + >>> client = HSClient() + >>> app = client.get_api_app_info() + >>> print app.client_id + + ''' + + def __init__(self, jsonstr=None, key=None, warnings=None): + ''' Initialization of the object + + Args: + jsonstr (str): a raw JSON string that is returned by a request. + We store all the data in `self.json_data` and use `__getattr__` + and `__setattr__` to make the data accessible like attributes + of the object + key (str): Optional key to use with jsonstr. If `key` exists, we'll + load the data of `jsonstr[key]` instead of the whole `jsonstr` + warnings (list): List of associated warnings + ''' + super(ApiApp, self).__init__(jsonstr, key, warnings) + + def __str__(self): + ''' Return a string representation of this Account ''' + return 'ApiApp %s (%s)' % (self.name, self.client_id) diff --git a/hellosign_sdk/resource/resource.py b/hellosign_sdk/resource/resource.py index f6fbc16..ec21371 100644 --- a/hellosign_sdk/resource/resource.py +++ b/hellosign_sdk/resource/resource.py @@ -2,9 +2,9 @@ # # The MIT License (MIT) -# +# # Copyright (C) 2014 hellosign.com -# +# # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights @@ -24,10 +24,11 @@ # SOFTWARE. # + class Resource(object): ''' An abstract class to represent some objects used by our SDK such as - Account, SignatureRequest, Template, Team, UnclaimedDraft, Embedded. + Account, ApiApp, SignatureRequest, Template, Team, UnclaimedDraft, Embedded. These objects share the same way of storing data. Information and settings are stored physically in `self.json_data`, and diff --git a/hellosign_sdk/resource/resource_list.py b/hellosign_sdk/resource/resource_list.py index 31cb888..f399691 100644 --- a/hellosign_sdk/resource/resource_list.py +++ b/hellosign_sdk/resource/resource_list.py @@ -2,9 +2,9 @@ # # The MIT License (MIT) -# +# # Copyright (C) 2014 hellosign.com -# +# # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights @@ -30,7 +30,8 @@ class ResourceList(list): items_keys = { 'SignatureRequest': 'signature_requests', - 'Template': 'templates' + 'Template': 'templates', + 'ApiApp': 'api_apps' } page = 0 diff --git a/hellosign_sdk/utils/hsformat.py b/hellosign_sdk/utils/hsformat.py index d110c1f..bbc47fd 100644 --- a/hellosign_sdk/utils/hsformat.py +++ b/hellosign_sdk/utils/hsformat.py @@ -1,8 +1,8 @@ # # The MIT License (MIT) -# +# # Copyright (C) 2014 hellosign.com -# +# # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights @@ -59,6 +59,15 @@ def format_file_url_params(file_urls): file_urls_payload["file_url[" + str(idx) + "]"] = fileurl return file_urls_payload + @staticmethod + def format_logo_params(logo): + ''' + Utility method for formatting custom logo parameters for API Apps + ''' + logo_payload = {} + logo_payload["custom_logo_file"] = open(logo, 'rb') + return logo_payload + @staticmethod def format_param_list(listed_params, output_name): ''' @@ -82,13 +91,13 @@ def format_dict_list(list_of_dicts, output_name, key=None): and outputs: signers[0][name] : val1 signers[0][email] : val2 - ... + ... Args: list_of_dicts (list of dicts) - the list to format - + output_name (str) - the parameter name to prepend to each key - + key (str, optional) - Used for substituting a key present in the dictionaries for the index. The above might become signers['Lawyer']['name'] instead of using a numerical index if the key "role_name" was specified. ''' @@ -126,7 +135,16 @@ def format_custom_fields(list_of_custom_fields): output_payload["custom_fields[" + key + "]"] = value return output_payload + @staticmethod + def format_signing_options(listed_params): + ''' + Utility method for formatting signing options for api consumption + Args: + listed_params (list of values) - the list to format + ''' + return listed_params + @staticmethod def strip_none_values(dictionary): if dictionary: - return dict((key, value) for (key, value) in dictionary.items() if value) \ No newline at end of file + return dict((key, value) for (key, value) in dictionary.items() if value) diff --git a/hellosign_sdk/utils/request.py b/hellosign_sdk/utils/request.py index 517ce8f..95fc68e 100644 --- a/hellosign_sdk/utils/request.py +++ b/hellosign_sdk/utils/request.py @@ -5,9 +5,9 @@ # # The MIT License (MIT) -# +# # Copyright (C) 2014 hellosign.com -# +# # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights @@ -111,7 +111,7 @@ def get_file(self, url, path_or_file=None, headers=None, filename=None): return True def get(self, url, headers=None, parameters=None, get_json=True): - ''' Send a GET request with custome headers and parameters + ''' Send a GET request with custom headers and parameters Args: url (str): URL to send the request to @@ -166,9 +166,31 @@ def post(self, url, data=None, files=None, headers=None, get_json=True): response = requests.post(url, headers=self.headers, data=data, auth=self.auth, files=files, verify=self.verify_ssl) json_response = self._process_json_response(response) - + return json_response if get_json is True else response + def delete(self, url, headers=None): + ''' Make POST request to a url to delete an API App or Template + + Args: + url (str): URL to send the request to + headers (str, optional): custom headers + + Returns: + None + + ''' + + if self.debug: + print("POST: %s, headers=%s" % (url, headers)) + + self.headers = self._get_default_headers() + if headers is not None: + self.headers.update(headers) + + response = requests.delete(url, headers=self.headers, auth=self.auth, verify=self.verify_ssl) + + return response #### HELPERS ######################################## @@ -200,7 +222,7 @@ def _process_json_response(self, response): ''' Process a given response ''' json_response = self._get_json_response(response) - + if self.response_callback is not None: json_response = self.response_callback(json_response) response._content = json.dumps(json_response) @@ -225,7 +247,7 @@ def _check_error(self, response, json_response=None): Returns: True if status code is not error code - + ''' # If status code is 4xx or 5xx, that should be an error @@ -246,7 +268,7 @@ def _check_warnings(self, json_response): Args: json_response (dict): JSON response - + ''' self.warnings = None diff --git a/setup.py b/setup.py index 3b8c6aa..6ac3162 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ def readme(): setup( name='hellosign-python-sdk', - version='3.8.7', + version='3.8.8', description="A Python wrapper for the HelloSign API (http://www.hellosign.com/api)", long_description=readme(), classifiers=[ @@ -20,7 +20,7 @@ def readme(): 'Programming Language :: Python :: 2.7' ], keywords='hellosign python sdk', - url='https://github.com/HelloFax/hellosign-python-sdk', + url='https://github.com/hellosign/hellosign-python-sdk', author='HelloSign', author_email='apisupport@hellosign.com', license='MIT',