Skip to content

Simple error handler #82

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

Merged
merged 1 commit into from
Jun 2, 2020
Merged

Simple error handler #82

merged 1 commit into from
Jun 2, 2020

Conversation

bidoubiwa
Copy link
Contributor

@bidoubiwa bidoubiwa commented May 28, 2020

Error handling in python

Custom error created

  • MeiliSearchApiError: Throws when API returns bad status code
  • MeiliSearchCommunicationError: Throws when problem accessing the API

Usage

# test.py
import meilisearch


client = meilisearch.Client("http://127.0.0.1:7700")
try:
    index = client.create_index(uid="movies1")
    index = client.create_index(uid="movies1")
except meilisearch.meilisearch_api_error.MeiliSearchApiError as err:
    print(err)
except meilisearch.meilisearch_communication_error.MeiliSearchCommunicationError as err:
    print(err)

MeiliSearchApiError will output

MeiliSearchApiError, HTTP status: 400 -> Impossible to create index; index already exists

Implementation

MeiliSearchApiError

import json

class MeiliSearchApiError(Exception):
    """Error from MeiliSearch API"""
    def __init__(self, error, request):
        self.status_code = request.status_code
        if request.text:
            self.message = f'HTTP status: {self.status_code} -> {json.loads(request.text)["message"]}'
        else:
            self.message = error
        super().__init__(self.message)

    def __str__(self):
        return f'MeiliSearchApiError, {self.message}'

This error will use the default HTTP error message if MeiliSearch did not return a message.

It is called in the __validate method of the httpRequests class:

try:
     request.raise_for_status()
     return HttpRequests.__to_json(request)
except requests.exceptions.HTTPError as err:
     raise MeiliSearchApiError(err, request) from None

By adding from None we prevent the chaining of errors.

MeiliSearchCommunicationError

class MeiliSearchCommunicationError(Exception):
    """Error when communicating with MeiliSearch"""
    def __init__(self, message):
        self.message = message
        super().__init__(self.message)

    def __str__(self):
        return f'MeiliSearchCommunicationError, {self.message}'

This error will use the default error thrown by the HTTP request library to display the communication problem.

Mentionned in: /meilisearch/integration-guides#19

@bidoubiwa bidoubiwa requested review from eskombro and curquiza May 28, 2020 09:36
@curquiza curquiza linked an issue May 28, 2020 that may be closed by this pull request
@bidoubiwa bidoubiwa marked this pull request as ready for review May 28, 2020 09:56
Copy link
Member

@curquiza curquiza left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Too bad for the code duplication with MeiliSearchCommunicationError, but I don't find any other way to do that! What do you think @eskombro?

Can you add tests for your exceptions? 🙂 Especially testing the Communication error (you can put localhost:8800 if I'm not wrong). I know you have to wait for the @eskombro merge 😉

Are these changes breaking for you guys? @eskombro @bidoubiwa
For me, they are.
If they are, can you rename the apikey parameter into api_key please? :)

@@ -14,41 +16,52 @@ def __init__(self, config):


Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change

)
return self.__validate(request)
except requests.exceptions.ConnectionError as err:
raise MeiliSearchCommunicationError(err) from None
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
raise MeiliSearchCommunicationError(err) from None
raise MeiliSearchCommunicationError(err) from None

def __init__(self, error, request):
self.status_code = request.status_code
if request.text:
self.message = f'{json.loads(request.text)["message"]}'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if the is no "message" in the body? (it should not happen but it would be bad if there is an exception in the exception ^^ There was a time where MeiliSearch did not return any message for some HTTP error = 4XX or 5XX)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, should check if there's a message before setting it to the internat attribute. And have a default value like "No message received" for example

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't you think that the user would believe that there is an error because "there is no message" with this default value? @eskombro

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a simple unknown can solve that?

@curquiza
Copy link
Member

@bidoubiwa I need confirmation about usage.

With the MeiliSearchCommunicationError, if can write:

except meilisearch.meilisearch_communication_error.MeiliSearchCommunicationError as err:
    print(err.message)

And with MeiliSearchApiError:

except meilisearch.meilisearch_api_error.MeiliSearchApiError as err:
    print(err.status_code)
    print(err.message)

Can you confirm that?
Because the __str__ method is here for debugging (= display in the terminal) but http_status and message are mandatory for programming.

@curquiza
Copy link
Member

Like your work about Error handler anyway! It was needed!!

@curquiza curquiza linked an issue May 28, 2020 that may be closed by this pull request
Copy link
Member

@eskombro eskombro left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This error handler is great, and message is way better that returning the http error. I really like it.

I think that those error classes should be internal classes to HttpRequests class, and not separate classes in the package, what do you think @curquiza and @bidoubiwa ?

Or, we can have a MeiliSearchException (or MeiliSearchError) class and those classes inherit from the Super Error class, modifying just what is specific

:)

def __init__(self, error, request):
self.status_code = request.status_code
if request.text:
self.message = f'{json.loads(request.text)["message"]}'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, should check if there's a message before setting it to the internat attribute. And have a default value like "No message received" for example

@curquiza
Copy link
Member

I think that those error classes should be internal classes to HttpRequests class, and not separate classes in the package, what do you think @curquiza and @bidoubiwa ?

Not sure doing internal class error for the HTTPRequest class would be a good practice. I found these both separated files in Algolia and stripe packages.

Or, we can have a MeiliSearchException (or MeiliSearchError) class and those classes inherit from the Super Error class, modifying just what is specific

Why not! I tried to start a thing like that in our ruby SDK but completely useless for the moment (because of the lazy "we'll see later" implementation, not the idea 😂)

@eskombro
Copy link
Member

eskombro commented May 29, 2020

We checked with @bidoubiwa how several 'famous' packages handle errors. Seemds like conclusions are:

  1. Validate the Super error class idea, with inheriting for every specific error.
  2. Unvalidating the "internal class" idea. Nevertheless, errors are in one single file at the 'root' (same level as Index, Client)

We also agreed on adding tests, @bidoubiwa waits for #80 to be merged to implement the tests

Too bad for the code duplication with MeiliSearchCommunicationError, but I don't find any other way to do that! What do you think @eskombro?

I agree, but every way of implementing DRY I can think of makes me feel like we would be adding unnecessary complexity and loosing in readability, so I would prefer to accpt @bidoubiwa 's proposition

Are these changes breaking for you guys? @eskombro @bidoubiwa
For me, they are.

I think we should consider them as breaking. This has an impact an almost every return value

@eskombro
Copy link
Member

eskombro commented May 29, 2020

Ah, PS: Also, removing from None after raising an error, to have all the chain of errors in the console

@eskombro eskombro force-pushed the error_handler branch 3 times, most recently from 7acfca9 to 5c59b0c Compare June 2, 2020 14:46
@eskombro eskombro requested a review from curquiza June 2, 2020 14:51
- Parent MeiliSearch error class, single file and heritage
- Tests: MeiliSearchCommunicationError and MeiliSearchApiError
- Http requests error raise refacto
Copy link
Member

@curquiza curquiza left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@eskombro eskombro merged commit 475d865 into master Jun 2, 2020
@eskombro eskombro deleted the error_handler branch June 2, 2020 15:54
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

Successfully merging this pull request may close these issues.

Include message when Client Error is raised Error handler
3 participants