zohocrmsdk5-0


Namezohocrmsdk5-0 JSON
Version 2.0.0 PyPI version JSON
download
home_pagehttps://github.com/zoho/zohocrm-python-sdk-5.0
SummaryZoho CRM SDK for ZOHO CRM 5 APIs
upload_time2024-04-17 10:22:59
maintainerNone
docs_urlNone
authorZoho CRM API Team
requires_pythonNone
licenseApache Software License, Version 2.0, http://www.apache.org/licenses/LICENSE-2.0
keywords development zoho crm api zcrmsdk zohocrmsdksdk zcrm zohocrmsdk5_0
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            License
=======

    Copyright (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 5

## Table Of Contents

* [Overview](#overview)
* [Registering a Zoho Client](#registering-a-zoho-client)
* [Environmental Setup](#environmental-setup)
* [Including the SDK in your project](#including-the-sdk-in-your-project)
* [Persistence](#token-persistence)
  * [DataBase Persistence](#database-persistence)
  * [File Persistence](#file-persistence)
  * [Custom Persistence](#custom-persistence)
* [Configuration](#configuration)
* [Initialization](#initializing-the-application)
* [Class Hierarchy](#class-hierarchy)
* [Responses And Exceptions](#responses-and-exceptions)
* [Threading](#threading-in-the-python-sdk)
  * [Multithreading in a Multi-User App](#multithreading-in-a-multi-user-app)
  * [Multi-threading in a Single User App](#multi-threading-in-a-single-user-app)
* [Sample Code](#sdk-sample-code)
## Overview

Zoho CRM PYTHON SDK offers a way to create client Python applications that can be integrated with Zoho CRM.

## Registering a Zoho Client

Since Zoho CRM APIs are authenticated with OAuth2 standards, you should register your client app with Zoho. To register your app:

- Visit this page [https://api-console.zoho.com](https://api-console.zoho.com)

- Click on `ADD CLIENT`.

- Choose the `Client Type`.

- Enter **Client Name**, **Client Domain** or **Homepage URL** and **Authorized Redirect URIs** then click `CREATE`.

- 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 Setup

Python SDK is installable through **pip**. **pip** is 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 through **pip**.

## Including the SDK in your project

- Install **Python** from [python.org](https://www.python.org/downloads/) (if not installed).

- Install **Python SDK**
    - Navigate to the workspace of your client app.
    - Run the command below:

    ```sh
    pip install zohocrmsdk5_0
    ```
- The Python SDK will be installed in your client application.

## Token Persistence

Token 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 Contents

- [DataBase Persistence](#database-persistence)

- [File Persistence](#file-persistence)

- [Custom Persistence](#custom-persistence)

### Implementing OAuth Persistence

Once 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 Class **[TokenStore](zohocrmsdk/src/com/zoho/api/authenticator/store/token_store.py)**, which has the following callback methods.

- **find_token(self, [token](zohocrmsdk/src/com/zoho/api/authenticator/token.py))** - invoked before firing a request to fetch the saved tokens. This method should return implementation of inbuilt **Token Class** object for the library to process it.

- **save_token(self, [token](zohocrmsdk/src/com/zoho/api/authenticator/token.py))** - invoked after fetching access and refresh tokens from Zoho.

- **delete_token(self, id))** - The method to delete the particular stored token based on unique ID

- **get_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 Persistence

In case the user prefers to use default DataBase persistence, **MySQL** can be used.

- Create a table in your Database with required columns

  - id 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 Query

```sql
CREATE TABLE  oauthtoken (
  id varchar(10) NOT NULL,
  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),
  primary key (id)
)
```

#### Create DBStore object

```python
from zohocrmsdk.src.com.zoho.api.authenticator.store import DBStore
"""
DBStore takes the following parameters
1 -> 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 Persistence

In 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 contains
    - id 
    
    - user_name

    - client_id
    
    - client_secret

    - refresh_token

    - access_token

    - grant_token

    - expiry_time
    
    - redirect_url

    - api_domain

#### Create FileStore object

```python
from zohocrmsdk.src.com.zoho.api.authenticator.store import FileStore
"""
FileStore takes the following parameter
1 -> Absolute file path of the file to persist tokens
"""
store = FileStore(file_path='/Users/username/Documents/python_sdk_token.txt')
```

### Custom Persistence
Users can create their own logic for storing and retrieving authentication tokens using the custom persistence technique. To use Custom Persistence, you must implement **[TokenStore](zohocrmsdk/src/com/zoho/api/authenticator/store/token_store.py)** and override the methods.

```python
from zohocrmsdk.src.com.zoho.api.authenticator.store import TokenStore


class CustomStore(TokenStore):

    def __init__(self):
        pass

    def find_token(self, token):

        """
        Parameters:
            token (Token) : A Token (zohocrmsdk.src.com.zoho.api.authenticator.OAuthToken) class instance
        """

        # Add code to get the token
        return None

    def save_token(self, token):

        """
        Parameters:
            token (Token) : A Token (zohocrmsdk.src.com.zoho.api.authenticator.OAuthToken) class instance
        """

        # Add code to save the token

    def delete_token(self, id):

        """
        Parameters:
            id (String) : id representing the unique token id
        """

        # Add code to delete the token
    
    def get_tokens(self):

        """
        Returns:
            list: List of stored tokens
        """

        # Add code to get all the stored tokens
    
    def delete_tokens(self):
    
        # Add code to delete all the stored tokens
    
    def find_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.
        """
```

## Configuration

Before you get started with creating your Python application, you need to register your client and authenticate the app with Zoho.

| Mandatory Keys | Optional Keys |
|:---------------| :------------ |
| token          | logger        |
| environment    | store         |
|                | sdk_config    |
|                | proxy         |
|                | resource_path |
----

The **environment** key contains the domain information to make API calls. The **token** key 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.
  ```python
  from zohocrmsdk.src.com.zoho.crm.api.dc import USDataCenter

  """
  Configure the environment
  which is of the pattern Domain.Environment
  Available Domains: USDataCenter, EUDataCenter, INDataCenter, CNDataCenter, AUDataCenter
  Available Environments: PRODUCTION(), DEVELOPER(), SANDBOX()
  """
  environment = USDataCenter.PRODUCTION()
  ```

- Create an instance of **OAuthToken** with 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 for **grantToken flow :**
  ```python
  from zohocrmsdk.src.com.zoho.api.authenticator.oauth_token import OAuthToken
  token = OAuthToken(client_id= "clientId", client_secret="clientSecret", grant_token="grantToken", redirect_url="redirectURL")
  ```
  - Use the following method for **refreshToken flow :**
  ```python
  from zohocrmsdk.src.com.zoho.api.authenticator.oauth_token import OAuthToken
  token = OAuthToken(client_id= "clientId", client_secret="clientSecret", refresh_token="refreshToken", redirect_url="redirectURL")
  ```
  - Use the following method for **accessToken flow :**
  ```python
  from zohocrmsdk.src.com.zoho.api.authenticator.oauth_token import OAuthToken
  token = OAuthToken(access_token="accessToken")
  ```
  - Use the following method for **Id flow :**
  ```python
  from zohocrmsdk.src.com.zoho.api.authenticator.oauth_token import OAuthToken
  token = OAuthToken(id="id")
  ```

- Create an instance of **Logger** Class to log exception and API information.
  ```python
    from zohocrmsdk.src.com.zoho.api.logger import Logger
  
    """
    Create an instance of Logger Class that takes two parameters
    1 -> 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 of **TokenStore** to 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 Store
  ```python
  from zohocrmsdk.src.com.zoho.api.authenticator.store import DBStore
  """
  DBStore takes the following parameters
  1 -> 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 Store
  ```python
  from zohocrmsdk.src.com.zoho.api.authenticator.store import FileStore
  """
  FileStore takes the following parameter
  1 -> Absolute file path of the file to persist tokens
  """
  store = FileStore(file_path='/Users/python_sdk_tokens.txt')
  ```
  - Use the following method for Custom Store
  ```python
  token_store = CustomStore()
  ```
- Create an instance of SDKConfig containing SDK configurations.
  ```python
  from zohocrmsdk.src.com.zoho.crm.api.sdk_config import SDKConfig

  """
  By default, the SDK creates the SDKConfig instance
  auto_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 list
  
  connect_timeout (Default value is None) 
    A  Float field to set connect timeout
  
  read_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.
  ```python
  resource_path = '/Users/python-app'
  ```

- Create an instance of RequestProxy containing the proxy properties of the user.
    ```python
    from zohocrmsdk.src.com.zoho.crm.api.request_proxy import RequestProxy
    """
    RequestProxy takes the following parameters
    1 -> Host
    2 -> Port Number
    3 -> User Name. Default value is None
    4 -> 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 Application

Initialize the SDK using the following code.

```python
from zohocrmsdk.src.com.zoho.crm.api.dc import USDataCenter
from zohocrmsdk.src.com.zoho.api.authenticator.store import DBStore, FileStore
from zohocrmsdk.src.com.zoho.api.logger import Logger
from zohocrmsdk.src.com.zoho.crm.api.initializer import Initializer
from zohocrmsdk.src.com.zoho.api.authenticator.oauth_token import OAuthToken
from zohocrmsdk.src.com.zoho.crm.api.sdk_config import SDKConfig
from zohocrmsdk.src.com.zoho.crm.api.request_proxy import RequestProxy


class SDKInitializer(object):

    @staticmethod
    def initialize():
        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 arguments
        2 -> Environment instance
        3 -> Token instance
        4 -> TokenStore instance
        5 -> SDKConfig instance
        6 -> resource_path
        7 -> Logger instance. Default value is None
        8 -> 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 Hierarchy

![classdiagram](class_hierarchy.png)

## Responses and Exceptions

All SDK methods return an instance of the APIResponse class.

After a successful API request, the **get_object()** method returns an instance of the **ResponseWrapper** (for **GET**) or the **ActionWrapper** (for **POST, PUT, DELETE**)

Whenever the API returns an error response, the **get_object()** returns an instance of **APIException** class.

**ResponseWrapper** (for **GET** requests) and **ActionWrapper** (for **POST, PUT, DELETE** requests) are the expected objects for Zoho CRM APIs’ responses

However, some specific operations have different expected objects, such as the following:

- Operations involving records in Tags
    - **RecordActionWrapper**

- Getting Record Count for a specific Tag operation
    - **CountWrapper**

- Operations involving BaseCurrency
    - **BaseCurrencyActionWrapper**

- Lead convert operation
    - **ConvertActionWrapper**

- Retrieving Deleted records operation
    - **DeletedRecordsWrapper**

- Record image download operation
    - **FileBodyWrapper**

- MassUpdate record operations
    - **MassUpdateActionWrapper**
    - **MassUpdateResponseWrapper**
  
### GET Requests

- The **get_object()** returns an instance of one of the following classes, based on the return type.

    - Most of the APIs follows the **Common** Structure as below.

  - The **ResponseHandler class** encompasses the following
    - **ResponseWrapper class** (for **application/json** responses)
    - **FileBodyWrapper class** (for File download responses)
    - **APIException class**


- Some of the APIs follow the **Particular** Structure as below.

  - The **ResponseHandler class** encompasses the following
    - **BodyWrapper class** (for **application/json** responses in **backup** API , holds the instance of **Backup class**)
    - **HistoryWrapper class** (for **application/json** responses in **backup** API, holds the list of instances of **History class** and instance of **Info class**)
    - **UrlsWrapper class** (for **application/json** responses in **backup** API, holds the instance of **Urls class**)
    - **BodyWrapper class** (for **application/json** responses in **ContactRoles** API, holds the list of instances of **ContactRole class**)
    - **BodyWrapper class** (for **application/json** responses in **Currencies** API, holds the list of instances of **Currency class**)
    - **BodyWrapper class** (for **application/json** responses in **CustomView** API, holds the list of instances of **CustomView class** and instance of **Info class** )
    - **BodyWrapper class** (for **application/json** responses in **DealContactroles** API, holds the list of instances of **Data class** and instance of **Info class** )
    - **BodyWrapper class** (for **application/json** responses in **FieldMapDependency** API, holds the list of instances of **MapDependency class** and instance of **Info class** )
    - **BodyWrapper class** (for **application/json** responses in **Fields** API, holds the list of instances of **Field class**)
    - **BodyWrapper class** (for **application/json** responses in **Pipeline** API, holds the list of instances of **Pipeline class**)
    - **ProfieWrapper class** (for **application/json** responses in **Profiles** API, holds the list of instances of **Profile class** and instance of **Info class**)
    - **ConversionOptionsResponseWrapper class**  (for **application/json** responses in **Record** API, holds the instance of **ConversionOption class**)
    - **SourcesCountWrapper class** (for **application/json** responses in **UserGroups** API, holds the List of instances of **SourceCount class**)
    - **SourcesWrapper class** (for **application/json** responses in **Usergroups** APi, holds the List of instances of **Sources class** and instance of **Info class**)


  - The **ResponseWrapper class** in **BulkWrite** API encompasses the following
    - **BulkWriteResponse class** (for **application/json** responses)
    - **APIException class**

  - The **CountHandler class** encompasses the following
    - **CountWrapper class** (for **application/json** responses in **Record** API, holds the Long **count**)
    - **APIException class**

  - The **DeletedRecordsHandler class** encompasses the following
    - **DeletedRecordsWrapper class** (for **application/json** responses in **Record** API, holds the list of instances of **DeletedRecord class** and instance of **Info class**)
    - **APIException class**

  - The **DownloadHandler class** encompasses the following
    - **FileBodyWrapper class** (for File download responses)
    - **APIException class**

  - The **MassUpdateResponseHandler class** encompasses the following
    - **MassUpdateResponseWrapper class** (for **application/json** responses in **Record** API, holds the list of instances of **MassUpdateResponse class**)
    - **APIException class**

  - The **MassUpdateResponse class** encompasses of following
    - **MassUpdate class** (for **application/json** responses)
    - **APIException class**

  - The **ValidationHandler class** in **UserTerritories** API encomposses the following
    - **ValidationWrapper class** (for **application/json** responses, holds the list of instances of **ValidationGroup class**)
    - **APIException class**

  - The **ValidationGroup class** in **UserTerritories** API encompasses the following
    - **Validation class**
    - **BulkValidation class**

### POST, PUT, DELETE Requests

- The **getObject()** of the returned APIResponse instance returns the response as follows.

- Most of the APIs follows the **Common** Structure as.

  - The **ActionHandler class** encompasses the following
    - **ActionWrapper class** (for **application/json** responses)
    - **APIException class**

  - The **ActionWrapper class** contains **Property/Properties** that may contain one/list of **ActionResponse class**.

  - The **ActionResponse class** encompasses the following
    - **SuccessResponse class** (for **application/json** responses)
    - **APIException class**

- Some of the APIs follow the **Particular** Structure as.

  - The **ActionHandler class** encompasses the following
    - **ActionWrapper class** (for **application/json** responses)
    - **APIException class**

  - The **ActionWrapper class** contains **Property/Properties** that may contain one/list of **ActionResponse class**.

  - The **ActionResponse class** encompasses the following
    - **BusinessHoursCreated class** (for **application/json** responses in **BusinessHours** API)
    - **MassDeleteScheduled class** (for **application/json** responses in **MassDeleteCVID** API)
    - **APIEXception class**

  - The **RecordActionHandler class** encompasses the following
    - **RecordActionWrapper class** (for **application/json** responses in **Tags** API, holds the list of instance of **RecordActionResponse class**, Boolean **wfScheduler**, String **successCount** and Boolean **lockedCount**)
    - **APIException class**

  - **RecordActionResponse class** encompasses the following
    - **RecordSuccessResponse class** (for **application/json** responses)
    - **APIException class**

  - The **ActionHandler class** in **Currencies** API encompasses the following
    - **BaseCurrencyActionWrapper class** (for **application/json** responses)
    - **APIException class**

  - The **BaseCurrencyActionWrapper class** contains **Property/Properties** that contain **BaseCurrencyActionResponse class**.

  - The **BaseCurrencyActionResponse class** encompasses the following
    - **SuccessResponse class** (for **application/json** responses)
    - **APIException class**

  - The **MassUpdateActionHandler class** encompasses the following
    - **MassUpdateActionWrapper class** (for **application/json** responses in **Record** API, holds the list of instances of **MassUpdateActionResponse class**)
    - **APIException class**

  - The **MassUpdateActionResponse class** encompasses of following
    - **MassUpdateSuccessResponse class** (for **application/json** responses)
    - **APIException class**

  - The **FileHandler class** in **Record** API encompasses the following 
    - **SuccessResponse class** (for **application/json** responses)
    - **APIException class**

  - The **SignActionHandler class** in **MailMerge** API encompasses the following
    - **SignActionWrapper class** (for **application/json** responses)
    - **APIException class**
    
  - The **DeleteActionHandler class** encompasses the following
    - **DeleteActionWrapper class** (for **application/json** responses in **ShareRecords** API, holds the instance of **DeleteActionResponse class**)
    - **APIException class**
  - The **DeleteActionResponse class** encompasses the following
    - **SuccessResponse class** (for **application/json** responses)
    - **APIException class**

  - The **TransferActionHandler class** in **UserTerritories** API encompasses the following
    - **TransferActionWrapper class** (fro **application/json** responses , holds the list of instances of **TransferActionResponse class**)

  - The **TransferActionResponse class** encompasses the following 
    - **SuccessResponse class** (for **application/json** responses)
    - **APIException class**

  - The **ActionResponse class** in **Territories** API encompasses the following
    - **Success class** (for **application/json** responses)
    - **APIException class**

  - The **TransferPipelineActionHandler class** in **Pipeline** API encompasses the following
    - **TransferPipelineActionWrapper class** (for **application/json** responses, holds the list of insatnces of **TransferPipelineActionResponse class**)
    - **APIException class**
  
  - The **TransferPipelineActionResponse class** in **Pipeline** API encompasses the following
    - **TransferPipelineSuccessResponse class** (for **application/json** responses)
    - **APIException class**

For 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 App

Multi-threading for multi-users is achieved using Initializer's static **switch_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.

```python
# without proxy
Initializer.switch_user(environment=environment, token=token, sdk_config=sdk_config_instance)

# with proxy
Initializer.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.

```python
import threading
from zohocrmsdk.src.com.zoho.crm.api.parameter_map import ParameterMap 
from zohocrmsdk.src.com.zoho.crm.api.dc import USDataCenter, EUDataCenter
from zohocrmsdk.src.com.zoho.api.authenticator.store import DBStore
from zohocrmsdk.src.com.zoho.api.logger import Logger
from zohocrmsdk.src.com.zoho.crm.api.initializer import Initializer
from zohocrmsdk.src.com.zoho.api.authenticator.oauth_token import OAuthToken
from zohocrmsdk.src.com.zoho.crm.api.record import *
from zohocrmsdk.src.com.zoho.crm.api.request_proxy import RequestProxy
from zohocrmsdk.src.com.zoho.crm.api.sdk_config import SDKConfig


class MultiThread(threading.Thread):

    def __init__(self, environment, token, module_api_name, sdk_config, proxy=None):
        super().__init__()
        self.environment = environment
        self.token = token
        self.module_api_name = module_api_name
        self.sdk_config = sdk_config
        self.proxy = proxy

    def run(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)
            if response is not None:
                print('Status Code: ' + str(response.get_status_code()))
                if response.get_status_code() in [204, 304]:
                    print('No Content' if response.get_status_code() == 204 else 'Not Modified')
                    return
                response_object = response.get_object()
                if response_object is not None:
                    if isinstance(response_object, ResponseWrapper):
                        record_list = response_object.get_data()
                        for record in record_list:
                            for key, value in record.get_key_values().items():
                                print(key + " : " + str(value))
                    elif isinstance(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()
                        for key, value in details.items():
                            print(key + ' : ' + str(value))
                        print("Message: " + response_object.get_message().get_value())
        except Exception as e:
            print(e)
    @staticmethod
    def call():
        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 from **call()**.

- The details of **user1** are given in the variables token1, environment1.

- Similarly, the details of another user **user2** are given in the variables token2, environment2.

- For each user, an instance of **MultiThread class** is created.

- When the **start()** is called which in-turn invokes the **run()**,  the details of user1 are passed to the **switch_user** method through the **MultiThread object**. Therefore, this creates a thread for user1.

- Similarly, When the **start()** is invoked again,  the details of user2 are passed to the **switch_user** function through the **MultiThread object**. Therefore, this creates a thread for user2.

### Multi-threading in a Single User App

Here is a sample code to depict multi-threading for a single-user app.

```python
import threading
from zohocrmsdk.src.com.zoho.crm.api.parameter_map import ParameterMap 
from zohocrmsdk.src.com.zoho.crm.api.dc import USDataCenter
from zohocrmsdk.src.com.zoho.api.authenticator.store import DBStore
from zohocrmsdk.src.com.zoho.api.logger import Logger
from zohocrmsdk.src.com.zoho.crm.api.initializer import Initializer
from zohocrmsdk.src.com.zoho.api.authenticator.oauth_token import OAuthToken
from zohocrmsdk.src.com.zoho.crm.api.sdk_config import SDKConfig
from zohocrmsdk.src.com.zoho.crm.api.record import *


class MultiThread(threading.Thread):

    def __init__(self, module_api_name):
        super().__init__()
        self.module_api_name = module_api_name

    def run(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)
            if response is not None:
                print('Status Code: ' + str(response.get_status_code()))
                if response.get_status_code() in [204, 304]:
                    print('No Content' if response.get_status_code() == 204 else 'Not Modified')
                    return
                response_object = response.get_object()
                if response_object is not None:
                    if isinstance(response_object, ResponseWrapper):
                        record_list = response_object.get_data()
                        for record in record_list:
                            for key, value in record.get_key_values().items():
                                print(key + " : " + str(value))
                    elif isinstance(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()
                        for key, value in details.items():
                            print(key + ' : ' + str(value))
                        print("Message: " + response_object.get_message().get_value())
        except Exception as e:
            print(e)
            
    @staticmethod
    def call():
        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 from **call()** where the SDK is initialized with the details of the user.

- When the **start()** 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 code

```python
from datetime import datetime
from zohocrmsdk.src.com.zoho.api.authenticator import OAuthToken
from zohocrmsdk.src.com.zoho.crm.api import Initializer, ParameterMap, HeaderMap
from zohocrmsdk.src.com.zoho.crm.api.dc import USDataCenter
from zohocrmsdk.src.com.zoho.crm.api.record import RecordOperations, GetRecordsParam, GetRecordsHeader, ResponseWrapper, Record, APIException


class GetRecord:
    @staticmethod
    def initialize():
        environment = USDataCenter.PRODUCTION()
        token = OAuthToken(client_id="clientID", client_secret="clientSecret", grant_token="grantToken")
        Initializer.initialize(environment, token)
        
    @staticmethod
    def get_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
        """
        """
        example
        module_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"]
        for field in field_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)
        if response is not None:
            print('Status Code: ' + str(response.get_status_code()))
            if response.get_status_code() in [204, 304]:
                print('No Content' if response.get_status_code() == 204 else 'Not Modified')
                return
            response_object = response.get_object()
            if response_object is not None:
                if isinstance(response_object, ResponseWrapper):
                    record_list = response_object.get_data()
                    for record in record_list:
                        print("Record ID: " + str(record.get_id()))
                        created_by = record.get_created_by()
                        if created_by is not None:
                            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()))
                        if record.get_modified_time() is not None:
                            print("Record ModifiedTime: " + str(record.get_modified_time()))
                        modified_by = record.get_modified_by()
                        if modified_by is not None:
                            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()
                        if tags is not None:
                            for tag in tags:
                                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: ')
                        for key, value in record.get_key_values().items():
                            print(key + " : " + str(value))
                elif isinstance(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()
                    for key, value in details.items():
                        print(key + ' : ' + str(value))
                    print("Message: " + response_object.get_message().get_value())

            
GetRecord.initialize()
GetRecord.get_records("Leads")
```

            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/zoho/zohocrm-python-sdk-5.0",
    "name": "zohocrmsdk5-0",
    "maintainer": null,
    "docs_url": null,
    "requires_python": null,
    "maintainer_email": null,
    "keywords": "development, zoho, crm, api, zcrmsdk, zohocrmsdksdk, zcrm, zohocrmsdk5_0",
    "author": "Zoho CRM API Team",
    "author_email": "support@zohocrm.com",
    "download_url": "https://files.pythonhosted.org/packages/8f/50/9a1a72aa584398498c370d2a715b550c345fc84961fc2eef81e26fc2624c/zohocrmsdk5_0-2.0.0.tar.gz",
    "platform": null,
    "description": "License\n=======\n\n    Copyright (c) 2021, ZOHO CORPORATION PRIVATE LIMITED \n    All rights reserved. \n\n    Licensed under the Apache License, Version 2.0 (the \"License\"); \n    you may not use this file except in compliance with the License. \n    You may obtain a copy of the License at \n    \n        http://www.apache.org/licenses/LICENSE-2.0 \n    \n    Unless required by applicable law or agreed to in writing, software \n    distributed under the License is distributed on an \"AS IS\" BASIS, \n    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. \n    See the License for the specific language governing permissions and \n    limitations under the License.\n\n# ZOHO CRM PYTHON SDK 5.0 for API version 5\n\n## Table Of Contents\n\n* [Overview](#overview)\n* [Registering a Zoho Client](#registering-a-zoho-client)\n* [Environmental Setup](#environmental-setup)\n* [Including the SDK in your project](#including-the-sdk-in-your-project)\n* [Persistence](#token-persistence)\n  * [DataBase Persistence](#database-persistence)\n  * [File Persistence](#file-persistence)\n  * [Custom Persistence](#custom-persistence)\n* [Configuration](#configuration)\n* [Initialization](#initializing-the-application)\n* [Class Hierarchy](#class-hierarchy)\n* [Responses And Exceptions](#responses-and-exceptions)\n* [Threading](#threading-in-the-python-sdk)\n  * [Multithreading in a Multi-User App](#multithreading-in-a-multi-user-app)\n  * [Multi-threading in a Single User App](#multi-threading-in-a-single-user-app)\n* [Sample Code](#sdk-sample-code)\n## Overview\n\nZoho CRM PYTHON SDK offers a way to create client Python applications that can be integrated with Zoho CRM.\n\n## Registering a Zoho Client\n\nSince Zoho CRM APIs are authenticated with OAuth2 standards, you should register your client app with Zoho. To register your app:\n\n- Visit this page [https://api-console.zoho.com](https://api-console.zoho.com)\n\n- Click on `ADD CLIENT`.\n\n- Choose the `Client Type`.\n\n- Enter **Client Name**, **Client Domain** or **Homepage URL** and **Authorized Redirect URIs** then click `CREATE`.\n\n- Your Client app will be created.\n\n- Select the created OAuth client.\n\n- Generate grant token by providing the necessary scopes, time duration (the duration for which the generated token is valid) and Scope Description.\n\n## Environmental Setup\n\nPython SDK is installable through **pip**. **pip** is a tool for dependency management in Python. SDK expects the following from the client app.\n\n- Client app must have Python(version 3 and above)\n\n- Python SDK must be installed into client app through **pip**.\n\n## Including the SDK in your project\n\n- Install **Python** from [python.org](https://www.python.org/downloads/) (if not installed).\n\n- Install **Python SDK**\n    - Navigate to the workspace of your client app.\n    - Run the command below:\n\n    ```sh\n    pip install zohocrmsdk5_0\n    ```\n- The Python SDK will be installed in your client application.\n\n## Token Persistence\n\nToken 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.\n\n### Table of Contents\n\n- [DataBase Persistence](#database-persistence)\n\n- [File Persistence](#file-persistence)\n\n- [Custom Persistence](#custom-persistence)\n\n### Implementing OAuth Persistence\n\nOnce 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.\n\nThe persistence is achieved by writing an implementation of the inbuilt Abstract Base Class **[TokenStore](zohocrmsdk/src/com/zoho/api/authenticator/store/token_store.py)**, which has the following callback methods.\n\n- **find_token(self, [token](zohocrmsdk/src/com/zoho/api/authenticator/token.py))** - invoked before firing a request to fetch the saved tokens. This method should return implementation of inbuilt **Token Class** object for the library to process it.\n\n- **save_token(self, [token](zohocrmsdk/src/com/zoho/api/authenticator/token.py))** - invoked after fetching access and refresh tokens from Zoho.\n\n- **delete_token(self, id))** - The method to delete the particular stored token based on unique ID\n\n- **get_tokens(self)** - The method to get the all the stored tokens.\n\n- **delete_tokens(self)** - The method to delete all the stored tokens.\n\n- **find_token_by_id(self, id)** - The method to retrieve the user's token details based on unique ID.\n\nNote:\n- id is a String.\n- token is an instance of Token Class.\n\n### DataBase Persistence\n\nIn case the user prefers to use default DataBase persistence, **MySQL** can be used.\n\n- Create a table in your Database with required columns\n\n  - id varchar(10)\n\n  - user_name varchar(255)\n  \n  - client_id varchar(255)\n  \n  - client_secret varchar(255)\n\n  - refresh_token varchar(255)\n\n  - access_token varchar(255)\n\n  - grant_token varchar(255)\n\n  - expiry_time varchar(20)\n  \n  - redirect_url varchar(255)\n\n  - api_domain varchar(255)\n\nNote:\n- Custom database name and table name can be set in DBStore instance.\n\n#### MySQL Query\n\n```sql\nCREATE TABLE  oauthtoken (\n  id varchar(10) NOT NULL,\n  user_name varchar(255),\n  client_id varchar(255),\n  client_secret varchar(255),\n  refresh_token varchar(255),\n  access_token varchar(255),\n  grant_token varchar(255),\n  expiry_time varchar(20),\n  redirect_url varchar(255),\n  api_domain varchar(255),\n  primary key (id)\n)\n```\n\n#### Create DBStore object\n\n```python\nfrom zohocrmsdk.src.com.zoho.api.authenticator.store import DBStore\n\"\"\"\nDBStore takes the following parameters\n1 -> DataBase host name. Default value \"localhost\"\n2 -> DataBase name. Default value \"zohooauth\"\n3 -> DataBase user name. Default value \"root\"\n4 -> DataBase password. Default value \"\"\n5 -> DataBase port number. Default value \"3306\"\n6->  DataBase table name . Default value \"oauthtoken\"\n\"\"\"\n# store = DBStore()\n\nstore = DBStore(host='host_name', database_name='database_name', user_name='user_name', password='password', port_number='port_number', table_name = \"table_name\")\n```\n\n### File Persistence\n\nIn case of File Persistence, the user can persist tokens in the local drive, by providing the absolute file path to the FileStore object.\n\n- The File contains\n    - id \n    \n    - user_name\n\n    - client_id\n    \n    - client_secret\n\n    - refresh_token\n\n    - access_token\n\n    - grant_token\n\n    - expiry_time\n    \n    - redirect_url\n\n    - api_domain\n\n#### Create FileStore object\n\n```python\nfrom zohocrmsdk.src.com.zoho.api.authenticator.store import FileStore\n\"\"\"\nFileStore takes the following parameter\n1 -> Absolute file path of the file to persist tokens\n\"\"\"\nstore = FileStore(file_path='/Users/username/Documents/python_sdk_token.txt')\n```\n\n### Custom Persistence\nUsers can create their own logic for storing and retrieving authentication tokens using the custom persistence technique. To use Custom Persistence, you must implement **[TokenStore](zohocrmsdk/src/com/zoho/api/authenticator/store/token_store.py)** and override the methods.\n\n```python\nfrom zohocrmsdk.src.com.zoho.api.authenticator.store import TokenStore\n\n\nclass CustomStore(TokenStore):\n\n    def __init__(self):\n        pass\n\n    def find_token(self, token):\n\n        \"\"\"\n        Parameters:\n            token (Token) : A Token (zohocrmsdk.src.com.zoho.api.authenticator.OAuthToken) class instance\n        \"\"\"\n\n        # Add code to get the token\n        return None\n\n    def save_token(self, token):\n\n        \"\"\"\n        Parameters:\n            token (Token) : A Token (zohocrmsdk.src.com.zoho.api.authenticator.OAuthToken) class instance\n        \"\"\"\n\n        # Add code to save the token\n\n    def delete_token(self, id):\n\n        \"\"\"\n        Parameters:\n            id (String) : id representing the unique token id\n        \"\"\"\n\n        # Add code to delete the token\n    \n    def get_tokens(self):\n\n        \"\"\"\n        Returns:\n            list: List of stored tokens\n        \"\"\"\n\n        # Add code to get all the stored tokens\n    \n    def delete_tokens(self):\n    \n        # Add code to delete all the stored tokens\n    \n    def find_token_by_id(self, id):\n        \n        \"\"\"\n        The method to get id token details.\n\n        Parameters:\n            id (String) : A String id.\n\n        Returns:\n            Token : A Token class instance representing the id token details.\n        \"\"\"\n```\n\n## Configuration\n\nBefore you get started with creating your Python application, you need to register your client and authenticate the app with Zoho.\n\n| Mandatory Keys | Optional Keys |\n|:---------------| :------------ |\n| token          | logger        |\n| environment    | store         |\n|                | sdk_config    |\n|                | proxy         |\n|                | resource_path |\n----\n\nThe **environment** key contains the domain information to make API calls. The **token** key 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.\n\n- Configure the API environment which decides the domain and the URL to make API calls.\n  ```python\n  from zohocrmsdk.src.com.zoho.crm.api.dc import USDataCenter\n\n  \"\"\"\n  Configure the environment\n  which is of the pattern Domain.Environment\n  Available Domains: USDataCenter, EUDataCenter, INDataCenter, CNDataCenter, AUDataCenter\n  Available Environments: PRODUCTION(), DEVELOPER(), SANDBOX()\n  \"\"\"\n  environment = USDataCenter.PRODUCTION()\n  ```\n\n- Create an instance of **OAuthToken** with 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.  \n  \n  - Use the following method for **grantToken flow :**\n  ```python\n  from zohocrmsdk.src.com.zoho.api.authenticator.oauth_token import OAuthToken\n  token = OAuthToken(client_id= \"clientId\", client_secret=\"clientSecret\", grant_token=\"grantToken\", redirect_url=\"redirectURL\")\n  ```\n  - Use the following method for **refreshToken flow :**\n  ```python\n  from zohocrmsdk.src.com.zoho.api.authenticator.oauth_token import OAuthToken\n  token = OAuthToken(client_id= \"clientId\", client_secret=\"clientSecret\", refresh_token=\"refreshToken\", redirect_url=\"redirectURL\")\n  ```\n  - Use the following method for **accessToken flow :**\n  ```python\n  from zohocrmsdk.src.com.zoho.api.authenticator.oauth_token import OAuthToken\n  token = OAuthToken(access_token=\"accessToken\")\n  ```\n  - Use the following method for **Id flow :**\n  ```python\n  from zohocrmsdk.src.com.zoho.api.authenticator.oauth_token import OAuthToken\n  token = OAuthToken(id=\"id\")\n  ```\n\n- Create an instance of **Logger** Class to log exception and API information.\n  ```python\n    from zohocrmsdk.src.com.zoho.api.logger import Logger\n  \n    \"\"\"\n    Create an instance of Logger Class that takes two parameters\n    1 -> Level of the log messages to be logged. Can be configured by typing Logger.Levels \".\" and choose any level from the list displayed.\n    2 -> Absolute file path, where messages need to be logged.\n    \"\"\"\n    logger = Logger.get_instance(level=Logger.Levels.INFO, file_path=\"/Users/python_sdk_log.log\")\n    ```\n\n- Create an instance of **TokenStore** to 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.\n\n  - Use the following method for DB Store\n  ```python\n  from zohocrmsdk.src.com.zoho.api.authenticator.store import DBStore\n  \"\"\"\n  DBStore takes the following parameters\n  1 -> DataBase host name. Default value \"localhost\"\n  2 -> DataBase name. Default value \"zohooauth\"\n  3 -> DataBase user name. Default value \"root\"\n  4 -> DataBase password. Default value \"\"\n  5 -> DataBase port number. Default value \"3306\"\n  6 -> DataBase table name. Default value \"oauthtoken\"\n  \"\"\"\n  # store = DBStore()\n\n  store = DBStore(host='host_name', database_name='database_name', user_name='user_name', password='password', port_number='port_number', table_name = \"table_name\")\n  ```\n  - Use the following method for File Store\n  ```python\n  from zohocrmsdk.src.com.zoho.api.authenticator.store import FileStore\n  \"\"\"\n  FileStore takes the following parameter\n  1 -> Absolute file path of the file to persist tokens\n  \"\"\"\n  store = FileStore(file_path='/Users/python_sdk_tokens.txt')\n  ```\n  - Use the following method for Custom Store\n  ```python\n  token_store = CustomStore()\n  ```\n- Create an instance of SDKConfig containing SDK configurations.\n  ```python\n  from zohocrmsdk.src.com.zoho.crm.api.sdk_config import SDKConfig\n\n  \"\"\"\n  By default, the SDK creates the SDKConfig instance\n  auto_refresh_fields (Default value is False)\n    if True - all the modules' fields will be auto-refreshed in the background, every hour.\n    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)\n\n  pick_list_validation (Default value is True)\n    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.\n    if True - the SDK validates the input. If the value does not exist in the pick list, the SDK throws an error.\n    if False - the SDK does not validate the input and makes the API request with the user\u2019s input to the pick list\n  \n  connect_timeout (Default value is None) \n    A  Float field to set connect timeout\n  \n  read_timeout (Default value is None) \n    A  Float field to set read timeout\n  \"\"\"\n  config = SDKConfig(auto_refresh_fields=True, pick_list_validation=False, connect_timeout=None, read_timeout=None)\n  ```\n\n- 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.\n  ```python\n  resource_path = '/Users/python-app'\n  ```\n\n- Create an instance of RequestProxy containing the proxy properties of the user.\n    ```python\n    from zohocrmsdk.src.com.zoho.crm.api.request_proxy import RequestProxy\n    \"\"\"\n    RequestProxy takes the following parameters\n    1 -> Host\n    2 -> Port Number\n    3 -> User Name. Default value is None\n    4 -> Password. Default value is an empty string\n    \"\"\"\n    # request_proxy = RequestProxy(host='proxyHost', port=80)\n    request_proxy = RequestProxy(host='proxyHost', port=80, user='userName', password='password')\n    ```\n\n## Initializing the Application\n\nInitialize the SDK using the following code.\n\n```python\nfrom zohocrmsdk.src.com.zoho.crm.api.dc import USDataCenter\nfrom zohocrmsdk.src.com.zoho.api.authenticator.store import DBStore, FileStore\nfrom zohocrmsdk.src.com.zoho.api.logger import Logger\nfrom zohocrmsdk.src.com.zoho.crm.api.initializer import Initializer\nfrom zohocrmsdk.src.com.zoho.api.authenticator.oauth_token import OAuthToken\nfrom zohocrmsdk.src.com.zoho.crm.api.sdk_config import SDKConfig\nfrom zohocrmsdk.src.com.zoho.crm.api.request_proxy import RequestProxy\n\n\nclass SDKInitializer(object):\n\n    @staticmethod\n    def initialize():\n        logger = Logger.get_instance(level=Logger.Levels.INFO, file_path='/Users/user_name/Documents/python_sdk_log.log')\n        environment = USDataCenter.PRODUCTION()\n        token = OAuthToken(client_id='clientId', client_secret='clientSecret', grant_token='grant_token', refresh_token=\"refresh_token\", redirect_url='redirectURL', id=\"id\")\n        # store = FileStore(file_path='/Users/username/Documents/python_sdk_tokens.txt')\n        # store = DBStore()\n        store = DBStore(host='host_name', database_name='database_name', user_name='user_name', password='password',port_number='port_number', table_name = \"table_name\")\n        config = SDKConfig(auto_refresh_fields=True, pick_list_validation=False, connect_timeout=None, read_timeout=None)\n        resource_path = '/Users/python-app'\n        # request_proxy = RequestProxy(host='host', port=8080)\n        request_proxy = RequestProxy(host='host', port=8080, user='user', password='password')\n        \"\"\"\n        Call the static initialize method of Initializer class that takes the following arguments\n        2 -> Environment instance\n        3 -> Token instance\n        4 -> TokenStore instance\n        5 -> SDKConfig instance\n        6 -> resource_path\n        7 -> Logger instance. Default value is None\n        8 -> RequestProxy instance. Default value is None\n        \"\"\"\n        Initializer.initialize(environment=environment, token=token, store=store, sdk_config=config, resource_path=resource_path, logger=logger, proxy=request_proxy)\n\nSDKInitializer.initialize()\n\n```\n\n- You can now access the functionalities of the SDK. Refer to the sample codes to make various API calls through the SDK.\n\n## Class Hierarchy\n\n![classdiagram](class_hierarchy.png)\n\n## Responses and Exceptions\n\nAll SDK methods return an instance of the APIResponse class.\n\nAfter a successful API request, the **get_object()** method returns an instance of the **ResponseWrapper** (for **GET**) or the **ActionWrapper** (for **POST, PUT, DELETE**)\n\nWhenever the API returns an error response, the **get_object()** returns an instance of **APIException** class.\n\n**ResponseWrapper** (for **GET** requests) and **ActionWrapper** (for **POST, PUT, DELETE** requests) are the expected objects for Zoho CRM APIs\u2019 responses\n\nHowever, some specific operations have different expected objects, such as the following:\n\n- Operations involving records in Tags\n    - **RecordActionWrapper**\n\n- Getting Record Count for a specific Tag operation\n    - **CountWrapper**\n\n- Operations involving BaseCurrency\n    - **BaseCurrencyActionWrapper**\n\n- Lead convert operation\n    - **ConvertActionWrapper**\n\n- Retrieving Deleted records operation\n    - **DeletedRecordsWrapper**\n\n- Record image download operation\n    - **FileBodyWrapper**\n\n- MassUpdate record operations\n    - **MassUpdateActionWrapper**\n    - **MassUpdateResponseWrapper**\n  \n### GET Requests\n\n- The **get_object()** returns an instance of one of the following classes, based on the return type.\n\n    - Most of the APIs follows the **Common** Structure as below.\n\n  - The **ResponseHandler class** encompasses the following\n    - **ResponseWrapper class** (for **application/json** responses)\n    - **FileBodyWrapper class** (for File download responses)\n    - **APIException class**\n\n\n- Some of the APIs follow the **Particular** Structure as below.\n\n  - The **ResponseHandler class** encompasses the following\n    - **BodyWrapper class** (for **application/json** responses in **backup** API , holds the instance of **Backup class**)\n    - **HistoryWrapper class** (for **application/json** responses in **backup** API, holds the list of instances of **History class** and instance of **Info class**)\n    - **UrlsWrapper class** (for **application/json** responses in **backup** API, holds the instance of **Urls class**)\n    - **BodyWrapper class** (for **application/json** responses in **ContactRoles** API, holds the list of instances of **ContactRole class**)\n    - **BodyWrapper class** (for **application/json** responses in **Currencies** API, holds the list of instances of **Currency class**)\n    - **BodyWrapper class** (for **application/json** responses in **CustomView** API, holds the list of instances of **CustomView class** and instance of **Info class** )\n    - **BodyWrapper class** (for **application/json** responses in **DealContactroles** API, holds the list of instances of **Data class** and instance of **Info class** )\n    - **BodyWrapper class** (for **application/json** responses in **FieldMapDependency** API, holds the list of instances of **MapDependency class** and instance of **Info class** )\n    - **BodyWrapper class** (for **application/json** responses in **Fields** API, holds the list of instances of **Field class**)\n    - **BodyWrapper class** (for **application/json** responses in **Pipeline** API, holds the list of instances of **Pipeline class**)\n    - **ProfieWrapper class** (for **application/json** responses in **Profiles** API, holds the list of instances of **Profile class** and instance of **Info class**)\n    - **ConversionOptionsResponseWrapper class**  (for **application/json** responses in **Record** API, holds the instance of **ConversionOption class**)\n    - **SourcesCountWrapper class** (for **application/json** responses in **UserGroups** API, holds the List of instances of **SourceCount class**)\n    - **SourcesWrapper class** (for **application/json** responses in **Usergroups** APi, holds the List of instances of **Sources class** and instance of **Info class**)\n\n\n  - The **ResponseWrapper class** in **BulkWrite** API encompasses the following\n    - **BulkWriteResponse class** (for **application/json** responses)\n    - **APIException class**\n\n  - The **CountHandler class** encompasses the following\n    - **CountWrapper class** (for **application/json** responses in **Record** API, holds the Long **count**)\n    - **APIException class**\n\n  - The **DeletedRecordsHandler class** encompasses the following\n    - **DeletedRecordsWrapper class** (for **application/json** responses in **Record** API, holds the list of instances of **DeletedRecord class** and instance of **Info class**)\n    - **APIException class**\n\n  - The **DownloadHandler class** encompasses the following\n    - **FileBodyWrapper class** (for File download responses)\n    - **APIException class**\n\n  - The **MassUpdateResponseHandler class** encompasses the following\n    - **MassUpdateResponseWrapper class** (for **application/json** responses in **Record** API, holds the list of instances of **MassUpdateResponse class**)\n    - **APIException class**\n\n  - The **MassUpdateResponse class** encompasses of following\n    - **MassUpdate class** (for **application/json** responses)\n    - **APIException class**\n\n  - The **ValidationHandler class** in **UserTerritories** API encomposses the following\n    - **ValidationWrapper class** (for **application/json** responses, holds the list of instances of **ValidationGroup class**)\n    - **APIException class**\n\n  - The **ValidationGroup class** in **UserTerritories** API encompasses the following\n    - **Validation class**\n    - **BulkValidation class**\n\n### POST, PUT, DELETE Requests\n\n- The **getObject()** of the returned APIResponse instance returns the response as follows.\n\n- Most of the APIs follows the **Common** Structure as.\n\n  - The **ActionHandler class** encompasses the following\n    - **ActionWrapper class** (for **application/json** responses)\n    - **APIException class**\n\n  - The **ActionWrapper class** contains **Property/Properties** that may contain one/list of **ActionResponse class**.\n\n  - The **ActionResponse class** encompasses the following\n    - **SuccessResponse class** (for **application/json** responses)\n    - **APIException class**\n\n- Some of the APIs follow the **Particular** Structure as.\n\n  - The **ActionHandler class** encompasses the following\n    - **ActionWrapper class** (for **application/json** responses)\n    - **APIException class**\n\n  - The **ActionWrapper class** contains **Property/Properties** that may contain one/list of **ActionResponse class**.\n\n  - The **ActionResponse class** encompasses the following\n    - **BusinessHoursCreated class** (for **application/json** responses in **BusinessHours** API)\n    - **MassDeleteScheduled class** (for **application/json** responses in **MassDeleteCVID** API)\n    - **APIEXception class**\n\n  - The **RecordActionHandler class** encompasses the following\n    - **RecordActionWrapper class** (for **application/json** responses in **Tags** API, holds the list of instance of **RecordActionResponse class**, Boolean **wfScheduler**, String **successCount** and Boolean **lockedCount**)\n    - **APIException class**\n\n  - **RecordActionResponse class** encompasses the following\n    - **RecordSuccessResponse class** (for **application/json** responses)\n    - **APIException class**\n\n  - The **ActionHandler class** in **Currencies** API encompasses the following\n    - **BaseCurrencyActionWrapper class** (for **application/json** responses)\n    - **APIException class**\n\n  - The **BaseCurrencyActionWrapper class** contains **Property/Properties** that contain **BaseCurrencyActionResponse class**.\n\n  - The **BaseCurrencyActionResponse class** encompasses the following\n    - **SuccessResponse class** (for **application/json** responses)\n    - **APIException class**\n\n  - The **MassUpdateActionHandler class** encompasses the following\n    - **MassUpdateActionWrapper class** (for **application/json** responses in **Record** API, holds the list of instances of **MassUpdateActionResponse class**)\n    - **APIException class**\n\n  - The **MassUpdateActionResponse class** encompasses of following\n    - **MassUpdateSuccessResponse class** (for **application/json** responses)\n    - **APIException class**\n\n  - The **FileHandler class** in **Record** API encompasses the following \n    - **SuccessResponse class** (for **application/json** responses)\n    - **APIException class**\n\n  - The **SignActionHandler class** in **MailMerge** API encompasses the following\n    - **SignActionWrapper class** (for **application/json** responses)\n    - **APIException class**\n    \n  - The **DeleteActionHandler class** encompasses the following\n    - **DeleteActionWrapper class** (for **application/json** responses in **ShareRecords** API, holds the instance of **DeleteActionResponse class**)\n    - **APIException class**\n  - The **DeleteActionResponse class** encompasses the following\n    - **SuccessResponse class** (for **application/json** responses)\n    - **APIException class**\n\n  - The **TransferActionHandler class** in **UserTerritories** API encompasses the following\n    - **TransferActionWrapper class** (fro **application/json** responses , holds the list of instances of **TransferActionResponse class**)\n\n  - The **TransferActionResponse class** encompasses the following \n    - **SuccessResponse class** (for **application/json** responses)\n    - **APIException class**\n\n  - The **ActionResponse class** in **Territories** API encompasses the following\n    - **Success class** (for **application/json** responses)\n    - **APIException class**\n\n  - The **TransferPipelineActionHandler class** in **Pipeline** API encompasses the following\n    - **TransferPipelineActionWrapper class** (for **application/json** responses, holds the list of insatnces of **TransferPipelineActionResponse class**)\n    - **APIException class**\n  \n  - The **TransferPipelineActionResponse class** in **Pipeline** API encompasses the following\n    - **TransferPipelineSuccessResponse class** (for **application/json** responses)\n    - **APIException class**\n\nFor 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.\n\nAll other exceptions such as SDK anomalies and other unexpected behaviours are thrown under the SDKException class.\n\n## Multithreading in a Multi-user App\n\nMulti-threading for multi-users is achieved using Initializer's static **switch_user()** method.\nswitch_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.\n\n```python\n# without proxy\nInitializer.switch_user(environment=environment, token=token, sdk_config=sdk_config_instance)\n\n# with proxy\nInitializer.switch_user(environment=environment, token=token, sdk_config=sdk_config_instance, proxy=request_proxy)\n```\n\nHere is a sample code to depict multi-threading for a multi-user app.\n\n```python\nimport threading\nfrom zohocrmsdk.src.com.zoho.crm.api.parameter_map import ParameterMap \nfrom zohocrmsdk.src.com.zoho.crm.api.dc import USDataCenter, EUDataCenter\nfrom zohocrmsdk.src.com.zoho.api.authenticator.store import DBStore\nfrom zohocrmsdk.src.com.zoho.api.logger import Logger\nfrom zohocrmsdk.src.com.zoho.crm.api.initializer import Initializer\nfrom zohocrmsdk.src.com.zoho.api.authenticator.oauth_token import OAuthToken\nfrom zohocrmsdk.src.com.zoho.crm.api.record import *\nfrom zohocrmsdk.src.com.zoho.crm.api.request_proxy import RequestProxy\nfrom zohocrmsdk.src.com.zoho.crm.api.sdk_config import SDKConfig\n\n\nclass MultiThread(threading.Thread):\n\n    def __init__(self, environment, token, module_api_name, sdk_config, proxy=None):\n        super().__init__()\n        self.environment = environment\n        self.token = token\n        self.module_api_name = module_api_name\n        self.sdk_config = sdk_config\n        self.proxy = proxy\n\n    def run(self):\n        try:\n            Initializer.switch_user(environment=self.environment, token=self.token, sdk_config=self.sdk_config, proxy=self.proxy)\n            param_instance = ParameterMap()\n            param_instance.add(GetRecordsParam.fields, \"id\")\n            response = RecordOperations().get_records(self.module_api_name, param_instance)\n            if response is not None:\n                print('Status Code: ' + str(response.get_status_code()))\n                if response.get_status_code() in [204, 304]:\n                    print('No Content' if response.get_status_code() == 204 else 'Not Modified')\n                    return\n                response_object = response.get_object()\n                if response_object is not None:\n                    if isinstance(response_object, ResponseWrapper):\n                        record_list = response_object.get_data()\n                        for record in record_list:\n                            for key, value in record.get_key_values().items():\n                                print(key + \" : \" + str(value))\n                    elif isinstance(response_object, APIException):\n                        print(\"Status: \" + response_object.get_status().get_value())\n                        print(\"Code: \" + response_object.get_code().get_value())\n                        print(\"Details\")\n                        details = response_object.get_details()\n                        for key, value in details.items():\n                            print(key + ' : ' + str(value))\n                        print(\"Message: \" + response_object.get_message().get_value())\n        except Exception as e:\n            print(e)\n    @staticmethod\n    def call():\n        logger = Logger.get_instance(level=Logger.Levels.INFO, file_path=\"/Users/user_name/Documents/python_sdk_log.log\")\n        token1 = OAuthToken(client_id=\"clientId1\", client_secret=\"clientSecret1\", grant_token=\"Grant Token\", refresh_token=\"refresh_token\", id=\"id\")\n        environment1 = USDataCenter.PRODUCTION()\n        store = DBStore()\n        sdk_config_1 = SDKConfig(auto_refresh_fields=True, pick_list_validation=False)\n        resource_path = '/Users/user_name/Documents/python-app'\n        user1_module_api_name = 'Leads'\n        user2_module_api_name = 'Contacts'\n        environment2 = EUDataCenter.SANDBOX()\n        sdk_config_2 = SDKConfig(auto_refresh_fields=False, pick_list_validation=True)\n        token2 = OAuthToken(client_id=\"clientId2\", client_secret=\"clientSecret2\",grant_token=\"GRANT Token\", refresh_token=\"refresh_token\", redirect_url=\"redirectURL\", id=\"id\")\n        request_proxy_user_2 = RequestProxy(\"host\", 8080)\n        Initializer.initialize(environment=environment1, token=token1, store=store, sdk_config=sdk_config_1, resource_path=resource_path, logger=logger)\n        t1 = MultiThread(environment1, token1, user1_module_api_name, sdk_config_1)\n        t2 = MultiThread(environment2, token2, user2_module_api_name, sdk_config_2, request_proxy_user_2)\n        t1.start()\n        t2.start()\n        t1.join()\n        t2.join()\n\n        \nMultiThread.call()\n\n```\n\n- The program execution starts from **call()**.\n\n- The details of **user1** are given in the variables token1, environment1.\n\n- Similarly, the details of another user **user2** are given in the variables token2, environment2.\n\n- For each user, an instance of **MultiThread class** is created.\n\n- When the **start()** is called which in-turn invokes the **run()**,  the details of user1 are passed to the **switch_user** method through the **MultiThread object**. Therefore, this creates a thread for user1.\n\n- Similarly, When the **start()** is invoked again,  the details of user2 are passed to the **switch_user** function through the **MultiThread object**. Therefore, this creates a thread for user2.\n\n### Multi-threading in a Single User App\n\nHere is a sample code to depict multi-threading for a single-user app.\n\n```python\nimport threading\nfrom zohocrmsdk.src.com.zoho.crm.api.parameter_map import ParameterMap \nfrom zohocrmsdk.src.com.zoho.crm.api.dc import USDataCenter\nfrom zohocrmsdk.src.com.zoho.api.authenticator.store import DBStore\nfrom zohocrmsdk.src.com.zoho.api.logger import Logger\nfrom zohocrmsdk.src.com.zoho.crm.api.initializer import Initializer\nfrom zohocrmsdk.src.com.zoho.api.authenticator.oauth_token import OAuthToken\nfrom zohocrmsdk.src.com.zoho.crm.api.sdk_config import SDKConfig\nfrom zohocrmsdk.src.com.zoho.crm.api.record import *\n\n\nclass MultiThread(threading.Thread):\n\n    def __init__(self, module_api_name):\n        super().__init__()\n        self.module_api_name = module_api_name\n\n    def run(self):\n        try:\n            print(\"Calling Get Records for module: \" + self.module_api_name)\n            param_instance = ParameterMap()\n            param_instance.add(GetRecordsParam.fields, \"id\")\n            response = RecordOperations().get_records(self.module_api_name, param_instance)\n            if response is not None:\n                print('Status Code: ' + str(response.get_status_code()))\n                if response.get_status_code() in [204, 304]:\n                    print('No Content' if response.get_status_code() == 204 else 'Not Modified')\n                    return\n                response_object = response.get_object()\n                if response_object is not None:\n                    if isinstance(response_object, ResponseWrapper):\n                        record_list = response_object.get_data()\n                        for record in record_list:\n                            for key, value in record.get_key_values().items():\n                                print(key + \" : \" + str(value))\n                    elif isinstance(response_object, APIException):\n                        print(\"Status: \" + response_object.get_status().get_value())\n                        print(\"Code: \" + response_object.get_code().get_value())\n                        print(\"Details\")\n                        details = response_object.get_details()\n                        for key, value in details.items():\n                            print(key + ' : ' + str(value))\n                        print(\"Message: \" + response_object.get_message().get_value())\n        except Exception as e:\n            print(e)\n            \n    @staticmethod\n    def call():\n        logger = Logger.get_instance(level=Logger.Levels.INFO, file_path=\"/Users/user_name/Documents/python_sdk_log.log\")\n        token = OAuthToken(client_id=\"clientId\", client_secret=\"clientSecret\", grant_token=\"grant_token\", refresh_token=\"refresh_token\", redirect_url=\"redirectURL\", id=\"id\")\n        environment = USDataCenter.PRODUCTION()\n        store = DBStore()\n        sdk_config = SDKConfig()\n        resource_path = '/Users/user_name/Documents/python-app'\n        Initializer.initialize(environment=environment, token=token, store=store, sdk_config=sdk_config, resource_path=resource_path, logger=logger)\n        t1 = MultiThread('Leads')\n        t2 = MultiThread('Quotes')\n        t1.start()\n        t2.start()\n        t1.join()\n        t2.join()\n\n\nMultiThread.call()\n```\n\n- The program execution starts from **call()** where the SDK is initialized with the details of the user.\n\n- When the **start()** 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.\n\n## SDK Sample code\n\n```python\nfrom datetime import datetime\nfrom zohocrmsdk.src.com.zoho.api.authenticator import OAuthToken\nfrom zohocrmsdk.src.com.zoho.crm.api import Initializer, ParameterMap, HeaderMap\nfrom zohocrmsdk.src.com.zoho.crm.api.dc import USDataCenter\nfrom zohocrmsdk.src.com.zoho.crm.api.record import RecordOperations, GetRecordsParam, GetRecordsHeader, ResponseWrapper, Record, APIException\n\n\nclass GetRecord:\n    @staticmethod\n    def initialize():\n        environment = USDataCenter.PRODUCTION()\n        token = OAuthToken(client_id=\"clientID\", client_secret=\"clientSecret\", grant_token=\"grantToken\")\n        Initializer.initialize(environment, token)\n        \n    @staticmethod\n    def get_records(module_api_name):\n        \"\"\"\n        This method is used to get all the records of a module and print the response.\n        :param module_api_name: The API Name of the module to fetch records\n        \"\"\"\n        \"\"\"\n        example\n        module_api_name = 'Leads'\n        \"\"\"\n        record_operations = RecordOperations()\n        param_instance = ParameterMap()\n        param_instance.add(GetRecordsParam.page, 1)\n        param_instance.add(GetRecordsParam.per_page, 120)\n        field_names = [\"Company\", \"Email\"]\n        for field in field_names:\n            param_instance.add(GetRecordsParam.fields, field)\n        header_instance = HeaderMap()\n        header_instance.add(GetRecordsHeader.if_modified_since,\n                            datetime.fromisoformat('2020-01-01T00:00:00+05:30'))\n        response = record_operations.get_records(module_api_name, param_instance, header_instance)\n        if response is not None:\n            print('Status Code: ' + str(response.get_status_code()))\n            if response.get_status_code() in [204, 304]:\n                print('No Content' if response.get_status_code() == 204 else 'Not Modified')\n                return\n            response_object = response.get_object()\n            if response_object is not None:\n                if isinstance(response_object, ResponseWrapper):\n                    record_list = response_object.get_data()\n                    for record in record_list:\n                        print(\"Record ID: \" + str(record.get_id()))\n                        created_by = record.get_created_by()\n                        if created_by is not None:\n                            print(\"Record Created By - Name: \" + created_by.get_name())\n                            print(\"Record Created By - ID: \" + str(created_by.get_id()))\n                            print(\"Record Created By - Email: \" + created_by.get_email())\n                        print(\"Record CreatedTime: \" + str(record.get_created_time()))\n                        if record.get_modified_time() is not None:\n                            print(\"Record ModifiedTime: \" + str(record.get_modified_time()))\n                        modified_by = record.get_modified_by()\n                        if modified_by is not None:\n                            print(\"Record Modified By - Name: \" + modified_by.get_name())\n                            print(\"Record Modified By - ID: \" + str(modified_by.get_id()))\n                            print(\"Record Modified By - Email: \" + modified_by.get_email())\n                        tags = record.get_tag()\n                        if tags is not None:\n                            for tag in tags:\n                                print(\"Record Tag Name: \" + tag.get_name())\n                                print(\"Record Tag ID: \" + str(tag.get_id()))\n                        print(\"Record Field Value: \" + str(record.get_key_value('Last_Name')))\n                        print('Record KeyValues: ')\n                        for key, value in record.get_key_values().items():\n                            print(key + \" : \" + str(value))\n                elif isinstance(response_object, APIException):\n                    print(\"Status: \" + response_object.get_status().get_value())\n                    print(\"Code: \" + response_object.get_code().get_value())\n                    print(\"Details\")\n                    details = response_object.get_details()\n                    for key, value in details.items():\n                        print(key + ' : ' + str(value))\n                    print(\"Message: \" + response_object.get_message().get_value())\n\n            \nGetRecord.initialize()\nGetRecord.get_records(\"Leads\")\n```\n",
    "bugtrack_url": null,
    "license": "Apache Software License, Version 2.0, http://www.apache.org/licenses/LICENSE-2.0",
    "summary": "Zoho CRM SDK for ZOHO CRM 5 APIs",
    "version": "2.0.0",
    "project_urls": {
        "Homepage": "https://github.com/zoho/zohocrm-python-sdk-5.0"
    },
    "split_keywords": [
        "development",
        " zoho",
        " crm",
        " api",
        " zcrmsdk",
        " zohocrmsdksdk",
        " zcrm",
        " zohocrmsdk5_0"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "c797c684864b58e12494f0d13e8aa9be33470453b0f1c7121d59b5126698c4da",
                "md5": "2cbf1be6e59eb5ba6f1d5fd426e4fc24",
                "sha256": "51a9253f887ef437c3c9380e00517395b3b461a0f4c9ef2eca9c33c7bdb681dc"
            },
            "downloads": -1,
            "filename": "zohocrmsdk5_0-2.0.0-py2.py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "2cbf1be6e59eb5ba6f1d5fd426e4fc24",
            "packagetype": "bdist_wheel",
            "python_version": "py2.py3",
            "requires_python": null,
            "size": 1134267,
            "upload_time": "2024-04-17T10:22:57",
            "upload_time_iso_8601": "2024-04-17T10:22:57.950578Z",
            "url": "https://files.pythonhosted.org/packages/c7/97/c684864b58e12494f0d13e8aa9be33470453b0f1c7121d59b5126698c4da/zohocrmsdk5_0-2.0.0-py2.py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "8f509a1a72aa584398498c370d2a715b550c345fc84961fc2eef81e26fc2624c",
                "md5": "fa7c41d9eb7af24e07426a4d64b533e5",
                "sha256": "9cd12958ba126420b9ce950f7e8463bcaa1def5846d7c0c88f3c0d4d6528249d"
            },
            "downloads": -1,
            "filename": "zohocrmsdk5_0-2.0.0.tar.gz",
            "has_sig": false,
            "md5_digest": "fa7c41d9eb7af24e07426a4d64b533e5",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": null,
            "size": 459367,
            "upload_time": "2024-04-17T10:22:59",
            "upload_time_iso_8601": "2024-04-17T10:22:59.827465Z",
            "url": "https://files.pythonhosted.org/packages/8f/50/9a1a72aa584398498c370d2a715b550c345fc84961fc2eef81e26fc2624c/zohocrmsdk5_0-2.0.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-04-17 10:22:59",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "zoho",
    "github_project": "zohocrm-python-sdk-5.0",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": false,
    "requirements": [],
    "lcname": "zohocrmsdk5-0"
}
        
Elapsed time: 0.21949s