def _refresh_token(self): """ Called internally to Authenticate to generate a new refreshed token as the existing one has expired """ token_refresh_payload = { "Accept": "application/json", "grant_type": "password", "client_id": self.client_key, "username": self.username, "password": self.password, "refresh_token": self.token.refresh_token } # Attempt to get a token token_refresh_result = requests.post(self.cache.get_uri("Token"), data=token_refresh_payload, verify=self.https_verify) # Get the token details if token_refresh_result.status_code == 200: # We were successful - create a new token using the newly obtained json token self.token = AccessToken() self.token.load(token_refresh_result.json()) else: # We could not refresh our a token - this is bad, generate an exception raise Exception( "Error Refreshing Token, returned HTTP:{} '{}' - {}".format( token_refresh_result.status_code, token_refresh_result.reason, token_refresh_result.text))
def _get_token(self): """ Called internally to Authenticate to Cherwell and retrieves a new Bearer token """ token_payload = { "Accept": "application/json", "grant_type": "password", "client_id": self.client_key, "username": self.username, "password": self.password } # Attempt to get a token token_result = requests.post(self.cache.get_uri("Token"), data=token_payload, verify=self.https_verify) if token_result.status_code == 200: # We were successful - create a new token using the newly obtained json token self.token = AccessToken() self.token.load(token_result.json()) else: # We could not get a token - this is bad, generate an exception raise Exception( "Error Getting Token, returned HTTP:{} '{}' - {}".format( token_result.status_code, token_result.reason, token_result.text))
class Connection: """ The 'Connection' class serves to provide a REST API Connection to a Cherwell instance. All interactions to a Cherwell instance occur through an instantiation of this class. Parameters ---------- uri : str This is the base uri for the Cherwell instance and typically take the form of 'http://127.0.0.1' or 'https://127.0.0.1'. The IP address can be replaced by the host name provided DNS name resolution is setup correctly' client_key : str This is the REST API key generated by the Cherwell Administration tool (Orange Pill). This is needed to properly authenticate with the Cherwell REST API username : str The username of a Cherwell user that has access to all the objects and functions necessary for the required interaction password : str The password of the user referred to in the 'username' :parameter cache: ObjectCache An instance of the ObjectCache class that was previously created and/or saved/cached. If a ObjectCache instance is not provided then this is defaulted to 'None' and a new ObjectCache will be instantiated token : AccessToken An instance of the AccessToken class that was previously created or saved/cached. If a AccessToken is not provided then this is defaulted to 'None' and a new AccessToken will be created during the first interaction to the Cherwell REST API https_verify : bool Flag set to verify SSL Certificates during requests API Calls """ def __init__(self, base_uri="", client_key="", username="", password="", cache=None, token=None, https_verify=False): """ Connection instance initialisation """ # Set the values we need that were passed in self.uri = base_uri self.username = username if not password or not client_key: try: self.client_key = CherwellCredentials.decrypt_message( "cherwell_api_key") self.password = CherwellCredentials.decrypt_message( "cherwell_password") except: raise Exception( "Unable to locate encrypted Cherwell API Key and Password. Please run the function CherwellCrentials.create_encrypted_cherwell_credentials(password, client_key) first or pass in the password and client key in the CherwellClient.Connection Method" ) else: self.client_key = client_key self.password = password # Initialise a cache object - we can reuse if isinstance(cache, ObjectCache): # we have been passed a saved cache object self.cache = cache else: # We don't have a saved cache object - instantiate a new one self.cache = ObjectCache(self.uri, self.client_key) if token is not None and isinstance(token, AccessToken): # We have been passed an existing token self.token = token else: # Token not correct type - start with no token self.token = None self.https_verify = https_verify def cache(self): """ Returns the ObjectCache object used by the Connection to avoid repeated round trip to through the Cherwell API for data that is largely reused """ return self.cache def token(self): """ Returns the current AccessToken which contains the Bearer token used for repeated API calls without the need to re-authenticate """ return self.token def _get_token(self): """ Called internally to Authenticate to Cherwell and retrieves a new Bearer token """ token_payload = { "Accept": "application/json", "grant_type": "password", "client_id": self.client_key, "username": self.username, "password": self.password } # Attempt to get a token token_result = requests.post(self.cache.get_uri("Token"), data=token_payload, verify=self.https_verify) if token_result.status_code == 200: # We were successful - create a new token using the newly obtained json token self.token = AccessToken() self.token.load(token_result.json()) else: # We could not get a token - this is bad, generate an exception raise Exception( "Error Getting Token, returned HTTP:{} '{}' - {}".format( token_result.status_code, token_result.reason, token_result.text)) def _refresh_token(self): """ Called internally to Authenticate to generate a new refreshed token as the existing one has expired """ token_refresh_payload = { "Accept": "application/json", "grant_type": "password", "client_id": self.client_key, "username": self.username, "password": self.password, "refresh_token": self.token.refresh_token } # Attempt to get a token token_refresh_result = requests.post(self.cache.get_uri("Token"), data=token_refresh_payload, verify=self.https_verify) # Get the token details if token_refresh_result.status_code == 200: # We were successful - create a new token using the newly obtained json token self.token = AccessToken() self.token.load(token_refresh_result.json()) else: # We could not refresh our a token - this is bad, generate an exception raise Exception( "Error Refreshing Token, returned HTTP:{} '{}' - {}".format( token_refresh_result.status_code, token_refresh_result.reason, token_refresh_result.text)) def _get_authorisation_header(self): """ Called internally to create the Authorisation header for the REST API Calls. This method will request new tokens if needed or alternatively attempt to refresh an existing token""" if self.token is None or self.token.access_token is None: # Get a new token self._get_token() elif self.token.expired() or self.token.access_token is None: # Refresh the token if its expired self._refresh_token() if self.token: # We are connected return { "Authorization": "Bearer " + self.token.access_token, "Content-Type": "application/json" } else: # we are not connected return None def get_business_object_id(self, business_object_name, refresh=False): """ This method used the Cherwell API to retrieve the unique identifier associated with the requested Cherwell business object. If the id is found in cache then it is returned from cache and a call to the Cherwell REST API is avoided. Parameters ---------- business_object_name : str The Cherwell 'internalname' of an object for which the id is being requested refresh : bool A value of true indicates that an API call must be made to get the business object id again, even if the id is already available in the cache Returns ------- The Cherwell business object id if found or 'None' if not found """ if not self.cache.get_business_object_id( business_object_name) or refresh: # Default the return to None self.cache.set_business_object_id(business_object_name, None) # Call the API to get the result result_business_object_id = requests.get( self.cache.get_uri("BusinessObjectID") + business_object_name, headers=self._get_authorisation_header(), verify=self.https_verify) if result_business_object_id.status_code == 200: if result_business_object_id.text != "[]": # Success - save the value self.cache.set_business_object_id( business_object_name, result_business_object_id.json()[0]["busObId"]) else: # There was a problem with the API call, generate an exception raise Exception( "Error getting the business object id for '{}'. HTTP:{} '{}' - {}" .format(business_object_name, result_business_object_id.status_code, result_business_object_id.reason, result_business_object_id.text)) return self.cache.get_business_object_id(business_object_name) def get_business_object_template(self, business_object_name, refresh=False): """ This method used the Cherwell API to retrieve a json template (list of fields and attributes) for a given business object. These templates can be used to get the value of fields but can also be used for updating an object in Cherwell. If the template is found in cache then it is returned from cache and a call to the Cherwell REST API is avoided. Parameters ---------- business_object_name : str The Cherwell 'internalname' of an object for which the template is being requested. refresh : bool A value of True indicates that an API call must be made to get the business object template again, even if the template is already available in the cache. Returns ------- The Cherwell business object template if found or 'None' if not found. """ if not self.cache.get_business_object_template( business_object_name) or refresh: # Default the return to None self.cache.set_business_object_template(business_object_name, None) # Get the object id for the requested object object_id = self.get_business_object_id(business_object_name) # Setup the templateRequest template_request = { "busObID": object_id, "includeRequired": "True", "includeAll": "True" } # Call the API to get the template result_business_object_template = requests.post( self.cache.get_uri("BusinessObjectTemplate"), json=template_request, headers=self._get_authorisation_header(), verify=self.https_verify) if result_business_object_template.status_code == 200: # Success - we have the template self.cache.set_business_object_template( business_object_name, result_business_object_template.json()) return self.cache.get_business_object_template(business_object_name) def get_business_object_field_id(self, business_object_name, field_name, refresh=False): """ This method used the Cherwell API to retrieve the field id for a passed in Cherwell business object and field name. The field id is retrieved from a business object template. The Business object Template generally provides the following information about a Cherwell Business Object. -- The list of fields configured in the business object as well as the following attributes for each field: -- displayName -- name -- value -- html -- dirty (signifies a change of value if true) -- fieldId Parameters ---------- business_object_name : str The Cherwell 'internalname' of an object for which the field is being requested. field_name : str The 'internalname' of a field in the passed in Cherwell business object, for which the id is being requested. refresh : bool A value of True indicates that an API call must be made to get the business object template again, even if the template is already available in the cache. Returns ------- The Cherwell field id of the requested field, in the requested Cherwell business object. """ # Default the returning Id to None field_id = None # Make sure we have the template template = self.get_business_object_template(business_object_name, refresh) # Loop through the template and find the field name for field in template["fields"]: if field["name"] == field_name: # We matched the field - get the id field_id = field["fieldId"] # Break out of loop - we found what we need break return field_id def get_business_object_summary(self, business_object_name, refresh=False): """ This method used the Cherwell API to retrieve a Cherwell business object summary. The Business object Summary generally provides the following information about a Cherwell Business Object. -- What type of object it is i.e. 'Major', 'Supporting', 'Lookup' or 'Group' -- The field id used to main the state -- The display name -- The internal name -- The business object id -- The configured lifecycle states such as 'Active', 'Inactive', 'Retired' as examples -- The first record id in the business object table -- The fieldid for the field used as the Record Id (RecId) If the summary is found in cache then it is returned from cache and a call to the Cherwell REST API is avoided. Parameters ---------- business_object_name : str The Cherwell 'internalname' of an object for which the summary is being requested. refresh : bool A value of True indicates that an API call must be made to get the business object summary again, even if the template is already available in the cache. Returns ------- The Cherwell summary for the requested business object or 'None' if not found. """ if not self.cache.get_business_object_summary( business_object_name) or refresh: # Default the return to None self.cache.set_business_object_summary(business_object_name, None) # Call the API to get the summary result_business_object_summary = requests.get( "{}{}".format(self.cache.get_uri("BusinessObjectSummary"), business_object_name), headers=self._get_authorisation_header(), verify=self.https_verify) if result_business_object_summary.status_code == 200: if result_business_object_summary.text != "[]": # Success - we have the summary self.cache.set_business_object_summary( business_object_name, result_business_object_summary.json()) return self.cache.get_business_object_summary(business_object_name) def get_business_object_internalname(self, business_object_id, refresh=False): """ This method uses the Cherwell API to retrieve a Cherwell business object schema, which it then uses to get the business objects internal name. The Business schema generally provides the following information about a Cherwell Business Object. -- Details about every field on the business object -- The internal name of the business object If the schema is found in cache then it is returned from cache and a call to the Cherwell REST API is avoided. Parameters ---------- business_object_id : str The Cherwell business object id of an object for which the schema is being requested. refresh : bool A value of True indicates that an API call must be made to get the business object schema again, even if the schema is already available in the cache. Returns ------- The Cherwell Business Object Internal name for the requested business object or 'None' if not found. """ if not self.cache.get_business_object_schema( business_object_id) or refresh: # Default the return to None self.cache.set_business_object_schema(business_object_id, None) # Call the API to get the schema result_business_object_schema = requests.get( "{}{}".format(self.cache.get_uri("BusinessObjectSchema"), business_object_id), headers=self._get_authorisation_header(), verify=self.https_verify) if result_business_object_schema.status_code == 200: if result_business_object_schema.text != "[]": # Success - we have the schema self.cache.set_business_object_schema( business_object_id, result_business_object_schema.json()) if self.cache.get_business_object_schema(business_object_id): return self.cache.get_business_object_schema( business_object_id)["name"] else: return self.cache.get_business_object_schema(business_object_id) def set_business_object_field_value_in_template(self, template, field_name, value): """ This method can be used to update a fields value in a Cherwell business object template. The template can then be passed back to Cherwell to be used to create or update a Business Object. If the field is found, and the value updated - then the 'dirty' flag is also set to True. This lets Cherwell know the value has been updated - when we pass the template back. Parameters ---------- template : str The Cherwell business object template to update. The template was likely previously obtained using the 'get_business_object_template' method. field_name : str The name of the field to search for in the template and whose value needs to be updated. value : str The value to set the required field to, in the Cherwell business object template. Returns ------- 'True' if the field was found and updated, else false. """ # Default the search to false field_found = False # Loop through the passed in template and modify the fields to the required values for field_id in range(0, len(template["fields"])): if template["fields"][field_id]["displayName"] == field_name: # We matched the field - set the value template["fields"][field_id]["value"] = value # Set the dirty flag to true to show it changed template["fields"][field_id]["dirty"] = True # Set found to true field_found = True # Caller can decide what to do with False returns (field not found) return field_found def create_business_object(self, template, business_object_name): """ This method can be used to create a new business object using a Cherwell business object template. Parameters ---------- template : str The Cherwell business object template to use to create the new business object. business_object_name : str The name of the Cherwell business object to create using the passed in template. Returns : tuple ------- -- New record public id -- New record id -- business object id Generates an exception if the new business object cannot be created """ # Get the object id for the requested object business_object_id = self.get_business_object_id(business_object_name) # Setup the payload payload = {"busObID": business_object_id, "fields": template["fields"]} # Attempt to save the new Business Object result_new = requests.post(self.cache.get_uri("BusinessObjectSave"), json=payload, headers=self._get_authorisation_header(), verify=self.https_verify) if result_new.status_code == 200: # Success - return the public id and record id return str(result_new.json()["busObPublicId"]), str( result_new.json()["busObRecId"]), business_object_id else: # There was a problem with the API call, generate an exception raise Exception( "Error creating the new business object '{}'. HTTP:{} '{}' - {}" .format(business_object_name, result_new.status_code, result_new.reason, result_new.text)) def get_new_business_object(self, business_object_name, refresh=False): """ This method can be used to return a obtain a new 'BusinessObject' instance with all the fields set as object instance properties. This 'Business Object' instance can be used to update properties and 'Save()' a new record to Cherwell. Parameters ---------- business_object_name : str The Cherwell 'internalname' of an object for which the the 'BusinessObject' is being requested. refresh : bool A value of True indicates that an API call must be made to get the business object properties again, even if they have previously been retrieved. This can be used if its expected the number/name of fields has changed since the last call. Returns : BusinessObject ------- A new 'BusinessObject' instance with instance properties that reflect the fields of the Cherwell Business Object. These properties can be modified and the 'Save()' method can be called to save a new record. """ # Default the returning object to None new_business_object = None # Get the object id for the requested object business_object_id = self.get_business_object_id(business_object_name) # Get the business object template self.get_business_object_template(business_object_name, refresh) # Create a new business object passing in the name and object id, and the # function used to get a new header new_business_object = BusinessObject( business_object_name, self._get_authorisation_header, self.cache.get_uri("BusinessObjectSave"), self.cache.get_uri("BusinessObjectDelete"), business_object_id) # Load the fields from a passed in template new_business_object.load( self.cache.get_business_object_template(business_object_name)) # return the new business object return new_business_object def get_business_objects(self, filter_object): """ This method can be used to return a list of 'BusinessObject' instances that meet the search criteria defined in the passed in SimpleFilter. These 'Business Object' instances can be used to update properties and 'Save()' the values back to Cherwell. These 'Business Object' instances even have a 'Delete()' method to delete the record in Cherwell. SimpleFilter example: -------------------- # Create a new instance of a SimpleFilter object - setting the business object name to look for search_filter = Filter.SimpleFilter("CustomerInternal") # add a search filter where you are looking for all customers with 'FirstName' that contains 'Susan' search_filter.add_search_fields("FirstName", "contains", "Susan") Parameters ---------- business_object_name : str The Cherwell 'internalname' of an object for which the the 'BusinessObject' is being requested. filter_object : SimpleFilter An instance of the SimpleFilter class that has been populated with the search fields. By default SimpleFilter are setup to return all fields so all attributes can be populated Returns : Tuple(Int: Num Records, List: BusinessObjects) ------- -- The number of business objects returned -- A list of business objects with fully populated fields as instance properties. These properties can be modified and the 'Save()' method can be called to save a new record. The Business Object can also be deleted by calling the 'Delete()' method """ # Return value business_objects = [] # setup the filter array filter_info = [] # return value to indicate how many rows found rows = 0 # Start at page number 0 page_number = 1 if isinstance(filter_object, Filter.SimpleFilter): # Get the object id for the requested object business_object_id = self.get_business_object_id( filter_object.business_object_name) # Loop through each search field in the filter for field in filter_object.search_fields: # Get the field id for the field field_id = self.get_business_object_field_id( filter_object.business_object_name, field["Field"]) # Setup the filter to be passed in filter_info.append({ "fieldID": field_id, "operator": field["Operator"], "value": field["Value"] }) else: # Raise an exception raise ValueError( "Filter object is not an instance of SimpleFilter") while True: # Setup the payload payload = { "busObID": business_object_id, "filters": filter_info, "includeAllFields": filter_object.include_all_fields, "pageNumber": page_number } # Attempt to get the required business object result_bus_obj_search = requests.post( self.cache.get_uri("BusinessSearchResults"), json=payload, headers=self._get_authorisation_header(), verify=self.https_verify) if result_bus_obj_search.status_code == 200: search_results = result_bus_obj_search.json() # Get how many rows were returned rows = search_results["totalRows"] # If we got no records back - means we have found them all - exit if len(search_results["businessObjects"]) == 0: break # Loop through all the business object returned and get a new business object for each for business_object in search_results["businessObjects"]: # Create a new business object passing in the name and object id, and the # function used to get a new header new_business_object = BusinessObject( filter_object.business_object_name, self._get_authorisation_header, self.cache.get_uri("BusinessObjectSave"), self.cache.get_uri("BusinessObjectDelete"), business_object_id, business_object["busObRecId"], business_object["busObPublicId"]) # Load the fields from the obtained template new_business_object.load(business_object) # add the new object to the business objects collection business_objects.append(new_business_object) else: # There was a problem with the API call, generate an exception raise Exception( "Error searching for business objects '{}'. HTTP:{} '{}' - {}" .format(filter_object.business_object_name, result_bus_obj_search.status_code, result_bus_obj_search.reason, result_bus_obj_search.text)) # Increment the page number page_number = page_number + 1 # Return the business object return rows, business_objects def get_business_records(self, filter_object): """ This method can be used to retrieve the business object record id 'busObRecId' as well as any number of fields from matching business object in Cherwell. AdHocFilter example: -------------------- # Create a new instance of a AdHocFilter object - setting the business object name to look for search_filter = Filter.AdHocFilter("CustomerInternal") # add a search filter where you are looking for all customers with 'FirstName' that contains 'Susan' search_filter.add_search_fields("FirstName", "contains", "Susan") # Specify you want 2 fields returned search_filter.add_fields("Email") search_filter.add_fields("Phone") Tips: -- To return all fields, set 'include_all_fields' to 'True' when initialising the AdHocFilter. -- Operators for searching can include: -- 'eq' for equal to -- 'gt' for greater than -- 'lt' for less than -- 'contains' for a like comparison to see if the field contains that value -- 'startswith' to check if the fields starts with the supplied value Parameters ---------- filter_object : AdHocFilter An instance of the AdhocFilter class that has been populated with the search fields and fields to return. Returns : List ------- A list of matching records and their fields will be returned in a list/dictionary structure. Example: [ { 'busObRecId': u'9365db49c71a885a905d8b4a9d82e19eccaf3aba16', 'fields': [ {'fieldId': u'9337c23403da50548767c24e48aede27e5dd274521', 'displayName': u'Email', 'name': u'Email', 'value': u'*****@*****.**'}, {'fieldId': u'9337c23376e3ad38cdfa00495c92f33775a61c393b', 'displayName': u'Phone', 'name': u'Phone', 'value': u'(719) 386.7000'} ] }, { 'busObRecId': u'93d5c17090fa0a4981c57b459eb051c83f5475409b', 'fields': [ {'fieldId': u'9337c23403da50548767c24e48aede27e5dd274521', 'displayName': u'Email', 'name': u'Email', 'value': u"Susan.O'*****@*****.**"}, {'fieldId': u'9337c23376e3ad38cdfa00495c92f33775a61c393b', 'displayName': u'Phone', 'name': u'Phone','value': u'(719) 386.7000'} ] } ] """ # Return value business_objects = [] # setup the filter array filter_info = [] # Setup the fields array field_list = [] # return value to indicate how many rows found rows = 0 if isinstance(filter_object, Filter.AdHocFilter): # Get the object id for the requested object business_object_id = self.get_business_object_id( filter_object.business_object_name) # Loop through each search field in the filter for field in filter_object.search_fields: # Get the field id for the field field_id = self.get_business_object_field_id( filter_object.business_object_name, field["Field"]) # Setup the filter to be passed in filter_info.append({ "fieldID": field_id, "operator": field["Operator"], "value": field["Value"] }) # Loop though each field in the filter for field in filter_object.fields: # Get the field id for each field field_id = self.get_business_object_field_id( filter_object.business_object_name, field) # Append the field id to the fields list field_list.append(field_id) else: # Raise an exception raise ValueError("Filter object is not a AdHocFilter") # Setup the payload payload = { "busObID": business_object_id, "fields": field_list, "filters": filter_info, "includeAllFields": filter_object.include_all_fields } # Attempt to get the required business object result_search = requests.post( self.cache.get_uri("BusinessSearchResults"), json=payload, headers=self._get_authorisation_header(), verify=self.https_verify) if result_search.status_code == 200: # We have some records search_results = result_search.json() # Get how many rows were returned rows = search_results["totalRows"] # Loop through all the business object returned for business_object in search_results["businessObjects"]: field_result_list = [] # Loop through each field returned for field in business_object["fields"]: # Add to the field result list field_dict = { "name": field["name"], "value": field["value"], "displayName": field["displayName"], "fieldId": field["fieldId"] } field_result_list.append(field_dict) # Add the fields and business object id into a dictionary business_object_dict = { "busObRecId": business_object["busObRecId"], "fields": field_result_list } # Add the this dictionary to the array to be returned business_objects.append(business_object_dict) else: # There was a problem with the API call, generate an exception raise Exception( "Error searching for business objects '{}'. HTTP:{} '{}' - {}". format(filter_object.business_object_name, result_search.status_code, result_search.reason, result_search.text)) # Return the number of object and the array of objects return rows, business_objects def get_saved_search_results(self, association, scope, search_name, scope_owner="None", include_schema="false", results_as_simple_results_list="false"): """ This method can be used to return the paged results of a saved search. Parameters ---------- association : str The Business Object Display Name this search belongs to scope : str The scope the search belongs to e.g. Global scope_owner : str The scope owner the search belongs to defaulted to None search_name : str The name of the saved search you want to run include_schema : str Use to include the table schema of the saved search. If false, results contain the fieldid and field value without field information. Default is false. true or false results_as_simple_results_list : str Indicates if the results should be returned in a simple results list format or a table format. Default is a table format. true or false Returns : List ------- A list of paged results of a saved search. Example: { "businessObjects": [ { "busObId": "944ee68cc77c109557cf534f1d815ddca5282a0316", "busObPublicId": "SL1-Dev", "busObRecId": "94633cf94d6d924d17bc87477aae77d684a553b8f3", "fields": [ { "dirty": false, "displayName": "Federation Name", "fieldId": "BO:944ee68cc77c109557cf534f1d815ddca5282a0316,FI:944ee692875572fd7d5ddf44ddb1a2b66f93fbed3e", "html": null, "name": "FederationName", "value": "SL1-Dev" }, { "dirty": false, "displayName": "Federation Type", "fieldId": "BO:944ee68cc77c109557cf534f1d815ddca5282a0316,FI:944eef6fda969659714fed4498adabf5a712f72ada", "html": null, "name": "FederationType", "value": "ScienceLogic" }, { "dirty": false, "displayName": "Status", "fieldId": "BO:944ee68cc77c109557cf534f1d815ddca5282a0316,FI:944ee695474b8573c238464f298f8aaeb380ff511e", "html": null, "name": "Status", "value": "Active" } ], "links": [ { "name": "Delete Record", "url": "http://ec2-3-104-173-24.ap-southeast-2.compute.amazonaws.com/CherwellAPI/api/V1/deletebusinessobject/busobid/944ee68cc77c109557cf534f1d815ddca5282a0316/busobrecid/94633cf94d6d924d17bc87477aae77d684a553b8f3" } ], "errorCode": null, "errorMessage": null, "hasError": false } ], "hasPrompts": false, "links": [ { "name": "First Page", "url": "http://ec2-3-104-173-24.ap-southeast-2.compute.amazonaws.com/CherwellAPI/api/V1/getsearchresults/association/944ee68cc77c109557cf534f1d815ddca5282a0316/scope/Global/scopeowner/None/searchname/All%20Active%20Federation%20Sources?pagesize=200&pagenumber=1" }, { "name": "Last Page", "url": "http://ec2-3-104-173-24.ap-southeast-2.compute.amazonaws.com/CherwellAPI/api/V1/getsearchresults/association/944ee68cc77c109557cf534f1d815ddca5282a0316/scope/Global/scopeowner/None/searchname/All%20Active%20Federation%20Sources?pagesize=200&pagenumber=1" }, { "name": "Next Page", "url": "http://ec2-3-104-173-24.ap-southeast-2.compute.amazonaws.com/CherwellAPI/api/V1/getsearchresults/association/944ee68cc77c109557cf534f1d815ddca5282a0316/scope/Global/scopeowner/None/searchname/All%20Active%20Federation%20Sources?pagesize=200&pagenumber=1" }, { "name": "Previous Page", "url": "http://ec2-3-104-173-24.ap-southeast-2.compute.amazonaws.com/CherwellAPI/api/V1/getsearchresults/association/944ee68cc77c109557cf534f1d815ddca5282a0316/scope/Global/scopeowner/None/searchname/All%20Active%20Federation%20Sources?pagesize=200&pagenumber=1" } ], "prompts": [], "searchResultsFields": [], "simpleResults": null, "totalRows": 1, "errorCode": null, "errorMessage": null, "hasError": false } """ # Return value business_objects = [] # setup the filter array filter_info = [] # Setup the fields array field_list = [] # return value to indicate how many rows found rows = 0 # Get the object id for the requested object business_object_id = self.get_business_object_id(association) saved_search_uri = self.cache.get_uri("RunSavedSearch").replace( "[association]", business_object_id).replace("[scope]", scope).replace( "[scopeowner]", scope_owner).replace("[searchname]", search_name).replace( "[includeschema]", include_schema).replace("[resultsAsSimpleResultsList]", results_as_simple_results_list) # Attempt to get the required business object result_search = requests.get(saved_search_uri, headers=self._get_authorisation_header(), verify=self.https_verify) if result_search.status_code == 200: # We have some records search_results = result_search.json() # Get how many rows were returned rows = search_results["totalRows"] # Loop through all the business object returned for business_object in search_results["businessObjects"]: field_result_list = [] # Loop through each field returned for field in business_object["fields"]: # Add to the field result list field_dict = { "name": field["name"], "value": field["value"], "displayName": field["displayName"], "fieldId": field["fieldId"] } field_result_list.append(field_dict) # Add the fields and business object id into a dictionary business_object_dict = { "busObRecId": business_object["busObRecId"], "fields": field_result_list } # Add the this dictionary to the array to be returned business_objects.append(business_object_dict) else: # There was a problem with the API call, generate an exception raise Exception( "Error searching for business objects '{}'. HTTP:{} '{}' - {}". format(association, result_search.status_code, result_search.reason, result_search.text)) # Return the number of object and the array of objects return rows, business_objects def logout(self): """ This method can be used to logout of the Cherwell instance. The user referenced in the authentication token is instantly logged out. A new CherwellClient instance and token will be required to login again. """ # Attempt to logout response = requests.delete(self.cache.get_uri("Logout"), headers=self._get_authorisation_header(), verify=self.https_verify) if response.status_code == 200: # Success - Logged out successfully return str(response.status_code) else: # There was a problem with the API call, generate an exception raise Exception( "Error logging out of Cherwell 'HTTP:{} '{}' - {}".format( response.status_code, response.reason, response.text))
class Connection: """ The 'Connection' class serves to provide a REST API Connection to a Cherwell instance. All interactions to a Cherwell instance occur through an instantiation of this class. Parameters ---------- uri : str This is the base uri for the Cherwell instance and typically take the form of 'http://127.0.0.1' or 'https://127.0.0.1'. The IP address can be replaced by the host name provided DNS name resolution is setup correctly' client_key : str This is the REST API key generated by the Cherwell Administration tool (Orange Pill). This is needed to properly authenticate with the Cherwell REST API username : str The username of a Cherwell user that has access to all the objects and functions necessary for the required interaction password : str The password of the user referred to in the 'username' :parameter cache: ObjectCache An instance of the ObjectCache class that was previously created and/or saved/cached. If a ObjectCache instance is not provided then this is defaulted to 'None' and a new ObjectCache will be instantiated token : AccessToken An instance of the AccessToken class that was previously created or saved/cached. If a AccessToken is not provided then this is defaulted to 'None' and a new AccessToken will be created during the first interaction to the Cherwell REST API """ def __init__(self, base_uri="", client_key="", username="", password="", cache=None, token=None): """ Connection instance initialisation """ # Set the values we need that were passed in self.uri = base_uri self.client_key = client_key self.username = username self.password = password # Initialise a cache object - we can reuse if isinstance(cache, ObjectCache): # we have been passed a saved cache object self.cache = cache else: # We don't have a saved cache object - instantiate a new one self.cache = ObjectCache(self.uri, self.client_key) if token is not None and isinstance(token, AccessToken): # We have been passed an existing token self.token = token else: # Token not correct type - start with no token self.token = None def cache(self): """ Returns the ObjectCache object used by the Connection to avoid repeated round trip to through the Cherwell API for data that is largely reused """ return self.cache def token(self): """ Returns the current AccessToken which contains the Bearer token used for repeated API calls without the need to re-authenticate """ return self.token def _get_token(self): """ Called internally to Authenticate to Cherwell and retrieves a new Bearer token """ token_payload = { "Accept": "application/json", "grant_type": "password", "client_id": self.client_key, "username": self.username, "password": self.password } # Attempt to get a token token_result = requests.post(self.cache.get_uri("Token"), data=token_payload) if token_result.status_code == 200: # We were successful - create a new token using the newly obtained json token self.token = AccessToken() self.token.load(token_result.json()) else: # We could not get a token - this is bad, generate an exception raise Exception( "Error Getting Token, returned HTTP:{} '{}' - {}".format( token_result.status_code, token_result.reason, token_result.text)) def _refresh_token(self): """ Called internally to Authenticate to generate a new refreshed token as the existing one has expired """ token_refresh_payload = { "Accept": "application/json", "grant_type": "password", "client_id": self.client_key, "username": self.username, "password": self.password, "refresh_token": self.token.refresh_token } # Attempt to get a token token_refresh_result = requests.post(self.cache.get_uri("Token"), data=token_refresh_payload) # Get the token details if token_refresh_result.status_code == 200: # We were successful - create a new token using the newly obtained json token self.token = AccessToken() self.token.load(token_refresh_result.json()) else: # We could not refresh our a token - this is bad, generate an exception raise Exception( "Error Refreshing Token, returned HTTP:{} '{}' - {}".format( token_refresh_result.status_code, token_refresh_result.reason, token_refresh_result.text)) def _get_authorisation_header(self): """ Called internally to create the Authorisation header for the REST API Calls. This method will request new tokens if needed or alternatively attempt to refresh an existing token""" if self.token is None or self.token.access_token is None: # Get a new token self._get_token() elif self.token.expired() or self.token.access_token is None: # Refresh the token if its expired self._refresh_token() if self.token: # We are connected return { "Authorization": "Bearer " + self.token.access_token, "Content-Type": "application/json" } else: # we are not connected return None def get_business_object_id(self, business_object_name, refresh=False): """ This method used the Cherwell API to retrieve the unique identifier associated with the requested Cherwell business object. If the id is found in cache then it is returned from cache and a call to the Cherwell REST API is avoided. Parameters ---------- business_object_name : str The Cherwell 'internalname' of an object for which the id is being requested refresh : bool A value of true indicates that an API call must be made to get the business object id again, even if the id is already available in the cache Returns ------- The Cherwell business object id if found or 'None' if not found """ if not self.cache.get_business_object_id( business_object_name) or refresh: # Default the return to None self.cache.set_business_object_id(business_object_name, None) # Call the API to get the result result_business_object_id = requests.get( self.cache.get_uri("BusinessObjectID") + business_object_name, headers=self._get_authorisation_header()) if result_business_object_id.status_code == 200: if result_business_object_id.text != "[]": # Success - save the value self.cache.set_business_object_id( business_object_name, result_business_object_id.json()[0]["busObId"]) else: # There was a problem with the API call, generate an exception raise Exception( "Error getting the business object id for '{}'. HTTP:{} '{}' - {}" .format(business_object_name, result_business_object_id.status_code, result_business_object_id.reason, result_business_object_id.text)) return self.cache.get_business_object_id(business_object_name) def get_business_object_template(self, business_object_name, refresh=False): """ This method used the Cherwell API to retrieve a json template (list of fields and attributes) for a given business object. These templates can be used to get the value of fields but can also be used for updating an object in Cherwell. If the template is found in cache then it is returned from cache and a call to the Cherwell REST API is avoided. Parameters ---------- business_object_name : str The Cherwell 'internalname' of an object for which the template is being requested. refresh : bool A value of True indicates that an API call must be made to get the business object template again, even if the template is already available in the cache. Returns ------- The Cherwell business object template if found or 'None' if not found. """ if not self.cache.get_business_object_template( business_object_name) or refresh: # Default the return to None self.cache.set_business_object_template(business_object_name, None) # Get the object id for the requested object object_id = self.get_business_object_id(business_object_name) # Setup the templateRequest template_request = { "busObID": object_id, "includeRequired": "True", "includeAll": "True" } # Call the API to get the template result_business_object_template = requests.post( self.cache.get_uri("BusinessObjectTemplate"), json=template_request, headers=self._get_authorisation_header()) if result_business_object_template.status_code == 200: # Success - we have the template self.cache.set_business_object_template( business_object_name, result_business_object_template.json()) return self.cache.get_business_object_template(business_object_name) def get_business_object_field_id(self, business_object_name, field_name, refresh=False): """ This method used the Cherwell API to retrieve the field id for a passed in Cherwell business object and field name. The field id is retrieved from a business object template. The Business object Template generally provides the following information about a Cherwell Business Object. -- The list of fields configured in the business object as well as the following attributes for each field: -- displayName -- name -- value -- html -- dirty (signifies a change of value if true) -- fieldId Parameters ---------- business_object_name : str The Cherwell 'internalname' of an object for which the field is being requested. field_name : str The 'internalname' of a field in the passed in Cherwell business object, for which the id is being requested. refresh : bool A value of True indicates that an API call must be made to get the business object template again, even if the template is already available in the cache. Returns ------- The Cherwell field id of the requested field, in the requested Cherwell business object. """ # Default the returning Id to None field_id = None # Make sure we have the template template = self.get_business_object_template(business_object_name, refresh) # Loop through the template and find the field name for field in template["fields"]: if field["name"] == field_name: # We matched the field - get the id field_id = field["fieldId"] # Break out of loop - we found what we need break return field_id def get_business_object_summary(self, business_object_name, refresh=False): """ This method used the Cherwell API to retrieve a Cherwell business object summary. The Business object Summary generally provides the following information about a Cherwell Business Object. -- What type of object it is i.e. 'Major', 'Supporting', 'Lookup' or 'Group' -- The field id used to main the state -- The display name -- The internal name -- The business object id -- The configured lifecycle states such as 'Active', 'Inactive', 'Retired' as examples -- The first record id in the business object table -- The fieldid for the field used as the Record Id (RecId) If the summary is found in cache then it is returned from cache and a call to the Cherwell REST API is avoided. Parameters ---------- business_object_name : str The Cherwell 'internalname' of an object for which the summary is being requested. refresh : bool A value of True indicates that an API call must be made to get the business object summary again, even if the template is already available in the cache. Returns ------- The Cherwell summary for the requested business object or 'None' if not found. """ if not self.cache.get_business_object_summary( business_object_name) or refresh: # Default the return to None self.cache.set_business_object_summary(business_object_name, None) # Call the API to get the summary result_business_object_summary = requests.get( "{}{}".format(self.cache.get_uri("BusinessObjectSummary"), business_object_name), headers=self._get_authorisation_header()) if result_business_object_summary.status_code == 200: if result_business_object_summary.text != "[]": # Success - we have the summary self.cache.set_business_object_summary( business_object_name, result_business_object_summary.json()) return self.cache.get_business_object_summary(business_object_name) def set_business_object_field_value_in_template(self, template, field_name, value): """ This method can be used to update a fields value in a Cherwell business object template. The template can then be passed back to Cherwell to be used to create or update a Business Object. If the field is found, and the value updated - then the 'dirty' flag is also set to True. This lets Cherwell know the value has been updated - when we pass the template back. Parameters ---------- template : str The Cherwell business object template to update. The template was likely previously obtained using the 'get_business_object_template' method. field_name : str The name of the field to search for in the template and whose value needs to be updated. value : str The value to set the required field to, in the Cherwell business object template. Returns ------- 'True' if the field was found and updated, else false. """ # Default the search to false field_found = False # Loop through the passed in template and modify the fields to the required values for field_id in range(0, len(template["fields"])): if template["fields"][field_id]["displayName"] == field_name: # We matched the field - set the value template["fields"][field_id]["value"] = value # Set the dirty flag to true to show it changed template["fields"][field_id]["dirty"] = True # Set found to true field_found = True # Caller can decide what to do with False returns (field not found) return field_found def create_business_object(self, template, business_object_name): """ This method can be used to create a new business object using a Cherwell business object template. Parameters ---------- template : str The Cherwell business object template to use to create the new business object. business_object_name : str The name of the Cherwell business object to create using the passed in template. Returns : tuple ------- -- New record public id -- New record id -- business object id Generates an exception if the new business object cannot be created """ # Get the object id for the requested object business_object_id = self.get_business_object_id(business_object_name) # Setup the payload payload = {"busObID": business_object_id, "fields": template["fields"]} # Attempt to save the new Business Object result_new = requests.post(self.cache.get_uri("BusinessObjectSave"), json=payload, headers=self._get_authorisation_header()) if result_new.status_code == 200: # Success - return the public id and record id return str(result_new.json()["busObPublicId"]), str( result_new.json()["busObRecId"]), business_object_id else: # There was a problem with the API call, generate an exception raise Exception( "Error creating the new business object '{}'. HTTP:{} '{}' - {}" .format(business_object_name, result_new.status_code, result_new.reason, result_new.text)) def get_new_business_object(self, business_object_name, refresh=False): """ This method can be used to return a obtain a new 'BusinessObject' instance with all the fields set as object instance properties. This 'Business Object' instance can be used to update properties and 'Save()' a new record to Cherwell. Parameters ---------- business_object_name : str The Cherwell 'internalname' of an object for which the the 'BusinessObject' is being requested. refresh : bool A value of True indicates that an API call must be made to get the business object properties again, even if they have previously been retrieved. This can be used if its expected the number/name of fields has changed since the last call. Returns : BusinessObject ------- A new 'BusinessObject' instance with instance properties that reflect the fields of the Cherwell Business Object. These properties can be modified and the 'Save()' method can be called to save a new record. """ # Default the returning object to None new_business_object = None # Get the object id for the requested object business_object_id = self.get_business_object_id(business_object_name) # Get the business object template self.get_business_object_template(business_object_name, refresh) # Create a new business object passing in the name and object id, and the # function used to get a new header new_business_object = BusinessObject( business_object_name, self._get_authorisation_header, self.cache.get_uri("BusinessObjectSave"), self.cache.get_uri("BusinessObjectDelete"), business_object_id) # Load the fields from a passed in template new_business_object.load( self.cache.get_business_object_template(business_object_name)) # return the new business object return new_business_object def get_business_objects(self, filter_object): """ This method can be used to return a list of 'BusinessObject' instances that meet the search criteria defined in the passed in SimpleFilter. These 'Business Object' instances can be used to update properties and 'Save()' the values back to Cherwell. These 'Business Object' instances even have a 'Delete()' method to delete the record in Cherwell. SimpleFilter example: -------------------- # Create a new instance of a SimpleFilter object - setting the business object name to look for search_filter = Filter.SimpleFilter("CustomerInternal") # add a search filter where you are looking for all customers with 'FirstName' that contains 'Susan' search_filter.add_search_fields("FirstName", "contains", "Susan") Parameters ---------- business_object_name : str The Cherwell 'internalname' of an object for which the the 'BusinessObject' is being requested. filter_object : SimpleFilter An instance of the SimpleFilter class that has been populated with the search fields. By default SimpleFilter are setup to return all fields so all attributes can be populated Returns : Tuple(Int: Num Records, List: BusinessObjects) ------- -- The number of business objects returned -- A list of business objects with fully populated fields as instance properties. These properties can be modified and the 'Save()' method can be called to save a new record. The Business Object can also be deleted by calling the 'Delete()' method """ # Return value business_objects = [] # setup the filter array filter_info = [] # return value to indicate how many rows found rows = 0 if isinstance(filter_object, Filter.SimpleFilter): # Get the object id for the requested object business_object_id = self.get_business_object_id( filter_object.business_object_name) # Loop through each search field in the filter for field in filter_object.search_fields: # Get the field id for the field field_id = self.get_business_object_field_id( filter_object.business_object_name, field["Field"]) # Setup the filter to be passed in filter_info.append({ "fieldID": field_id, "operator": field["Operator"], "value": field["Value"] }) else: # Raise an exception raise ValueError( "Filter object is not an instance of SimpleFilter") # Setup the payload payload = { "busObID": business_object_id, "filters": filter_info, "includeAllFields": filter_object.include_all_fields } # Attempt to get the required business object result_bus_obj_search = requests.post( self.cache.get_uri("BusinessSearchResults"), json=payload, headers=self._get_authorisation_header()) if result_bus_obj_search.status_code == 200: search_results = result_bus_obj_search.json() # Get how many rows were returned rows = search_results["totalRows"] # Loop through all the business object returned and get a new business object for each for business_object in search_results["businessObjects"]: # Create a new business object passing in the name and object id, and the # function used to get a new header new_business_object = BusinessObject( filter_object.business_object_name, self._get_authorisation_header, self.cache.get_uri("BusinessObjectSave"), self.cache.get_uri("BusinessObjectDelete"), business_object_id, business_object["busObRecId"], business_object["busObPublicId"]) # Load the fields from the obtained template new_business_object.load(business_object) # add the new object to the business objects collection business_objects.append(new_business_object) else: # There was a problem with the API call, generate an exception raise Exception( "Error searching for business objects '{}'. HTTP:{} '{}' - {}". format(filter_object.business_object_name, result_bus_obj_search.status_code, result_bus_obj_search.reason, result_bus_obj_search.text)) # Return the business object return rows, business_objects def get_business_records(self, filter_object): """ This method can be used to retrieve the business object record id 'busObRecId' as well as any number of fields from matching business object in Cherwell. AdHocFilter example: -------------------- # Create a new instance of a AdHocFilter object - setting the business object name to look for search_filter = Filter.AdHocFilter("CustomerInternal") # add a search filter where you are looking for all customers with 'FirstName' that contains 'Susan' search_filter.add_search_fields("FirstName", "contains", "Susan") # Specify you want 2 fields returned search_filter.add_fields("Email") search_filter.add_fields("Phone") Tips: -- To return all fields, set 'include_all_fields' to 'True' when initialising the AdHocFilter. -- Operators for searching can include: -- 'eq' for equal to -- 'gt' for greater than -- 'lt' for less than -- 'contains' for a like comparison to see if the field contains that value -- 'startswith' to check if the fields starts with the supplied value Parameters ---------- filter_object : AdHocFilter An instance of the AdhocFilter class that has been populated with the search fields and fields to return. Returns : List ------- A list of matching records and their fields will be returned in a list/dictionary structure. Example: [ { 'busObRecId': u'9365db49c71a885a905d8b4a9d82e19eccaf3aba16', 'fields': [ {'fieldId': u'9337c23403da50548767c24e48aede27e5dd274521', 'displayName': u'Email', 'name': u'Email', 'value': u'*****@*****.**'}, {'fieldId': u'9337c23376e3ad38cdfa00495c92f33775a61c393b', 'displayName': u'Phone', 'name': u'Phone', 'value': u'(719) 386.7000'} ] }, { 'busObRecId': u'93d5c17090fa0a4981c57b459eb051c83f5475409b', 'fields': [ {'fieldId': u'9337c23403da50548767c24e48aede27e5dd274521', 'displayName': u'Email', 'name': u'Email', 'value': u"Susan.O'*****@*****.**"}, {'fieldId': u'9337c23376e3ad38cdfa00495c92f33775a61c393b', 'displayName': u'Phone', 'name': u'Phone','value': u'(719) 386.7000'} ] } ] """ # Return value business_objects = [] # setup the filter array filter_info = [] # Setup the fields array field_list = [] # return value to indicate how many rows found rows = 0 if isinstance(filter_object, Filter.AdHocFilter): # Get the object id for the requested object business_object_id = self.get_business_object_id( filter_object.business_object_name) # Loop through each search field in the filter for field in filter_object.search_fields: # Get the field id for the field field_id = self.get_business_object_field_id( filter_object.business_object_name, field["Field"]) # Setup the filter to be passed in filter_info.append({ "fieldID": field_id, "operator": field["Operator"], "value": field["Value"] }) # Loop though each field in the filter for field in filter_object.fields: # Get the field id for each field field_id = self.get_business_object_field_id( filter_object.business_object_name, field) # Append the field id to the fields list field_list.append(field_id) else: # Raise an exception raise ValueError("Filter object is not a AdHocFilter") # Setup the payload payload = { "busObID": business_object_id, "fields": field_list, "filters": filter_info, "includeAllFields": filter_object.include_all_fields } # Attempt to get the required business object result_search = requests.post( self.cache.get_uri("BusinessSearchResults"), json=payload, headers=self._get_authorisation_header()) if result_search.status_code == 200: # We have some records search_results = result_search.json() # Get how many rows were returned rows = search_results["totalRows"] # Loop through all the business object returned for business_object in search_results["businessObjects"]: field_result_list = [] # Loop through each field returned for field in business_object["fields"]: # Add to the field result list field_dict = { "name": field["name"], "value": field["value"], "displayName": field["displayName"], "fieldId": field["fieldId"] } field_result_list.append(field_dict) # Add the fields and business object id into a dictionary business_object_dict = { "busObRecId": business_object["busObRecId"], "fields": field_result_list } # Add the this dictionary to the array to be returned business_objects.append(business_object_dict) else: # There was a problem with the API call, generate an exception raise Exception( "Error searching for business objects '{}'. HTTP:{} '{}' - {}". format(filter_object.business_object_name, result_search.status_code, result_search.reason, result_search.text)) # Return the number of object and the array of objects return rows, business_objects