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 2.1 for API version 2.1
## 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 zohocrmsdk2_1==3.x.x
```
- 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. There are three ways provided by the SDK in which persistence can be utilized. They are DataBase Persistence, File Persistence and Custom 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](zcrmsdk/src/com/zoho/api/authenticator/store/token_store.py)**, which has the following callback methods.
- **get_token(self, user, [token](zcrmsdk/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, user, [token](zcrmsdk/src/com/zoho/api/authenticator/token.py))** - invoked after fetching access and refresh tokens from Zoho.
- **delete_token(self, [token](zcrmsdk/src/com/zoho/api/authenticator/token.py))** - invoked before saving the latest tokens.
- **get_tokens(self)** - The method to get the all the stored tokens.
- **delete_tokens(self)** - The method to delete all the stored tokens.
- **get_token_by_id(self, id, token)** - The method to retrieve the user's token details based on unique ID.
Note:
- user is an instance of UserSignature Class.
- token is an instance of Token Class.
### DataBase Persistence
In case the user prefers to use default DataBase persistence, **MySQL** can be used.
- The database name should be **zohooauth**.
- There must be a table name **oauthtoken** with the following columns.
- id int(11)
- user_mail 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)
Note:
- Custom database name and table name can be set in DBStore instance.
#### MySQL Query
```sql
CREATE TABLE oauthtoken (
id varchar(255) NOT NULL,
user_mail varchar(255) NOT NULL,
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),
primary key (id)
)
```
#### Create DBStore object
```python
from zcrmsdk.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_mail
- client_id
- client_secret
- refresh_token
- access_token
- grant_token
- expiry_time
- redirect_url
#### Create FileStore object
```python
from zcrmsdk.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
To use Custom Persistence, you must implement **[TokenStore](zcrmsdk/src/com/zoho/api/authenticator/store/token_store.py)** and override the methods.
```python
from zcrmsdk.src.com.zoho.api.authenticator.store import TokenStore
class CustomStore(TokenStore):
def __init__(self):
pass
def get_token(self, user, token):
"""
Parameters:
user (UserSignature) : A UserSignature class instance.
token (Token) : A Token (zcrmsdk.src.com.zoho.api.authenticator.OAuthToken) class instance
"""
# Add code to get the token
return None
def save_token(self, user, token):
"""
Parameters:
user (UserSignature) : A UserSignature class instance.
token (Token) : A Token (zcrmsdk.src.com.zoho.api.authenticator.OAuthToken) class instance
"""
# Add code to save the token
def delete_token(self, token):
"""
Parameters:
token (Token) : A Token (zcrmsdk.src.com.zoho.api.authenticator.OAuthToken) class instance
"""
# 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 get_token_by_id(id, token):
"""
The method to get id token details.
Parameters:
id (String) : A String id.
token (Token) : A Token class instance.
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 |
| :---------------- | :------------ |
| user | logger |
| environment | store |
| token | sdk_config |
| | proxy |
| | resource_path |
----
- Create an instance of **UserSignature** Class that identifies the current user.
```python
from zcrmsdk.src.com.zoho.crm.api.user_signature import UserSignature
# Create an UserSignature instance that takes user Email as parameter
user = UserSignature(email='abc@zoho.com')
```
- Configure the API environment which decides the domain and the URL to make API calls.
```python
from zcrmsdk.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.
```python
from zcrmsdk.src.com.zoho.api.authenticator.oauth_token import OAuthToken
"""
Create a Token instance that takes the following parameters
1 -> OAuth client id.
2 -> OAuth client secret.
3 -> Grant token.
4 -> Refresh token.
5 -> OAuth redirect URL. Default value is None
6 -> id
7 -> Access token
"""
token = OAuthToken(client_id='clientId', client_secret='clientSecret', grant_token='grant_token', refresh_token="refresh_token", redirect_url='redirectURL', id="id", access_token="access_token")
```
- Create an instance of **Logger** Class to log exception and API information. By default, the SDK constructs a Logger instance with level - INFO and file_path - (sdk_logs.log, created in the current working directory)
```python
from zcrmsdk.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/user_name/Documents/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.
```python
from zcrmsdk.src.com.zoho.api.authenticator.store import DBStore, FileStore
"""
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")
"""
FileStore takes the following parameter
1 -> Absolute file path of the file to persist tokens
"""
#store = FileStore(file_path='/Users/username/Documents/python_sdk_tokens.txt')
```
- Create an instance of SDKConfig containing SDK configurations.
```python
from zcrmsdk.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(zcrmsdk/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/user_name/Documents/python-app'
```
- Create an instance of RequestProxy containing the proxy properties of the user.
```python
from zcrmsdk.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 zcrmsdk.src.com.zoho.crm.api.user_signature import UserSignature
from zcrmsdk.src.com.zoho.crm.api.dc import USDataCenter
from zcrmsdk.src.com.zoho.api.authenticator.store import DBStore, FileStore
from zcrmsdk.src.com.zoho.api.logger import Logger
from zcrmsdk.src.com.zoho.crm.api.initializer import Initializer
from zcrmsdk.src.com.zoho.api.authenticator.oauth_token import OAuthToken
from zcrmsdk.src.com.zoho.crm.api.sdk_config import SDKConfig
class SDKInitializer(object):
@staticmethod
def initialize():
"""
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/user_name/Documents/python_sdk_log.log')
# Create an UserSignature instance that takes user Email as parameter
user = UserSignature(email='abc@zoho.com')
"""
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 a Token instance that takes the following parameters
1 -> OAuth client id.
2 -> OAuth client secret.
3 -> Grant token.
4 -> Refresh token.
5 -> OAuth redirect URL.
6 -> id
"""
token = OAuthToken(client_id='clientId', client_secret='clientSecret', grant_token='grant_token', refresh_token="refresh_token", redirect_url='redirectURL', id="id")
"""
Create an instance of TokenStore
1 -> Absolute file path of the file to persist tokens
"""
store = FileStore(file_path='/Users/username/Documents/python_sdk_tokens.txt')
"""
Create an instance of TokenStore
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")
"""
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(zcrmsdk/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 (in the key resource_path) to store user-specific files containing information about fields in modules.
"""
resource_path = '/Users/user_name/Documents/python-app'
"""
Create an instance of RequestProxy class that takes the following parameters
1 -> Host
2 -> Port Number
3 -> User Name. Default value is None
4 -> Password. Default value is None
"""
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
1 -> UserSignature instance
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(user=user, 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.
- For **application/json** responses
- **ResponseWrapper**
- **CountWrapper**
- **DeletedRecordsWrapper**
- **MassUpdateResponseWrapper**
- **APIException**
- For **file download** responses
- **FileBodyWrapper**
- **APIException**
### POST, PUT, DELETE Requests
- The **getObject()** returns an instance of one of the following classes
- **ActionWrapper**
- **RecordActionWrapper**
- **BaseCurrencyActionWrapper**
- **MassUpdateActionWrapper**
- **ConvertActionWrapper**
- **APIException**
- These wrapper classes may contain one or a list of instances of the following classes, depending on the response.
- **SuccessResponse Class**, if the request was successful.
- **APIException Class**, if the request was erroneous.
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.
## Threading in the Python SDK
Threads in a Python program help you achieve parallelism. By using multiple threads, you can make a Python program run faster and do multiple things simultaneously.
The **Python SDK** (from version 3.x.x) supports both single-user and multi-user app.
### 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 user, 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(user=user, environment=environment, token=token, sdk_config=sdk_config_instance)
# with proxy
Initializer.switch_user(user=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 zcrmsdk.src.com.zoho.crm.api.user_signature import UserSignature
from zcrmsdk.src.com.zoho.crm.api.dc import USDataCenter, EUDataCenter
from zcrmsdk.src.com.zoho.api.authenticator.store import DBStore
from zcrmsdk.src.com.zoho.api.logger import Logger
from zcrmsdk.src.com.zoho.crm.api.initializer import Initializer
from zcrmsdk.src.com.zoho.api.authenticator.oauth_token import OAuthToken
from zcrmsdk.src.com.zoho.crm.api.record import *
from zcrmsdk.src.com.zoho.crm.api.request_proxy import RequestProxy
from zcrmsdk.src.com.zoho.crm.api.sdk_config import SDKConfig
class MultiThread(threading.Thread):
def __init__(self, environment, token, user, module_api_name, sdk_config, proxy=None):
super().__init__()
self.environment = environment
self.token = token
self.user = user
self.module_api_name = module_api_name
self.sdk_config = sdk_config
self.proxy = proxy
def run(self):
try:
Initializer.switch_user(user=self.user, environment=self.environment, token=self.token, sdk_config=self.sdk_config, proxy=self.proxy)
print('Getting records for User: ' + Initializer.get_initializer().user.get_email())
response = RecordOperations().get_records(self.module_api_name)
if response is not None:
# Get the status code from response
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
# Get object from response
response_object = response.get_object()
if response_object is not None:
# Check if expected ResponseWrapper instance is received.
if isinstance(response_object, ResponseWrapper):
# Get the list of obtained Record instances
record_list = response_object.get_data()
for record in record_list:
for key, value in record.get_key_values().items():
print(key + " : " + str(value))
# Check if the request returned an exception
elif isinstance(response_object, APIException):
# Get the Status
print("Status: " + response_object.get_status().get_value())
# Get the Code
print("Code: " + response_object.get_code().get_value())
print("Details")
# Get the details dict
details = response_object.get_details()
for key, value in details.items():
print(key + ' : ' + str(value))
# Get the Message
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")
user1 = UserSignature(email="abc@zoho.com")
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()
user2 = UserSignature(email="abc@zoho.eu")
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(user=user1, environment=environment1, token=token1, store=store, sdk_config=sdk_config_1, resource_path=resource_path, logger=logger)
t1 = MultiThread(environment1, token1, user1, user1_module_api_name, sdk_config_1)
t2 = MultiThread(environment2, token2, user2, 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 user1, token1, environment1.
- Similarly, the details of another user **user2** are given in the variables user2, 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 zcrmsdk.src.com.zoho.crm.api.user_signature import UserSignature
from zcrmsdk.src.com.zoho.crm.api.dc import USDataCenter
from zcrmsdk.src.com.zoho.api.authenticator.store import DBStore
from zcrmsdk.src.com.zoho.api.logger import Logger
from zcrmsdk.src.com.zoho.crm.api.initializer import Initializer
from zcrmsdk.src.com.zoho.api.authenticator.oauth_token import OAuthToken
from zcrmsdk.src.com.zoho.crm.api.sdk_config import SDKConfig
from zcrmsdk.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)
response = RecordOperations().get_records(self.module_api_name)
if response is not None:
# Get the status code from response
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
# Get object from response
response_object = response.get_object()
if response_object is not None:
# Check if expected ResponseWrapper instance is received.
if isinstance(response_object, ResponseWrapper):
# Get the list of obtained Record instances
record_list = response_object.get_data()
for record in record_list:
for key, value in record.get_key_values().items():
print(key + " : " + str(value))
# Check if the request returned an exception
elif isinstance(response_object, APIException):
# Get the Status
print("Status: " + response_object.get_status().get_value())
# Get the Code
print("Code: " + response_object.get_code().get_value())
print("Details")
# Get the details dict
details = response_object.get_details()
for key, value in details.items():
print(key + ' : ' + str(value))
# Get the Message
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")
user = UserSignature(email="abc@zoho.com")
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(user=user, 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 zcrmsdk.src.com.zoho.crm.api.user_signature import UserSignature
from zcrmsdk.src.com.zoho.crm.api.dc import USDataCenter
from zcrmsdk.src.com.zoho.api.authenticator.store import DBStore
from zcrmsdk.src.com.zoho.api.logger import Logger
from zcrmsdk.src.com.zoho.crm.api.initializer import Initializer
from zcrmsdk.src.com.zoho.api.authenticator.oauth_token import OAuthToken
from zcrmsdk.src.com.zoho.crm.api.record import *
from zcrmsdk.src.com.zoho.crm.api import HeaderMap, ParameterMap
from zcrmsdk.src.com.zoho.crm.api.sdk_config import SDKConfig
class Record(object):
def __init__(self):
pass
@staticmethod
def get_records():
"""
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/user_name/Documents/python_sdk_log.log")
# Create an UserSignature instance that takes user Email as parameter
user = UserSignature(email="abc@zoho.com")
"""
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 a Token instance that takes the following parameters
1 -> OAuth client id.
2 -> OAuth client secret.
3 -> Grant token.
4 -> Refresh token.
5 -> OAuth redirect URL.
6 -> id
"""
token = OAuthToken(client_id="clientId", client_secret="clientSecret", grant_token="grant_token", refresh_token="refresh_token", redirect_url="redirectURL", id="id")
"""
Create an instance of TokenStore
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()
"""
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(zcrmsdk/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 (in the key resource_path) to store user-specific files containing information about fields in modules.
"""
resource_path = '/Users/user_name/Documents/python-app'
"""
Call the static initialize method of Initializer class that takes the following arguments
1 -> UserSignature instance
2 -> Environment instance
3 -> Token instance
4 -> TokenStore instance
5 -> SDKConfig instance
6 -> resource_path
7 -> Logger instance
"""
Initializer.initialize(user=user, environment=environment, token=token, store=store, sdk_config=config, resource_path=resource_path, logger=logger)
try:
module_api_name = 'Leads'
param_instance = ParameterMap()
param_instance.add(GetRecordsParam.converted, 'both')
param_instance.add(GetRecordsParam.cvid, '12712717217218')
header_instance = HeaderMap()
header_instance.add(GetRecordsHeader.if_modified_since, datetime.now())
response = RecordOperations().get_records(module_api_name, param_instance, header_instance)
if response is not None:
# Get the status code from response
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
# Get object from response
response_object = response.get_object()
if response_object is not None:
# Check if expected ResponseWrapper instance is received.
if isinstance(response_object, ResponseWrapper):
# Get the list of obtained Record instances
record_list = response_object.get_data()
for record in record_list:
# Get the ID of each Record
print("Record ID: " + record.get_id())
# Get the createdBy User instance of each Record
created_by = record.get_created_by()
# Check if created_by is not None
if created_by is not None:
# Get the Name of the created_by User
print("Record Created By - Name: " + created_by.get_name())
# Get the ID of the created_by User
print("Record Created By - ID: " + created_by.get_id())
# Get the Email of the created_by User
print("Record Created By - Email: " + created_by.get_email())
# Get the CreatedTime of each Record
print("Record CreatedTime: " + str(record.get_created_time()))
if record.get_modified_time() is not None:
# Get the ModifiedTime of each Record
print("Record ModifiedTime: " + str(record.get_modified_time()))
# Get the modified_by User instance of each Record
modified_by = record.get_modified_by()
# Check if modified_by is not None
if modified_by is not None:
# Get the Name of the modified_by User
print("Record Modified By - Name: " + modified_by.get_name())
# Get the ID of the modified_by User
print("Record Modified By - ID: " + modified_by.get_id())
# Get the Email of the modified_by User
print("Record Modified By - Email: " + modified_by.get_email())
# Get the list of obtained Tag instance of each Record
tags = record.get_tag()
if tags is not None:
for tag in tags:
# Get the Name of each Tag
print("Record Tag Name: " + tag.get_name())
# Get the Id of each Tag
print("Record Tag ID: " + tag.get_id())
# To get particular field value
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))
# Check if the request returned an exception
elif isinstance(response_object, APIException):
# Get the Status
print("Status: " + response_object.get_status().get_value())
# Get the Code
print("Code: " + response_object.get_code().get_value())
print("Details")
# Get the details dict
details = response_object.get_details()
for key, value in details.items():
print(key + ' : ' + str(value))
# Get the Message
print("Message: " + response_object.get_message().get_value())
except Exception as e:
print(e)
Record.get_records()
```
Raw data
{
"_id": null,
"home_page": "https://github.com/zoho/zohocrm-python-sdk-2.1",
"name": "zohocrmsdk2-1",
"maintainer": null,
"docs_url": null,
"requires_python": null,
"maintainer_email": null,
"keywords": "development, zoho, crm, api, zcrmsdk, sdk, zcrm, zohocrmsdk2_1",
"author": "Zoho CRM API Team",
"author_email": "support@zohocrm.com",
"download_url": "https://files.pythonhosted.org/packages/fd/9c/a0f74297e4bc3a7f6562da34af0499027bd4ae021625604510e3ef1ab20f/zohocrmsdk2_1-3.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 2.1 for API version 2.1\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 zohocrmsdk2_1==3.x.x\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. There are three ways provided by the SDK in which persistence can be utilized. They are DataBase Persistence, File Persistence and Custom 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](zcrmsdk/src/com/zoho/api/authenticator/store/token_store.py)**, which has the following callback methods.\n\n- **get_token(self, user, [token](zcrmsdk/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, user, [token](zcrmsdk/src/com/zoho/api/authenticator/token.py))** - invoked after fetching access and refresh tokens from Zoho.\n\n- **delete_token(self, [token](zcrmsdk/src/com/zoho/api/authenticator/token.py))** - invoked before saving the latest tokens.\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- **get_token_by_id(self, id, token)** - The method to retrieve the user's token details based on unique ID.\n\nNote:\n\n- user is an instance of UserSignature Class.\n\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- The database name should be **zohooauth**.\n\n- There must be a table name **oauthtoken** with the following columns.\n\n - id int(11)\n\n - user_mail 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\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(255) NOT NULL,\n user_mail varchar(255) NOT NULL,\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 primary key (id)\n)\n```\n\n#### Create DBStore object\n\n```python\nfrom zcrmsdk.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\"\"\"\nstore = 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_mail\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#### Create FileStore object\n\n```python\nfrom zcrmsdk.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\nTo use Custom Persistence, you must implement **[TokenStore](zcrmsdk/src/com/zoho/api/authenticator/store/token_store.py)** and override the methods.\n\n```python\nfrom zcrmsdk.src.com.zoho.api.authenticator.store import TokenStore\n\n\nclass CustomStore(TokenStore):\n\n def __init__(self):\n pass\n\n def get_token(self, user, token):\n\n \"\"\"\n Parameters:\n user (UserSignature) : A UserSignature class instance.\n token (Token) : A Token (zcrmsdk.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, user, token):\n\n \"\"\"\n Parameters:\n user (UserSignature) : A UserSignature class instance.\n token (Token) : A Token (zcrmsdk.src.com.zoho.api.authenticator.OAuthToken) class instance\n \"\"\"\n\n # Add code to save the token\n\n def delete_token(self, token):\n\n \"\"\"\n Parameters:\n token (Token) : A Token (zcrmsdk.src.com.zoho.api.authenticator.OAuthToken) class instance\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 get_token_by_id(id, token):\n \n \"\"\"\n The method to get id token details.\n\n Parameters:\n id (String) : A String id.\n token (Token) : A Token class instance.\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| user | logger |\n| environment | store |\n| token | sdk_config |\n| | proxy |\n| | resource_path |\n----\n\n- Create an instance of **UserSignature** Class that identifies the current user.\n ```python\n from zcrmsdk.src.com.zoho.crm.api.user_signature import UserSignature\n\n # Create an UserSignature instance that takes user Email as parameter\n user = UserSignature(email='abc@zoho.com')\n ```\n\n- Configure the API environment which decides the domain and the URL to make API calls.\n ```python\n from zcrmsdk.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.\n ```python\n from zcrmsdk.src.com.zoho.api.authenticator.oauth_token import OAuthToken\n\n \"\"\"\n Create a Token instance that takes the following parameters\n 1 -> OAuth client id.\n 2 -> OAuth client secret.\n 3 -> Grant token.\n 4 -> Refresh token.\n 5 -> OAuth redirect URL. Default value is None\n 6 -> id\n 7 -> Access token\n \"\"\"\n token = OAuthToken(client_id='clientId', client_secret='clientSecret', grant_token='grant_token', refresh_token=\"refresh_token\", redirect_url='redirectURL', id=\"id\", access_token=\"access_token\")\n ```\n\n- Create an instance of **Logger** Class to log exception and API information. By default, the SDK constructs a Logger instance with level - INFO and file_path - (sdk_logs.log, created in the current working directory)\n ```python\n from zcrmsdk.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/user_name/Documents/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 ```python\n from zcrmsdk.src.com.zoho.api.authenticator.store import DBStore, FileStore\n\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 \"\"\"\n FileStore takes the following parameter\n 1 -> Absolute file path of the file to persist tokens\n \"\"\"\n #store = FileStore(file_path='/Users/username/Documents/python_sdk_tokens.txt')\n ```\n\n- Create an instance of SDKConfig containing SDK configurations.\n ```python\n from zcrmsdk.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(zcrmsdk/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/user_name/Documents/python-app'\n ```\n\n- Create an instance of RequestProxy containing the proxy properties of the user.\n ```python\n from zcrmsdk.src.com.zoho.crm.api.request_proxy import RequestProxy\n\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 zcrmsdk.src.com.zoho.crm.api.user_signature import UserSignature\nfrom zcrmsdk.src.com.zoho.crm.api.dc import USDataCenter\nfrom zcrmsdk.src.com.zoho.api.authenticator.store import DBStore, FileStore\nfrom zcrmsdk.src.com.zoho.api.logger import Logger\nfrom zcrmsdk.src.com.zoho.crm.api.initializer import Initializer\nfrom zcrmsdk.src.com.zoho.api.authenticator.oauth_token import OAuthToken\nfrom zcrmsdk.src.com.zoho.crm.api.sdk_config import SDKConfig\n\n\nclass SDKInitializer(object):\n\n @staticmethod\n def initialize():\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/user_name/Documents/python_sdk_log.log')\n\n # Create an UserSignature instance that takes user Email as parameter\n user = UserSignature(email='abc@zoho.com')\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 a Token instance that takes the following parameters\n 1 -> OAuth client id.\n 2 -> OAuth client secret.\n 3 -> Grant token.\n 4 -> Refresh token.\n 5 -> OAuth redirect URL.\n 6 -> id\n \"\"\"\n token = OAuthToken(client_id='clientId', client_secret='clientSecret', grant_token='grant_token', refresh_token=\"refresh_token\", redirect_url='redirectURL', id=\"id\")\n\n \"\"\"\n Create an instance of TokenStore\n 1 -> Absolute file path of the file to persist tokens\n \"\"\"\n store = FileStore(file_path='/Users/username/Documents/python_sdk_tokens.txt')\n\n \"\"\"\n Create an instance of TokenStore\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 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 \"\"\"\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(zcrmsdk/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 (in the key resource_path) to store user-specific files containing information about fields in modules. \n \"\"\"\n resource_path = '/Users/user_name/Documents/python-app'\n\n \"\"\"\n Create an instance of RequestProxy class that 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 None\n \"\"\"\n request_proxy = RequestProxy(host='host', port=8080)\n\n request_proxy = RequestProxy(host='host', port=8080, user='user', password='password')\n\n \"\"\"\n Call the static initialize method of Initializer class that takes the following arguments\n 1 -> UserSignature instance\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(user=user, environment=environment, token=token, store=store, sdk_config=config, resource_path=resource_path, logger=logger, proxy=request_proxy)\n\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 - For **application/json** responses\n - **ResponseWrapper**\n - **CountWrapper**\n - **DeletedRecordsWrapper**\n - **MassUpdateResponseWrapper**\n - **APIException**\n\n - For **file download** responses\n - **FileBodyWrapper**\n - **APIException**\n\n### POST, PUT, DELETE Requests\n\n- The **getObject()** returns an instance of one of the following classes\n - **ActionWrapper**\n - **RecordActionWrapper**\n - **BaseCurrencyActionWrapper**\n - **MassUpdateActionWrapper**\n - **ConvertActionWrapper**\n - **APIException**\n\n- These wrapper classes may contain one or a list of instances of the following classes, depending on the response.\n - **SuccessResponse Class**, if the request was successful.\n - **APIException Class**, if the request was erroneous.\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## Threading in the Python SDK\n\nThreads in a Python program help you achieve parallelism. By using multiple threads, you can make a Python program run faster and do multiple things simultaneously.\n\nThe **Python SDK** (from version 3.x.x) supports both single-user and multi-user app.\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 user, 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(user=user, environment=environment, token=token, sdk_config=sdk_config_instance)\n\n# with proxy\nInitializer.switch_user(user=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 zcrmsdk.src.com.zoho.crm.api.user_signature import UserSignature\nfrom zcrmsdk.src.com.zoho.crm.api.dc import USDataCenter, EUDataCenter\nfrom zcrmsdk.src.com.zoho.api.authenticator.store import DBStore\nfrom zcrmsdk.src.com.zoho.api.logger import Logger\nfrom zcrmsdk.src.com.zoho.crm.api.initializer import Initializer\nfrom zcrmsdk.src.com.zoho.api.authenticator.oauth_token import OAuthToken\nfrom zcrmsdk.src.com.zoho.crm.api.record import *\nfrom zcrmsdk.src.com.zoho.crm.api.request_proxy import RequestProxy\nfrom zcrmsdk.src.com.zoho.crm.api.sdk_config import SDKConfig\n\n\nclass MultiThread(threading.Thread):\n\n def __init__(self, environment, token, user, module_api_name, sdk_config, proxy=None):\n super().__init__()\n self.environment = environment\n self.token = token\n self.user = user\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(user=self.user, environment=self.environment, token=self.token, sdk_config=self.sdk_config, proxy=self.proxy)\n\n print('Getting records for User: ' + Initializer.get_initializer().user.get_email())\n\n response = RecordOperations().get_records(self.module_api_name)\n\n if response is not None:\n\n # Get the status code from response\n print('Status Code: ' + str(response.get_status_code()))\n\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\n # Get object from response\n response_object = response.get_object()\n\n if response_object is not None:\n\n # Check if expected ResponseWrapper instance is received.\n if isinstance(response_object, ResponseWrapper):\n # Get the list of obtained Record instances\n record_list = response_object.get_data()\n\n for record in record_list:\n for key, value in record.get_key_values().items():\n print(key + \" : \" + str(value))\n\n # Check if the request returned an exception\n elif isinstance(response_object, APIException):\n # Get the Status\n print(\"Status: \" + response_object.get_status().get_value())\n\n # Get the Code\n print(\"Code: \" + response_object.get_code().get_value())\n\n print(\"Details\")\n\n # Get the details dict\n details = response_object.get_details()\n\n for key, value in details.items():\n print(key + ' : ' + str(value))\n\n # Get the Message\n print(\"Message: \" + response_object.get_message().get_value())\n\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\n user1 = UserSignature(email=\"abc@zoho.com\")\n\n token1 = OAuthToken(client_id=\"clientId1\", client_secret=\"clientSecret1\", grant_token=\"Grant Token\", refresh_token=\"refresh_token\", id=\"id\")\n\n environment1 = USDataCenter.PRODUCTION()\n\n store = DBStore()\n\n sdk_config_1 = SDKConfig(auto_refresh_fields=True, pick_list_validation=False)\n\n resource_path = '/Users/user_name/Documents/python-app'\n\n user1_module_api_name = 'Leads'\n\n user2_module_api_name = 'Contacts'\n\n environment2 = EUDataCenter.SANDBOX()\n\n user2 = UserSignature(email=\"abc@zoho.eu\")\n\n sdk_config_2 = SDKConfig(auto_refresh_fields=False, pick_list_validation=True)\n\n token2 = OAuthToken(client_id=\"clientId2\", client_secret=\"clientSecret2\",grant_token=\"GRANT Token\", refresh_token=\"refresh_token\", redirect_url=\"redirectURL\", id=\"id\")\n\n request_proxy_user_2 = RequestProxy(\"host\", 8080)\n\n Initializer.initialize(user=user1, environment=environment1, token=token1, store=store, sdk_config=sdk_config_1, resource_path=resource_path, logger=logger)\n\n t1 = MultiThread(environment1, token1, user1, user1_module_api_name, sdk_config_1)\n t2 = MultiThread(environment2, token2, user2, user2_module_api_name, sdk_config_2, request_proxy_user_2)\n\n t1.start()\n t2.start()\n\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 user1, token1, environment1.\n\n- Similarly, the details of another user **user2** are given in the variables user2, 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 zcrmsdk.src.com.zoho.crm.api.user_signature import UserSignature\nfrom zcrmsdk.src.com.zoho.crm.api.dc import USDataCenter\nfrom zcrmsdk.src.com.zoho.api.authenticator.store import DBStore\nfrom zcrmsdk.src.com.zoho.api.logger import Logger\nfrom zcrmsdk.src.com.zoho.crm.api.initializer import Initializer\nfrom zcrmsdk.src.com.zoho.api.authenticator.oauth_token import OAuthToken\nfrom zcrmsdk.src.com.zoho.crm.api.sdk_config import SDKConfig\nfrom zcrmsdk.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\n response = RecordOperations().get_records(self.module_api_name)\n\n if response is not None:\n\n # Get the status code from response\n print('Status Code: ' + str(response.get_status_code()))\n\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\n # Get object from response\n response_object = response.get_object()\n\n if response_object is not None:\n\n # Check if expected ResponseWrapper instance is received.\n if isinstance(response_object, ResponseWrapper):\n # Get the list of obtained Record instances\n record_list = response_object.get_data()\n\n for record in record_list:\n for key, value in record.get_key_values().items():\n print(key + \" : \" + str(value))\n\n # Check if the request returned an exception\n elif isinstance(response_object, APIException):\n # Get the Status\n print(\"Status: \" + response_object.get_status().get_value())\n\n # Get the Code\n print(\"Code: \" + response_object.get_code().get_value())\n\n print(\"Details\")\n\n # Get the details dict\n details = response_object.get_details()\n\n for key, value in details.items():\n print(key + ' : ' + str(value))\n\n # Get the Message\n print(\"Message: \" + response_object.get_message().get_value())\n\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\n user = UserSignature(email=\"abc@zoho.com\")\n\n token = OAuthToken(client_id=\"clientId\", client_secret=\"clientSecret\", grant_token=\"grant_token\", refresh_token=\"refresh_token\", redirect_url=\"redirectURL\", id=\"id\")\n\n environment = USDataCenter.PRODUCTION()\n\n store = DBStore()\n\n sdk_config = SDKConfig()\n\n resource_path = '/Users/user_name/Documents/python-app'\n\n Initializer.initialize(user=user, environment=environment, token=token, store=store, sdk_config=sdk_config, resource_path=resource_path, logger=logger)\n\n t1 = MultiThread('Leads')\n t2 = MultiThread('Quotes')\n\n t1.start()\n t2.start()\n\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 zcrmsdk.src.com.zoho.crm.api.user_signature import UserSignature\nfrom zcrmsdk.src.com.zoho.crm.api.dc import USDataCenter\nfrom zcrmsdk.src.com.zoho.api.authenticator.store import DBStore\nfrom zcrmsdk.src.com.zoho.api.logger import Logger\nfrom zcrmsdk.src.com.zoho.crm.api.initializer import Initializer\nfrom zcrmsdk.src.com.zoho.api.authenticator.oauth_token import OAuthToken\nfrom zcrmsdk.src.com.zoho.crm.api.record import *\nfrom zcrmsdk.src.com.zoho.crm.api import HeaderMap, ParameterMap\nfrom zcrmsdk.src.com.zoho.crm.api.sdk_config import SDKConfig\n\n\nclass Record(object):\n\n def __init__(self):\n pass\n\n @staticmethod\n def get_records():\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,\n file_path=\"/Users/user_name/Documents/python_sdk_log.log\")\n\n # Create an UserSignature instance that takes user Email as parameter\n user = UserSignature(email=\"abc@zoho.com\")\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 a Token instance that takes the following parameters\n 1 -> OAuth client id.\n 2 -> OAuth client secret.\n 3 -> Grant token.\n 4 -> Refresh token.\n 5 -> OAuth redirect URL.\n 6 -> id\n \"\"\"\n token = OAuthToken(client_id=\"clientId\", client_secret=\"clientSecret\", grant_token=\"grant_token\", refresh_token=\"refresh_token\", redirect_url=\"redirectURL\", id=\"id\")\n\n \"\"\"\n Create an instance of TokenStore\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 \"\"\"\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(zcrmsdk/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 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 (in the key resource_path) to store user-specific files containing information about fields in modules. \n \"\"\"\n resource_path = '/Users/user_name/Documents/python-app'\n\n \"\"\"\n Call the static initialize method of Initializer class that takes the following arguments\n 1 -> UserSignature instance\n 2 -> Environment instance\n 3 -> Token instance\n 4 -> TokenStore instance\n 5 -> SDKConfig instance\n 6 -> resource_path\n 7 -> Logger instance\n \"\"\"\n Initializer.initialize(user=user, environment=environment, token=token, store=store, sdk_config=config, resource_path=resource_path, logger=logger)\n\n try:\n module_api_name = 'Leads'\n\n param_instance = ParameterMap()\n\n param_instance.add(GetRecordsParam.converted, 'both')\n\n param_instance.add(GetRecordsParam.cvid, '12712717217218')\n\n header_instance = HeaderMap()\n\n header_instance.add(GetRecordsHeader.if_modified_since, datetime.now())\n\n response = RecordOperations().get_records(module_api_name, param_instance, header_instance)\n\n if response is not None:\n\n # Get the status code from response\n print('Status Code: ' + str(response.get_status_code()))\n\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\n # Get object from response\n response_object = response.get_object()\n\n if response_object is not None:\n\n # Check if expected ResponseWrapper instance is received.\n if isinstance(response_object, ResponseWrapper):\n # Get the list of obtained Record instances\n record_list = response_object.get_data()\n\n for record in record_list:\n\n # Get the ID of each Record\n print(\"Record ID: \" + record.get_id())\n\n # Get the createdBy User instance of each Record\n created_by = record.get_created_by()\n\n # Check if created_by is not None\n if created_by is not None:\n # Get the Name of the created_by User\n print(\"Record Created By - Name: \" + created_by.get_name())\n\n # Get the ID of the created_by User\n print(\"Record Created By - ID: \" + created_by.get_id())\n\n # Get the Email of the created_by User\n print(\"Record Created By - Email: \" + created_by.get_email())\n\n # Get the CreatedTime of each Record\n print(\"Record CreatedTime: \" + str(record.get_created_time()))\n\n if record.get_modified_time() is not None:\n # Get the ModifiedTime of each Record\n print(\"Record ModifiedTime: \" + str(record.get_modified_time()))\n\n # Get the modified_by User instance of each Record\n modified_by = record.get_modified_by()\n\n # Check if modified_by is not None\n if modified_by is not None:\n # Get the Name of the modified_by User\n print(\"Record Modified By - Name: \" + modified_by.get_name())\n\n # Get the ID of the modified_by User\n print(\"Record Modified By - ID: \" + modified_by.get_id())\n\n # Get the Email of the modified_by User\n print(\"Record Modified By - Email: \" + modified_by.get_email())\n\n # Get the list of obtained Tag instance of each Record\n tags = record.get_tag()\n\n if tags is not None:\n for tag in tags:\n # Get the Name of each Tag\n print(\"Record Tag Name: \" + tag.get_name())\n\n # Get the Id of each Tag\n print(\"Record Tag ID: \" + tag.get_id())\n\n # To get particular field value\n print(\"Record Field Value: \" + str(record.get_key_value('Last_Name')))\n\n print('Record KeyValues: ')\n\n for key, value in record.get_key_values().items():\n print(key + \" : \" + str(value))\n\n # Check if the request returned an exception\n elif isinstance(response_object, APIException):\n # Get the Status\n print(\"Status: \" + response_object.get_status().get_value())\n\n # Get the Code\n print(\"Code: \" + response_object.get_code().get_value())\n\n print(\"Details\")\n\n # Get the details dict\n details = response_object.get_details()\n\n for key, value in details.items():\n print(key + ' : ' + str(value))\n\n # Get the Message\n print(\"Message: \" + response_object.get_message().get_value())\n\n except Exception as e:\n print(e)\n\n\nRecord.get_records()\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 2.1 APIs",
"version": "3.0.0",
"project_urls": {
"Homepage": "https://github.com/zoho/zohocrm-python-sdk-2.1"
},
"split_keywords": [
"development",
" zoho",
" crm",
" api",
" zcrmsdk",
" sdk",
" zcrm",
" zohocrmsdk2_1"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "c043266437cf13a504f6c8714e3a509310ae78e17bbd63b5ca4897a9ba42ce9e",
"md5": "11754ca2953854646a3179dcc11a8969",
"sha256": "181e3d9836472cf51a80a867093bf3a9f651c513a11ddb3a895b59eaccc4167b"
},
"downloads": -1,
"filename": "zohocrmsdk2_1-3.0.0-py2.py3-none-any.whl",
"has_sig": false,
"md5_digest": "11754ca2953854646a3179dcc11a8969",
"packagetype": "bdist_wheel",
"python_version": "py2.py3",
"requires_python": null,
"size": 498842,
"upload_time": "2024-04-16T14:31:04",
"upload_time_iso_8601": "2024-04-16T14:31:04.758481Z",
"url": "https://files.pythonhosted.org/packages/c0/43/266437cf13a504f6c8714e3a509310ae78e17bbd63b5ca4897a9ba42ce9e/zohocrmsdk2_1-3.0.0-py2.py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "fd9ca0f74297e4bc3a7f6562da34af0499027bd4ae021625604510e3ef1ab20f",
"md5": "adff6ad0c95302c3471eea6c4a2df213",
"sha256": "aac03f3ed7fa65c1bd12fdfbf4c7a1c7875f38742e83aed6a855f943ad2f2827"
},
"downloads": -1,
"filename": "zohocrmsdk2_1-3.0.0.tar.gz",
"has_sig": false,
"md5_digest": "adff6ad0c95302c3471eea6c4a2df213",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 241016,
"upload_time": "2024-04-16T14:31:06",
"upload_time_iso_8601": "2024-04-16T14:31:06.431692Z",
"url": "https://files.pythonhosted.org/packages/fd/9c/a0f74297e4bc3a7f6562da34af0499027bd4ae021625604510e3ef1ab20f/zohocrmsdk2_1-3.0.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-04-16 14:31:06",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "zoho",
"github_project": "zohocrm-python-sdk-2.1",
"travis_ci": false,
"coveralls": false,
"github_actions": false,
"lcname": "zohocrmsdk2-1"
}