Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bug in databricks.sql.exc.Error base class #437

Open
chidifrank opened this issue Sep 10, 2024 · 4 comments
Open

Bug in databricks.sql.exc.Error base class #437

chidifrank opened this issue Sep 10, 2024 · 4 comments

Comments

@chidifrank
Copy link

chidifrank commented Sep 10, 2024

Problem: When try except catching errors and logging them I occasionally get this TypeError

...
    try:
        with connection.cursor() as cursor:
            cursor.execute(query)
            ...
    except databricks.sql.Error as e:
        logger.debug(f"{str(e)}: failed to execute query {query}")
        ...

__str__ returned non-string (type NoneType)

Explanation:

String representations of a class must be of type string

the message attribute can be None therefore there needs to be a check/cast prior to returning.

Note: message_with_context will also fail when a message is None since you can't use the operand type + for NoneType and a str

class Error(Exception):
    """Base class for DB-API2.0 exceptions.
    `message`: An optional user-friendly error message. It should be short, actionable and stable
    `context`: Optional extra context about the error. MUST be JSON serializable
    """

    def __init__(self, message=None, context=None, *args, **kwargs):
        super().__init__(message, *args, **kwargs)
        self.message = message
        self.context = context or {}

    def __str__(self):
        return self.message

    def message_with_context(self):
        return self.message + ": " + json.dumps(self.context, default=str)
@kravets-levko
Copy link
Collaborator

@chidifrank thank you for reporting this! May I ask you to share a stacktrace for this error so I can understand where it comes from? None of that errors should be raised without a message, so your case indicates that something is wrong elsewhere

@chidifrank
Copy link
Author

@kravets-levko
Sure!

---------------------------------------------------------------------------
ServerOperationError

File /local_disk0/.ephemeral_nfs/cluster_libraries/python/lib/python3.10/site-packages/databricks/sql/client.py:768, in Cursor.execute(self, operation, parameters)
    767 self._close_and_clear_active_result_set()
--> 768 execute_response = self.thrift_backend.execute_command(
    769     operation=prepared_operation,
    770     session_handle=self.connection._session_handle,
    771     max_rows=self.arraysize,
    772     max_bytes=self.buffer_size_bytes,
    773     lz4_compression=self.connection.lz4_compression,
    774     cursor=self,
    775     use_cloud_fetch=self.connection.use_cloud_fetch,
    776     parameters=prepared_params,
    777 )
    778 self.active_result_set = ResultSet(
    779     self.connection,
    780     execute_response,
   (...)
    783     self.arraysize,
    784 )

File /local_disk0/.ephemeral_nfs/cluster_libraries/python/lib/python3.10/site-packages/databricks/sql/thrift_backend.py:869, in ThriftBackend.execute_command(self, operation, session_handle, max_rows, max_bytes, lz4_compression, cursor, use_cloud_fetch, parameters)
    868 resp = self.make_request(self._client.ExecuteStatement, req)
--> 869 return self._handle_execute_response(resp, cursor)

File /local_disk0/.ephemeral_nfs/cluster_libraries/python/lib/python3.10/site-packages/databricks/sql/thrift_backend.py:961, in ThriftBackend._handle_execute_response(self, resp, cursor)
    959 self._check_direct_results_for_error(resp.directResults)
--> 961 final_operation_state = self._wait_until_command_done(
    962     resp.operationHandle,
    963     resp.directResults and resp.directResults.operationStatus,
    964 )
    966 return self._results_message_to_execute_response(resp, final_operation_state)

File /local_disk0/.ephemeral_nfs/cluster_libraries/python/lib/python3.10/site-packages/databricks/sql/thrift_backend.py:807, in ThriftBackend._wait_until_command_done(self, op_handle, initial_operation_status_resp)
    806     operation_state = poll_resp.operationState
--> 807     self._check_command_not_in_error_or_closed_state(op_handle, poll_resp)
    808 return operation_state

File /local_disk0/.ephemeral_nfs/cluster_libraries/python/lib/python3.10/site-packages/databricks/sql/thrift_backend.py:607, in ThriftBackend._check_command_not_in_error_or_closed_state(self, op_handle, get_operations_resp)
    606     else:
--> 607         raise ServerOperationError(
    608             get_operations_resp.errorMessage,
    609             {
    610                 "operation-id": op_handle
    611                 and self.guid_to_hex_id(op_handle.operationId.guid),
    612                 "diagnostic-info": None,
    613             },
    614         )
    615 elif get_operations_resp.operationState == ttypes.TOperationState.CLOSED_STATE:

<class 'str'>: (<class 'TypeError'>, TypeError('__str__ returned non-string (type NoneType)'))```

@kravets-levko
Copy link
Collaborator

Thank you! Yeah, looks that it is initialized with the error message from server, but it actually may be empty. We need additional check there, as well as a reasonable fallback (probably)

@chidifrank
Copy link
Author

Thanks for the effort! @kravets-levko

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants