Package: zohocrmsdk5-0
LicenseCopyright (c) 2021, ZOHO CORPORATION PRIVATE LIMITED 
All rights reserved. 

Licensed under the Apache License, Version 2.0 (the "License"); 
you may not use this file except in compliance with the License. 
You may obtain a copy of the License at 

    http://www.apache.org/licenses/LICENSE-2.0 

Unless required by applicable law or agreed to in writing, software 
distributed under the License is distributed on an "AS IS" BASIS, 
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
See the License for the specific language governing permissions and 
limitations under the License.ZOHO CRM PYTHON SDK 5.0 for API version 5Table Of ContentsOverviewRegistering a Zoho ClientEnvironmental SetupIncluding the SDK in your projectPersistenceDataBase PersistenceFile PersistenceCustom PersistenceConfigurationInitializationClass HierarchyResponses And ExceptionsThreadingMultithreading in a Multi-User AppMulti-threading in a Single User AppSample CodeOverviewZoho CRM PYTHON SDK offers a way to create client Python applications that can be integrated with Zoho CRM.Registering a Zoho ClientSince Zoho CRM APIs are authenticated with OAuth2 standards, you should register your client app with Zoho. To register your app:Visit this pagehttps://api-console.zoho.comClick onADD CLIENT.Choose theClient Type.EnterClient Name,Client DomainorHomepage URLandAuthorized Redirect URIsthen clickCREATE.Your Client app will be created.Select the created OAuth client.Generate grant token by providing the necessary scopes, time duration (the duration for which the generated token is valid) and Scope Description.Environmental SetupPython SDK is installable throughpip.pipis a tool for dependency management in Python. SDK expects the following from the client app.Client app must have Python(version 3 and above)Python SDK must be installed into client app throughpip.Including the SDK in your projectInstallPythonfrompython.org(if not installed).InstallPython SDKNavigate to the workspace of your client app.Run the command below:pipinstallzohocrmsdk5_0The Python SDK will be installed in your client application.Token PersistenceToken persistence refers to storing and utilizing the authentication tokens that are provided by Zoho. Token persistence enables the SDK to automatically refresh the access token after initialization using the refresh token without the need for user intervention. There are three ways provided by the SDK in which persistence can be utilized. They are DataBase Persistence, File Persistence, and Custom Persistence. Please note that the default method of token persistence provided by the Zoho CRM SDK is File persistence.Table of ContentsDataBase PersistenceFile PersistenceCustom PersistenceImplementing OAuth PersistenceOnce the application is authorized, OAuth access and refresh tokens can be used for subsequent user data requests to Zoho CRM. Hence, they need to be persisted by the client app.The persistence is achieved by writing an implementation of the inbuilt Abstract Base ClassTokenStore, which has the following callback methods.find_token(self,token)- invoked before firing a request to fetch the saved tokens. This method should return implementation of inbuiltToken Classobject for the library to process it.save_token(self,token)- invoked after fetching access and refresh tokens from Zoho.delete_token(self, id))- The method to delete the particular stored token based on unique IDget_tokens(self)- The method to get the all the stored tokens.delete_tokens(self)- The method to delete all the stored tokens.find_token_by_id(self, id)- The method to retrieve the user's token details based on unique ID.Note:id is a String.token is an instance of Token Class.DataBase PersistenceIn case the user prefers to use default DataBase persistence,MySQLcan be used.Create a table in your Database with required columnsid varchar(10)user_name varchar(255)client_id varchar(255)client_secret varchar(255)refresh_token varchar(255)access_token varchar(255)grant_token varchar(255)expiry_time varchar(20)redirect_url varchar(255)api_domain varchar(255)Note:Custom database name and table name can be set in DBStore instance.MySQL QueryCREATETABLEoauthtoken(idvarchar(10)NOTNULL,user_namevarchar(255),client_idvarchar(255),client_secretvarchar(255),refresh_tokenvarchar(255),access_tokenvarchar(255),grant_tokenvarchar(255),expiry_timevarchar(20),redirect_urlvarchar(255),api_domainvarchar(255),primarykey(id))Create DBStore objectfromzohocrmsdk.src.com.zoho.api.authenticator.storeimportDBStore"""DBStore takes the following parameters1 -> DataBase host name. Default value "localhost"2 -> DataBase name. Default value "zohooauth"3 -> DataBase user name. Default value "root"4 -> DataBase password. Default value ""5 -> DataBase port number. Default value "3306"6->  DataBase table name . Default value "oauthtoken""""# store = DBStore()store=DBStore(host='host_name',database_name='database_name',user_name='user_name',password='password',port_number='port_number',table_name="table_name")File PersistenceIn case of File Persistence, the user can persist tokens in the local drive, by providing the absolute file path to the FileStore object.The File containsiduser_nameclient_idclient_secretrefresh_tokenaccess_tokengrant_tokenexpiry_timeredirect_urlapi_domainCreate FileStore objectfromzohocrmsdk.src.com.zoho.api.authenticator.storeimportFileStore"""FileStore takes the following parameter1 -> Absolute file path of the file to persist tokens"""store=FileStore(file_path='/Users/username/Documents/python_sdk_token.txt')Custom PersistenceUsers can create their own logic for storing and retrieving authentication tokens using the custom persistence technique. To use Custom Persistence, you must implementTokenStoreand override the methods.fromzohocrmsdk.src.com.zoho.api.authenticator.storeimportTokenStoreclassCustomStore(TokenStore):def__init__(self):passdeffind_token(self,token):"""Parameters:token (Token) : A Token (zohocrmsdk.src.com.zoho.api.authenticator.OAuthToken) class instance"""# Add code to get the tokenreturnNonedefsave_token(self,token):"""Parameters:token (Token) : A Token (zohocrmsdk.src.com.zoho.api.authenticator.OAuthToken) class instance"""# Add code to save the tokendefdelete_token(self,id):"""Parameters:id (String) : id representing the unique token id"""# Add code to delete the tokendefget_tokens(self):"""Returns:list: List of stored tokens"""# Add code to get all the stored tokensdefdelete_tokens(self):# Add code to delete all the stored tokensdeffind_token_by_id(self,id):"""The method to get id token details.Parameters:id (String) : A String id.Returns:Token : A Token class instance representing the id token details."""ConfigurationBefore you get started with creating your Python application, you need to register your client and authenticate the app with Zoho.Mandatory KeysOptional Keystokenloggerenvironmentstoresdk_configproxyresource_pathTheenvironmentkey contains the domain information to make API calls. Thetokenkey represents the OAuth info, including the clientID, clientSecret, grantToken, redirectURL, refreshToken or accessToken depending on the flow that you use. Refer to ##create an instance of OAuthToken## for more details.Configure the API environment which decides the domain and the URL to make API calls.fromzohocrmsdk.src.com.zoho.crm.api.dcimportUSDataCenter"""Configure the environmentwhich is of the pattern Domain.EnvironmentAvailable Domains: USDataCenter, EUDataCenter, INDataCenter, CNDataCenter, AUDataCenterAvailable Environments: PRODUCTION(), DEVELOPER(), SANDBOX()"""environment=USDataCenter.PRODUCTION()Create an instance ofOAuthTokenwith the information that you get after registering your Zoho client. In the context of token persistence, the grant token flow and refresh token flow involve storing and persisting the token. However, the access token flow does not involve token persistence and the access token is directly utilized for API calls. Depending on the tokens available with you, choose grantToken flow, refreshToken flow or accessToken flow.Use the following method forgrantToken flow :fromzohocrmsdk.src.com.zoho.api.authenticator.oauth_tokenimportOAuthTokentoken=OAuthToken(client_id="clientId",client_secret="clientSecret",grant_token="grantToken",redirect_url="redirectURL")Use the following method forrefreshToken flow :fromzohocrmsdk.src.com.zoho.api.authenticator.oauth_tokenimportOAuthTokentoken=OAuthToken(client_id="clientId",client_secret="clientSecret",refresh_token="refreshToken",redirect_url="redirectURL")Use the following method foraccessToken flow :fromzohocrmsdk.src.com.zoho.api.authenticator.oauth_tokenimportOAuthTokentoken=OAuthToken(access_token="accessToken")Use the following method forId flow :fromzohocrmsdk.src.com.zoho.api.authenticator.oauth_tokenimportOAuthTokentoken=OAuthToken(id="id")Create an instance ofLoggerClass to log exception and API information.fromzohocrmsdk.src.com.zoho.api.loggerimportLogger"""Create an instance of Logger Class that takes two parameters1 -> Level of the log messages to be logged. Can be configured by typing Logger.Levels "." and choose any level from the list displayed.2 -> Absolute file path, where messages need to be logged."""logger=Logger.get_instance(level=Logger.Levels.INFO,file_path="/Users/python_sdk_log.log")Create an instance ofTokenStoreto persist tokens, used for authenticating all the requests. By default, the SDK creates the sdk_tokens.txt created in the current working directory) to persist the tokens.Use the following method for DB Storefromzohocrmsdk.src.com.zoho.api.authenticator.storeimportDBStore"""DBStore takes the following parameters1 -> DataBase host name. Default value "localhost"2 -> DataBase name. Default value "zohooauth"3 -> DataBase user name. Default value "root"4 -> DataBase password. Default value ""5 -> DataBase port number. Default value "3306"6 -> DataBase table name. Default value "oauthtoken""""# store = DBStore()store=DBStore(host='host_name',database_name='database_name',user_name='user_name',password='password',port_number='port_number',table_name="table_name")Use the following method for File Storefromzohocrmsdk.src.com.zoho.api.authenticator.storeimportFileStore"""FileStore takes the following parameter1 -> Absolute file path of the file to persist tokens"""store=FileStore(file_path='/Users/python_sdk_tokens.txt')Use the following method for Custom Storetoken_store=CustomStore()Create an instance of SDKConfig containing SDK configurations.fromzohocrmsdk.src.com.zoho.crm.api.sdk_configimportSDKConfig"""By default, the SDK creates the SDKConfig instanceauto_refresh_fields (Default value is False)if True - all the modules' fields will be auto-refreshed in the background, every hour.if False - the fields will not be auto-refreshed in the background. The user can manually delete the file(s) or refresh the fields using methods from ModuleFieldsHandler(zohocrmsdk/src/com/zoho/crm/api/util/module_fields_handler.py)pick_list_validation (Default value is True)A boolean field that validates user input for a pick list field and allows or disallows the addition of a new value to the list.if True - the SDK validates the input. If the value does not exist in the pick list, the SDK throws an error.if False - the SDK does not validate the input and makes the API request with the user’s input to the pick listconnect_timeout (Default value is None)A  Float field to set connect timeoutread_timeout (Default value is None)A  Float field to set read timeout"""config=SDKConfig(auto_refresh_fields=True,pick_list_validation=False,connect_timeout=None,read_timeout=None)The path containing the absolute directory path to store user specific files containing module fields information. By default, the SDK stores the user-specific files within a folder in the current working directory.resource_path='/Users/python-app'Create an instance of RequestProxy containing the proxy properties of the user.fromzohocrmsdk.src.com.zoho.crm.api.request_proxyimportRequestProxy"""RequestProxy takes the following parameters1 -> Host2 -> Port Number3 -> User Name. Default value is None4 -> Password. Default value is an empty string"""# request_proxy = RequestProxy(host='proxyHost', port=80)request_proxy=RequestProxy(host='proxyHost',port=80,user='userName',password='password')Initializing the ApplicationInitialize the SDK using the following code.fromzohocrmsdk.src.com.zoho.crm.api.dcimportUSDataCenterfromzohocrmsdk.src.com.zoho.api.authenticator.storeimportDBStore,FileStorefromzohocrmsdk.src.com.zoho.api.loggerimportLoggerfromzohocrmsdk.src.com.zoho.crm.api.initializerimportInitializerfromzohocrmsdk.src.com.zoho.api.authenticator.oauth_tokenimportOAuthTokenfromzohocrmsdk.src.com.zoho.crm.api.sdk_configimportSDKConfigfromzohocrmsdk.src.com.zoho.crm.api.request_proxyimportRequestProxyclassSDKInitializer(object):@staticmethoddefinitialize():logger=Logger.get_instance(level=Logger.Levels.INFO,file_path='/Users/user_name/Documents/python_sdk_log.log')environment=USDataCenter.PRODUCTION()token=OAuthToken(client_id='clientId',client_secret='clientSecret',grant_token='grant_token',refresh_token="refresh_token",redirect_url='redirectURL',id="id")# store = FileStore(file_path='/Users/username/Documents/python_sdk_tokens.txt')# store = DBStore()store=DBStore(host='host_name',database_name='database_name',user_name='user_name',password='password',port_number='port_number',table_name="table_name")config=SDKConfig(auto_refresh_fields=True,pick_list_validation=False,connect_timeout=None,read_timeout=None)resource_path='/Users/python-app'# request_proxy = RequestProxy(host='host', port=8080)request_proxy=RequestProxy(host='host',port=8080,user='user',password='password')"""Call the static initialize method of Initializer class that takes the following arguments2 -> Environment instance3 -> Token instance4 -> TokenStore instance5 -> SDKConfig instance6 -> resource_path7 -> Logger instance. Default value is None8 -> RequestProxy instance. Default value is None"""Initializer.initialize(environment=environment,token=token,store=store,sdk_config=config,resource_path=resource_path,logger=logger,proxy=request_proxy)SDKInitializer.initialize()You can now access the functionalities of the SDK. Refer to the sample codes to make various API calls through the SDK.Class HierarchyResponses and ExceptionsAll SDK methods return an instance of the APIResponse class.After a successful API request, theget_object()method returns an instance of theResponseWrapper(forGET) or theActionWrapper(forPOST, PUT, DELETE)Whenever the API returns an error response, theget_object()returns an instance ofAPIExceptionclass.ResponseWrapper(forGETrequests) andActionWrapper(forPOST, PUT, DELETErequests) are the expected objects for Zoho CRM APIs’ responsesHowever, some specific operations have different expected objects, such as the following:Operations involving records in TagsRecordActionWrapperGetting Record Count for a specific Tag operationCountWrapperOperations involving BaseCurrencyBaseCurrencyActionWrapperLead convert operationConvertActionWrapperRetrieving Deleted records operationDeletedRecordsWrapperRecord image download operationFileBodyWrapperMassUpdate record operationsMassUpdateActionWrapperMassUpdateResponseWrapperGET RequestsTheget_object()returns an instance of one of the following classes, based on the return type.Most of the APIs follows theCommonStructure as below.TheResponseHandler classencompasses the followingResponseWrapper class(forapplication/jsonresponses)FileBodyWrapper class(for File download responses)APIException classSome of the APIs follow theParticularStructure as below.TheResponseHandler classencompasses the followingBodyWrapper class(forapplication/jsonresponses inbackupAPI , holds the instance ofBackup class)HistoryWrapper class(forapplication/jsonresponses inbackupAPI, holds the list of instances ofHistory classand instance ofInfo class)UrlsWrapper class(forapplication/jsonresponses inbackupAPI, holds the instance ofUrls class)BodyWrapper class(forapplication/jsonresponses inContactRolesAPI, holds the list of instances ofContactRole class)BodyWrapper class(forapplication/jsonresponses inCurrenciesAPI, holds the list of instances ofCurrency class)BodyWrapper class(forapplication/jsonresponses inCustomViewAPI, holds the list of instances ofCustomView classand instance ofInfo class)BodyWrapper class(forapplication/jsonresponses inDealContactrolesAPI, holds the list of instances ofData classand instance ofInfo class)BodyWrapper class(forapplication/jsonresponses inFieldMapDependencyAPI, holds the list of instances ofMapDependency classand instance ofInfo class)BodyWrapper class(forapplication/jsonresponses inFieldsAPI, holds the list of instances ofField class)BodyWrapper class(forapplication/jsonresponses inPipelineAPI, holds the list of instances ofPipeline class)ProfieWrapper class(forapplication/jsonresponses inProfilesAPI, holds the list of instances ofProfile classand instance ofInfo class)ConversionOptionsResponseWrapper class(forapplication/jsonresponses inRecordAPI, holds the instance ofConversionOption class)SourcesCountWrapper class(forapplication/jsonresponses inUserGroupsAPI, holds the List of instances ofSourceCount class)SourcesWrapper class(forapplication/jsonresponses inUsergroupsAPi, holds the List of instances ofSources classand instance ofInfo class)TheResponseWrapper classinBulkWriteAPI encompasses the followingBulkWriteResponse class(forapplication/jsonresponses)APIException classTheCountHandler classencompasses the followingCountWrapper class(forapplication/jsonresponses inRecordAPI, holds the Longcount)APIException classTheDeletedRecordsHandler classencompasses the followingDeletedRecordsWrapper class(forapplication/jsonresponses inRecordAPI, holds the list of instances ofDeletedRecord classand instance ofInfo class)APIException classTheDownloadHandler classencompasses the followingFileBodyWrapper class(for File download responses)APIException classTheMassUpdateResponseHandler classencompasses the followingMassUpdateResponseWrapper class(forapplication/jsonresponses inRecordAPI, holds the list of instances ofMassUpdateResponse class)APIException classTheMassUpdateResponse classencompasses of followingMassUpdate class(forapplication/jsonresponses)APIException classTheValidationHandler classinUserTerritoriesAPI encomposses the followingValidationWrapper class(forapplication/jsonresponses, holds the list of instances ofValidationGroup class)APIException classTheValidationGroup classinUserTerritoriesAPI encompasses the followingValidation classBulkValidation classPOST, PUT, DELETE RequestsThegetObject()of the returned APIResponse instance returns the response as follows.Most of the APIs follows theCommonStructure as.TheActionHandler classencompasses the followingActionWrapper class(forapplication/jsonresponses)APIException classTheActionWrapper classcontainsProperty/Propertiesthat may contain one/list ofActionResponse class.TheActionResponse classencompasses the followingSuccessResponse class(forapplication/jsonresponses)APIException classSome of the APIs follow theParticularStructure as.TheActionHandler classencompasses the followingActionWrapper class(forapplication/jsonresponses)APIException classTheActionWrapper classcontainsProperty/Propertiesthat may contain one/list ofActionResponse class.TheActionResponse classencompasses the followingBusinessHoursCreated class(forapplication/jsonresponses inBusinessHoursAPI)MassDeleteScheduled class(forapplication/jsonresponses inMassDeleteCVIDAPI)APIEXception classTheRecordActionHandler classencompasses the followingRecordActionWrapper class(forapplication/jsonresponses inTagsAPI, holds the list of instance ofRecordActionResponse class, BooleanwfScheduler, StringsuccessCountand BooleanlockedCount)APIException classRecordActionResponse classencompasses the followingRecordSuccessResponse class(forapplication/jsonresponses)APIException classTheActionHandler classinCurrenciesAPI encompasses the followingBaseCurrencyActionWrapper class(forapplication/jsonresponses)APIException classTheBaseCurrencyActionWrapper classcontainsProperty/Propertiesthat containBaseCurrencyActionResponse class.TheBaseCurrencyActionResponse classencompasses the followingSuccessResponse class(forapplication/jsonresponses)APIException classTheMassUpdateActionHandler classencompasses the followingMassUpdateActionWrapper class(forapplication/jsonresponses inRecordAPI, holds the list of instances ofMassUpdateActionResponse class)APIException classTheMassUpdateActionResponse classencompasses of followingMassUpdateSuccessResponse class(forapplication/jsonresponses)APIException classTheFileHandler classinRecordAPI encompasses the followingSuccessResponse class(forapplication/jsonresponses)APIException classTheSignActionHandler classinMailMergeAPI encompasses the followingSignActionWrapper class(forapplication/jsonresponses)APIException classTheDeleteActionHandler classencompasses the followingDeleteActionWrapper class(forapplication/jsonresponses inShareRecordsAPI, holds the instance ofDeleteActionResponse class)APIException classTheDeleteActionResponse classencompasses the followingSuccessResponse class(forapplication/jsonresponses)APIException classTheTransferActionHandler classinUserTerritoriesAPI encompasses the followingTransferActionWrapper class(froapplication/jsonresponses , holds the list of instances ofTransferActionResponse class)TheTransferActionResponse classencompasses the followingSuccessResponse class(forapplication/jsonresponses)APIException classTheActionResponse classinTerritoriesAPI encompasses the followingSuccess class(forapplication/jsonresponses)APIException classTheTransferPipelineActionHandler classinPipelineAPI encompasses the followingTransferPipelineActionWrapper class(forapplication/jsonresponses, holds the list of insatnces ofTransferPipelineActionResponse class)APIException classTheTransferPipelineActionResponse classinPipelineAPI encompasses the followingTransferPipelineSuccessResponse class(forapplication/jsonresponses)APIException classFor example, when you insert two records, and one of them was inserted successfully while the other one failed, the ActionWrapper will contain one instance each of the SuccessResponse and APIException classes.All other exceptions such as SDK anomalies and other unexpected behaviours are thrown under the SDKException class.Multithreading in a Multi-user AppMulti-threading for multi-users is achieved using Initializer's staticswitch_user()method.
switch_user() takes the value initialized previously for enviroment, token and sdk_config incase None is passed (or default value is passed). In case of request_proxy, if intended, the value has to be passed again else None(default value) will be taken.# without proxyInitializer.switch_user(environment=environment,token=token,sdk_config=sdk_config_instance)# with proxyInitializer.switch_user(environment=environment,token=token,sdk_config=sdk_config_instance,proxy=request_proxy)Here is a sample code to depict multi-threading for a multi-user app.importthreadingfromzohocrmsdk.src.com.zoho.crm.api.parameter_mapimportParameterMapfromzohocrmsdk.src.com.zoho.crm.api.dcimportUSDataCenter,EUDataCenterfromzohocrmsdk.src.com.zoho.api.authenticator.storeimportDBStorefromzohocrmsdk.src.com.zoho.api.loggerimportLoggerfromzohocrmsdk.src.com.zoho.crm.api.initializerimportInitializerfromzohocrmsdk.src.com.zoho.api.authenticator.oauth_tokenimportOAuthTokenfromzohocrmsdk.src.com.zoho.crm.api.recordimport*fromzohocrmsdk.src.com.zoho.crm.api.request_proxyimportRequestProxyfromzohocrmsdk.src.com.zoho.crm.api.sdk_configimportSDKConfigclassMultiThread(threading.Thread):def__init__(self,environment,token,module_api_name,sdk_config,proxy=None):super().__init__()self.environment=environmentself.token=tokenself.module_api_name=module_api_nameself.sdk_config=sdk_configself.proxy=proxydefrun(self):try:Initializer.switch_user(environment=self.environment,token=self.token,sdk_config=self.sdk_config,proxy=self.proxy)param_instance=ParameterMap()param_instance.add(GetRecordsParam.fields,"id")response=RecordOperations().get_records(self.module_api_name,param_instance)ifresponseisnotNone:print('Status Code: '+str(response.get_status_code()))ifresponse.get_status_code()in[204,304]:print('No Content'ifresponse.get_status_code()==204else'Not Modified')returnresponse_object=response.get_object()ifresponse_objectisnotNone:ifisinstance(response_object,ResponseWrapper):record_list=response_object.get_data()forrecordinrecord_list:forkey,valueinrecord.get_key_values().items():print(key+" : "+str(value))elifisinstance(response_object,APIException):print("Status: "+response_object.get_status().get_value())print("Code: "+response_object.get_code().get_value())print("Details")details=response_object.get_details()forkey,valueindetails.items():print(key+' : '+str(value))print("Message: "+response_object.get_message().get_value())exceptExceptionase:print(e)@staticmethoddefcall():logger=Logger.get_instance(level=Logger.Levels.INFO,file_path="/Users/user_name/Documents/python_sdk_log.log")token1=OAuthToken(client_id="clientId1",client_secret="clientSecret1",grant_token="Grant Token",refresh_token="refresh_token",id="id")environment1=USDataCenter.PRODUCTION()store=DBStore()sdk_config_1=SDKConfig(auto_refresh_fields=True,pick_list_validation=False)resource_path='/Users/user_name/Documents/python-app'user1_module_api_name='Leads'user2_module_api_name='Contacts'environment2=EUDataCenter.SANDBOX()sdk_config_2=SDKConfig(auto_refresh_fields=False,pick_list_validation=True)token2=OAuthToken(client_id="clientId2",client_secret="clientSecret2",grant_token="GRANT Token",refresh_token="refresh_token",redirect_url="redirectURL",id="id")request_proxy_user_2=RequestProxy("host",8080)Initializer.initialize(environment=environment1,token=token1,store=store,sdk_config=sdk_config_1,resource_path=resource_path,logger=logger)t1=MultiThread(environment1,token1,user1_module_api_name,sdk_config_1)t2=MultiThread(environment2,token2,user2_module_api_name,sdk_config_2,request_proxy_user_2)t1.start()t2.start()t1.join()t2.join()MultiThread.call()The program execution starts fromcall().The details ofuser1are given in the variables token1, environment1.Similarly, the details of another useruser2are given in the variables token2, environment2.For each user, an instance ofMultiThread classis created.When thestart()is called which in-turn invokes therun(),  the details of user1 are passed to theswitch_usermethod through theMultiThread object. Therefore, this creates a thread for user1.Similarly, When thestart()is invoked again,  the details of user2 are passed to theswitch_userfunction through theMultiThread object. Therefore, this creates a thread for user2.Multi-threading in a Single User AppHere is a sample code to depict multi-threading for a single-user app.importthreadingfromzohocrmsdk.src.com.zoho.crm.api.parameter_mapimportParameterMapfromzohocrmsdk.src.com.zoho.crm.api.dcimportUSDataCenterfromzohocrmsdk.src.com.zoho.api.authenticator.storeimportDBStorefromzohocrmsdk.src.com.zoho.api.loggerimportLoggerfromzohocrmsdk.src.com.zoho.crm.api.initializerimportInitializerfromzohocrmsdk.src.com.zoho.api.authenticator.oauth_tokenimportOAuthTokenfromzohocrmsdk.src.com.zoho.crm.api.sdk_configimportSDKConfigfromzohocrmsdk.src.com.zoho.crm.api.recordimport*classMultiThread(threading.Thread):def__init__(self,module_api_name):super().__init__()self.module_api_name=module_api_namedefrun(self):try:print("Calling Get Records for module: "+self.module_api_name)param_instance=ParameterMap()param_instance.add(GetRecordsParam.fields,"id")response=RecordOperations().get_records(self.module_api_name,param_instance)ifresponseisnotNone:print('Status Code: '+str(response.get_status_code()))ifresponse.get_status_code()in[204,304]:print('No Content'ifresponse.get_status_code()==204else'Not Modified')returnresponse_object=response.get_object()ifresponse_objectisnotNone:ifisinstance(response_object,ResponseWrapper):record_list=response_object.get_data()forrecordinrecord_list:forkey,valueinrecord.get_key_values().items():print(key+" : "+str(value))elifisinstance(response_object,APIException):print("Status: "+response_object.get_status().get_value())print("Code: "+response_object.get_code().get_value())print("Details")details=response_object.get_details()forkey,valueindetails.items():print(key+' : '+str(value))print("Message: "+response_object.get_message().get_value())exceptExceptionase:print(e)@staticmethoddefcall():logger=Logger.get_instance(level=Logger.Levels.INFO,file_path="/Users/user_name/Documents/python_sdk_log.log")token=OAuthToken(client_id="clientId",client_secret="clientSecret",grant_token="grant_token",refresh_token="refresh_token",redirect_url="redirectURL",id="id")environment=USDataCenter.PRODUCTION()store=DBStore()sdk_config=SDKConfig()resource_path='/Users/user_name/Documents/python-app'Initializer.initialize(environment=environment,token=token,store=store,sdk_config=sdk_config,resource_path=resource_path,logger=logger)t1=MultiThread('Leads')t2=MultiThread('Quotes')t1.start()t2.start()t1.join()t2.join()MultiThread.call()The program execution starts fromcall()where the SDK is initialized with the details of the user.When thestart()is called which in-turn invokes the run(),  the module_api_name is switched through the MultiThread object. Therefore, this creates a thread for the particular MultiThread instance.SDK Sample codefromdatetimeimportdatetimefromzohocrmsdk.src.com.zoho.api.authenticatorimportOAuthTokenfromzohocrmsdk.src.com.zoho.crm.apiimportInitializer,ParameterMap,HeaderMapfromzohocrmsdk.src.com.zoho.crm.api.dcimportUSDataCenterfromzohocrmsdk.src.com.zoho.crm.api.recordimportRecordOperations,GetRecordsParam,GetRecordsHeader,ResponseWrapper,Record,APIExceptionclassGetRecord:@staticmethoddefinitialize():environment=USDataCenter.PRODUCTION()token=OAuthToken(client_id="clientID",client_secret="clientSecret",grant_token="grantToken")Initializer.initialize(environment,token)@staticmethoddefget_records(module_api_name):"""This method is used to get all the records of a module and print the response.:param module_api_name: The API Name of the module to fetch records""""""examplemodule_api_name = 'Leads'"""record_operations=RecordOperations()param_instance=ParameterMap()param_instance.add(GetRecordsParam.page,1)param_instance.add(GetRecordsParam.per_page,120)field_names=["Company","Email"]forfieldinfield_names:param_instance.add(GetRecordsParam.fields,field)header_instance=HeaderMap()header_instance.add(GetRecordsHeader.if_modified_since,datetime.fromisoformat('2020-01-01T00:00:00+05:30'))response=record_operations.get_records(module_api_name,param_instance,header_instance)ifresponseisnotNone:print('Status Code: '+str(response.get_status_code()))ifresponse.get_status_code()in[204,304]:print('No Content'ifresponse.get_status_code()==204else'Not Modified')returnresponse_object=response.get_object()ifresponse_objectisnotNone:ifisinstance(response_object,ResponseWrapper):record_list=response_object.get_data()forrecordinrecord_list:print("Record ID: "+str(record.get_id()))created_by=record.get_created_by()ifcreated_byisnotNone:print("Record Created By - Name: "+created_by.get_name())print("Record Created By - ID: "+str(created_by.get_id()))print("Record Created By - Email: "+created_by.get_email())print("Record CreatedTime: "+str(record.get_created_time()))ifrecord.get_modified_time()isnotNone:print("Record ModifiedTime: "+str(record.get_modified_time()))modified_by=record.get_modified_by()ifmodified_byisnotNone:print("Record Modified By - Name: "+modified_by.get_name())print("Record Modified By - ID: "+str(modified_by.get_id()))print("Record Modified By - Email: "+modified_by.get_email())tags=record.get_tag()iftagsisnotNone:fortagintags:print("Record Tag Name: "+tag.get_name())print("Record Tag ID: "+str(tag.get_id()))print("Record Field Value: "+str(record.get_key_value('Last_Name')))print('Record KeyValues: ')forkey,valueinrecord.get_key_values().items():print(key+" : "+str(value))elifisinstance(response_object,APIException):print("Status: "+response_object.get_status().get_value())print("Code: "+response_object.get_code().get_value())print("Details")details=response_object.get_details()forkey,valueindetails.items():print(key+' : '+str(value))print("Message: "+response_object.get_message().get_value())GetRecord.initialize()GetRecord.get_records("Leads")