def documents(self): """ Retrieve the :class:`AgreementDocuments <pyEchosign.classes.documents.AgreementDocument>` associated with this agreement. If the files have not already been retrieved, this will result in an additional request to the API. Returns: A list of :class:`AgreementDocument <pyEchosign.classes.documents.AgreementDocument>` """ # If _documents is None, no (successful) API call has been made to retrieve them if self._documents is None: url = self.account.api_access_point + 'agreements/{}/documents'.format( self.echosign_id) r = requests.get(url, headers=get_headers(self.account.access_token)) # Raise Exception if there was an error check_error(r) try: data = r.json() except ValueError: raise ApiError( 'Unexpected response from Echosign API: Status {} - {}'. format(r.status_code, r.content)) else: self._documents = [] # Take both sections of documents from the response and turn into AgreementDocuments documents = self._document_data_to_document( data.get('documents', [])) supporting_documents = self._document_data_to_document( data.get('supportingDocuments', [])) self._documents = documents + supporting_documents return self._documents
def delete(self): """ Deletes the LibraryDocument from Echosign. It will not be visible on the Manage page. """ url = self.account.api_access_point + 'libraryDocuments/{}'.format( self.echosign_id) headers = get_headers(self.account.access_token) r = requests.delete(url, headers=headers) check_error(r)
def audit_trail_file(self): # type: () -> BytesIO """ The PDF file of the audit trail.""" endpoint = '{}agreements/{}/auditTrail'.format( self.account.api_access_point, self.echosign_id) response = requests.get(endpoint, headers=get_headers(self.account.access_token)) check_error(response) return BytesIO(response.content)
def combined_document(self): # type: () -> BytesIO """ The PDF file containing all documents within this agreement.""" endpoint = '{}agreements/{}/combinedDocument'.format( self.account.api_access_point, self.echosign_id) response = requests.get(endpoint, headers=get_headers(self.account.access_token)) check_error(response) return BytesIO(response.content)
def get_library_documents(self): """ Gets all Library Documents for the EchosignAccount Returns: A list of :class:`Agreement <pyEchosign.classes.library_document.LibraryDocument>` objects """ url = self.api_access_point + 'libraryDocuments' headers = get_headers(self.access_token) r = requests.get(url, headers=headers) response_data = r.json() check_error(r) return LibraryDocument.json_to_agreements(self, response_data)
def get_form_data(self): """ Retrieves the form data for this agreement as CSV. Returns: StringIO """ url = '{}agreements/{}/formData'.format(self.account.api_access_point, self.echosign_id) r = requests.get(url, headers=self.account.headers()) check_error(r) return StringIO(r.text)
def retrieve_complete_document(self): """ Retrieves the remaining data for the LibraryDocument, such as locale, status, and security options. """ url = self.account.api_access_point + 'libraryDocuments/{}'.format( self.echosign_id) headers = get_headers(self.account.access_token) r = requests.get(url, headers=headers) check_error(r) response_data = r.json() self._locale = response_data.get('locale') self._status = response_data.get('status') self._security_options = response_data.get('securityOptions') self.fully_retrieved = True
def send_reminder(self, comment=''): """ Send a reminder for an agreement to the participants. Args: comment: An optional comment that will be sent with the reminder """ url = self.account.api_access_point + 'reminders' payload = dict(agreementId=self.echosign_id, comment=comment) r = requests.post(url, data=json.dumps(payload), headers=self.account.headers()) check_error(r)
def __init__(self, account, file_name, file, mime_type=None): # type: (EchosignAccount, str, Union[IOBase, FileIO, BytesIO], str) -> None self.file_name = file_name self.file = file self.mime_type = mime_type self.document_id = None self.expiration_date = None # With file data provided, make request to Echosign API for transient document url = account.api_access_point + 'transientDocuments' # Create post_data file_tuple = (file_name, file) # Only add the mime type if provided if mime_type is not None: file_tuple = file_tuple + (mime_type, ) files = dict(File=file_tuple) r = requests.post(url, headers=get_headers(account.access_token, content_type=None), files=files) if response_success(r): log.debug('Request to create document {} successful.'.format( self.file_name)) response_data = r.json() self.document_id = response_data.get('transientDocumentId', None) # If there was no document ID, something went wrong if self.document_id is None: log.error( 'Did not receive a transientDocumentId from Echosign. Received: {}' .format(r.content)) raise ApiError( 'Did not receive a Transient Document ID from Echosign') else: today = arrow.now() # Document will expire in 7 days from creation self.expiration_date = today.replace(days=+7).datetime else: try: log.error( 'Error encountered creating document {}. Received message: {}' .format(self.file_name, r.content)) finally: check_error(r)
def get_agreements(self, query=None): # type: (str) -> List[Agreement] """ Gets all agreements for the EchosignAccount Keyword Args: query: (str) A search query to filter results by Returns: A list of :class:`Agreement <pyEchosign.classes.agreement.Agreement>` objects """ url = self.api_access_point + 'agreements' params = dict() if query is not None: params.update({'query': query}) r = requests.get(url, headers=get_headers(self.access_token), params=params) check_error(r) response_body = r.json() return Agreement.json_to_agreements(self, response_body)
def cancel(self): """ Cancels the agreement on Echosign. Agreement will still be visible in the Manage page. """ url = '{}agreements/{}/status'.format(self.account.api_access_point, self.echosign_id) body = dict(value='CANCEL') r = requests.put(url, headers=get_headers(self.account.access_token), data=json.dumps(body)) if response_success(r): log.debug('Request to cancel agreement {} successful.'.format( self.echosign_id)) else: try: log.error( 'Error encountered cancelling agreement {}. Received message: {}' .format(self.echosign_id, r.content)) finally: check_error(r)
def delete(self): """ Deletes the agreement on Echosign. Agreement will not be visible in the Manage page. Note: This action requires the 'agreement_retention' scope, which doesn't appear to be actually available via OAuth """ url = self.account.api_access_point + 'agreements/' + self.echosign_id r = requests.delete(url, headers=get_headers(self.account.access_token)) if response_success(r): log.debug('Request to delete agreement {} successful.'.format( self.echosign_id)) else: try: log.error( 'Error encountered deleting agreement {}. Received message:{}' .format(self.echosign_id, r.content)) finally: check_error(r)
def send(self, recipients, agreement_name=None, ccs=None, days_until_signing_deadline=0, external_id='', signature_flow=SignatureFlow.SEQUENTIAL, message='', merge_fields=None): # type: (List[User], str, list, int, str, Agreement.SignatureFlow, str, List[Dict[str, str]]) -> None """ Sends this agreement to Echosign for signature Args: agreement_name: A string for the document name which will appear in the Echosign Manage page, the email to recipients, etc. Defaults to the name for the Agreement. recipients: A list of :class:`Users <pyEchosign.classes.users.User>`. The order which they are provided in the list determines the order in which they sign. ccs: (optional) A list of email addresses to be CC'd on the Echosign agreement emails (document sent, document fully signed, etc) days_until_signing_deadline: (optional) "The number of days that remain before the document expires. You cannot sign the document after it expires" Defaults to 0, for no expiration. external_id: (optional) "A unique identifier for your transaction... You can use the ExternalID to search for your transaction through [the] API" signature_flow: (optional) (SignatureFlow): The routing style of this agreement, defaults to Sequential. merge_fields: (optional) A list of dictionaries, with each one providing the 'field_name' and 'default_value' keys. The field name maps to the field on the document, and the default value is what will be placed inside. message: (optional) A message which will be displayed to recipients of the agreement Returns: A namedtuple representing the information received back from the API. Contains the following attributes `agreement_id` *"The unique identifier that can be used to query status and download signed documents"* `embedded_code` *"Javascript snippet suitable for an embedded page taking a user to a URL"* `expiration` *"Expiration date for autologin. This is based on the user setting, API_AUTO_LOGIN_LIFETIME"* `url` *"Standalone URL to direct end users to"* Raises: ApiError: If the API returns an error, such as a 403. The exact response from the API is provided. """ if agreement_name is None: agreement_name = self.name if ccs is None: ccs = [] security_options = dict(passwordProtection="NONE", kbaProtection="NONE", webIdentityProtection="NONE", protectOpen=False, internalPassword="", externalPassword="", openPassword="") files_data = [{ 'transientDocumentId': file.document_id } for file in self.files] if merge_fields is None: merge_fields = [] converted_merge_fields = [ dict(fieldName=field['field_name'], defaultValue=field['default_value']) for field in merge_fields ] recipients_data = self.__construct_recipient_agreement_request( recipients) document_creation_info = dict( signatureType="ESIGN", name=agreement_name, callbackInfo="", securityOptions=security_options, locale="", ccs=ccs, externalId=external_id, signatureFlow=signature_flow, fileInfos=files_data, mergeFieldInfo=converted_merge_fields, recipientSetInfos=recipients_data, message=message, daysUntilSigningDeadline=days_until_signing_deadline, ) request_data = dict(documentCreationInfo=document_creation_info) url = self.account.api_access_point + 'agreements' api_response = requests.post(url, headers=self.account.headers(), data=json.dumps(request_data)) if response_success(api_response): response = namedtuple( 'Response', ('agreement_id', 'embedded_code', 'expiration', 'url')) response_data = api_response.json() embedded_code = response_data.get('embeddedCode', None) expiration = response_data.get('expiration', None) url = response_data.get('url', None) response = response(response_data['agreementId'], embedded_code, expiration, url) return response else: check_error(api_response)