zoho / zohocrm-python-sdk-7.0

Apache License 2.0
0 stars 0 forks source link

Intermittent TypeError in API Response using `search_records` #5

Closed CRFL6372 closed 1 week ago

CRFL6372 commented 1 week ago

When using RecordOperations.search_records() to retrieve records by criteria, we encounter a TypeError on approximately 5-10% of records. The error traceback indicates an issue converting a dict to an int within the json_converter.py of the SDK:

TypeError: int() argument must be a string, a bytes-like object or a real number, not 'dict'

Steps:

  1. Use RecordOperations.search_records() with a search criteria.
  2. Iterate through the records returned.
  3. Observe that in around 5-10% of cases, the SDK raises a the error when processing the API response.

Observed Behavior:

The API response unexpectedly includes a dict where an int or other simple type is expected.

Expected Behavior:

API response should consistently return expected data types across all records of a given module.

Environment:

Additional Notes:

Searching records by criteria works fine in the old version of the SDK. The records which cause conflict here (in my example below) are perfectly retrieved by the old version using search_records_by_criteria. In all honestly I feel that the old version is less problematic and has a much more straightforward design all around.

My example, with snippets and traceback:

Two records in our "Deals" module. They each have a unique value for a field called "Acuity Code".

Below are identical tests for each record using search_records. Implementation of search_records simply returns the first record object found:

ZohoRecord.get_record_from_certificate


class ZohoRecord(object):

    @staticmethod
    def get_record_from_certificate(module_api_name, acuity_certificate):

        print(f"Debug - Starting get_record_from_certificate for {acuity_certificate}")
        print(f"Acuity certificate type: {type(acuity_certificate)}")

        # Get instance of RecordOperations Class
        record_operations = RecordOperations(module_api_name)

        # Get instance of ParameterMap Class
        param_instance = ParameterMap()

        # param_instance.add(
        #     SearchRecordsParam.criteria, "Acuity_Code:equals:623D1761")
        param_instance.add(
            SearchRecordsParam.criteria, "Acuity_Code:equals:" + acuity_certificate
        )

        print("Debug - marker just befor search record op")

        # # Call searchRecords method that takes ParameterMap Instance and moduleAPIName as parameter
        response = record_operations.search_records(param_instance)

        if response is not None:

            # Get object from response
            response_object = response.get_object()
            print(f"Debug - Starting get_record_from_certificate for {acuity_certificate}")

            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()
                    print(f"Debug - Records Returned: {record_list}")

                    for record in record_list:
                        # Get the ID of each Record and return
                        print(f"Debug - Processing Record ID: {record.get_id()}")
                        record_id = record.get_id()
                        print("Record ID: " + str(record_id))
                        return record

                # 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())

Testing Records:

Note: Both records with the specified critiera 100% exist in CRM and, as I mentioned before, are perfectly retrieved by the old SDK.

test.py
from zohocrmsdk.src.com.zoho.crm.api.modules import *
from zohocrmsdk.src.com.zoho.crm.api.record import RecordOperations, Record

from ZohoRecord import ZohoRecord
from ZohoSDKInitiallizer import ZohoSDKInitiallizer

from helper import AcuityAPI

ZohoSDKInitiallizer.initialize()

records_list = []
module = "Deals"
appointment_type_id = "13622855"  # Online class appointment type ID

certificates_dict = {"6A7DEE29":1, "623D1761":1}

for cert, remaining_count in certificates_dict.items():
    print(f"Debug - Certificate: {cert}, Type of Cert: {type(cert)}, Type of Remaining Count: {type(remaining_count)}, Remaining Count: {remaining_count}")

    record = ZohoRecord.get_record_from_certificate(module, cert)

    print(record)

Output

Note that the first record is retrieved, and it's ID printed, but the search_records response for the second record fails despite the arguments being of the same type, and both records existing equally in CRM.

Debug - Certificate: 6A7DEE29, Type of Cert: <class 'str'>, Type of Remaining Count: <class 'int'>, Remaining Count: 1d.record.Record object at 0x105d5e690>
Debug - Starting get_record_from_certificate for 6A7DEE29
Acuity certificate type: <class 'str'>
Debug marker - just befor search record op
Debug - API Response received: <zohocrmsdk.src.com.zoho.crm.api.util.api_response.APIResponse object at 0x105c92c90>
Debug - Starting get_record_from_certificate for 6A7DEE29
Debug - Records Returned: [<zohocrmsdk.src.com.zoho.crm.api.record.record.Record object at 0x105d27cb0>]
Debug - Processing Record ID: 4034173000188606002
Record ID: 4034173000188606002
<zohocrmsdk.src.com.zoho.crm.api.record.record.Record object at 0x105d27cb0>
Debug - Certificate: 55D0A83F, Type of Cert: <class 'str'>, Type of Remaining Count: <class 'int'>, Remaining Count: 1d.record.Record object at 0x105c83650>
Debug - Certificate: 623D1761, Type of Cert: <class 'str'>, Type of Remaining Count: <class 'int'>, Remaining Count: 1
Debug - Starting get_record_from_certificate for 623D1761
Acuity certificate type: <class 'str'>
Debug marker - just befor search record op
Traceback (most recent call last):
File "/Users/X/acuity-new/classes-remaining/venv/lib/python3.13/site-packages/zohocrmsdk/src/com/zoho/crm/api/util/common_api_handler.py", line 271, in api_call
return_object = convert_instance.get_wrapped_response(response, class_name)
File "/Users/X/acuity-new/classes-remaining/venv/lib/python3.13/site-packages/zohocrmsdk/src/com/zoho/crm/api/util/json_converter.py", line 445, in get_wrapped_response
return self.get_response(response.json(), pack)
~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/X/acuity-new/classes-remaining/venv/lib/python3.13/site-packages/zohocrmsdk/src/com/zoho/crm/api/util/json_converter.py", line 471, in get_response
instance = self.find_match(classes, response_json)
File "/Users/X/acuity-new/classes-remaining/venv/lib/python3.13/site-packages/zohocrmsdk/src/com/zoho/crm/api/util/json_converter.py", line 694, in find_match
return self.get_response(response_json, pack)
~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^
File "/Users/X/acuity-new/classes-remaining/venv/lib/python3.13/site-packages/zohocrmsdk/src/com/zoho/crm/api/util/json_converter.py", line 488, in get_response
instance = self.not_record_response(instance=instance, class_name=class_name,
response_json=response_json, class_detail=class_detail)
File "/Users/X/acuity-new/classes-remaining/venv/lib/python3.13/site-packages/zohocrmsdk/src/com/zoho/crm/api/util/json_converter.py", line 499, in not_record_response
member_value = self.get_data(key_data, key_detail)
File "/Users/X/acuity-new/classes-remaining/venv/lib/python3.13/site-packages/zohocrmsdk/src/com/zoho/crm/api/util/json_converter.py", line 568, in get_data
return self.get_data_value(data_type, key_data, member_detail)
~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/X/acuity-new/classes-remaining/venv/lib/python3.13/site-packages/zohocrmsdk/src/com/zoho/crm/api/util/json_converter.py", line 574, in get_data_value
member_value = self.get_collections_data(key_data, member_detail)
File "/Users/X/acuity-new/classes-remaining/venv/lib/python3.13/site-packages/zohocrmsdk/src/com/zoho/crm/api/util/json_converter.py", line 639, in get_collections_data
values.append(self.get_response(response, pack))
~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^
File "/Users/X/acuity-new/classes-remaining/venv/lib/python3.13/site-packages/zohocrmsdk/src/com/zoho/crm/api/util/json_converter.py", line 484, in get_response
instance = self.is_record_response(response_json, class_detail, package_name)
File "/Users/X/acuity-new/classes-remaining/venv/lib/python3.13/site-packages/zohocrmsdk/src/com/zoho/crm/api/util/json_converter.py", line 553, in is_record_response
key_value = self.get_data(key_data, key_detail)
File "/Users/X/acuity-new/classes-remaining/venv/lib/python3.13/site-packages/zohocrmsdk/src/com/zoho/crm/api/util/json_converter.py", line 568, in get_data
return self.get_data_value(data_type, key_data, member_detail)
~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/X/acuity-new/classes-remaining/venv/lib/python3.13/site-packages/zohocrmsdk/src/com/zoho/crm/api/util/json_converter.py", line 587, in get_data_value
member_value = DataTypeConverter.pre_convert(key_data, data_type)
File "/Users/X/acuity-new/classes-remaining/venv/lib/python3.13/site-packages/zohocrmsdk/src/com/zoho/crm/api/util/datatype_converter.py", line 111, in pre_convert
return DataTypeConverter.pre_converter_map[data_type](obj)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^
File "/Users/X/acuity-new/classes-remaining/venv/lib/python3.13/site-packages/zohocrmsdk/src/com/zoho/crm/api/util/datatype_converter.py", line 34, in <lambda>
DataTypeConverter.add_to_map("Integer", lambda obj: int(obj), lambda obj: int(obj))
~~~^^^^^
TypeError: int() argument must be a string, a bytes-like object or a real number, not 'dict'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
File "/Users/X/acuity-new/classes-remaining/test.py", line 25, in <module>
record = ZohoRecord.get_record_from_certificate(module, cert)
File "/Users/X/acuity-new/classes-remaining/ZohoRecord.py", line 48, in get_record_from_certificate
response = record_operations.search_records(param_instance)
File "/Users/X/acuity-new/classes-remaining/venv/lib/python3.13/site-packages/zohocrmsdk/src/com/zoho/crm/api/record/record_operations.py", line 529, in search_records
return handler_instance.api_call(ResponseHandler.__module__, 'application/json')
~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/X/acuity-new/classes-remaining/venv/lib/python3.13/site-packages/zohocrmsdk/src/com/zoho/crm/api/util/common_api_handler.py", line 282, in api_call
raise sdk_exception
zohocrmsdk.src.com.zoho.crm.api.exception.sdk_exception.SDKException: Caused By: None - int() argument must be a string, a bytes-like object or a real number, not 'dict'
raja-7453 commented 1 week ago

@CRFL6372 Please draft a mail to support@zohocrm.com. We will follow up on this request over there. Thanks!

CRFL6372 commented 1 week ago

Will do, raja. I have also found that the commonality between affected records is that they were created in CRM using a Wizard.

It seems something about their structure is not being properly handled by the SDK.