0

I am looking to learn how to make a more robust API in Python using pythonic methods. My goal is to achieve a more strict API response, to reduce the amount of parsing errors on our frontend and increase standardisation for response types (error states, etc).

For the sake of argument, I am using Python 3.11 and Flask. Below is sample code:

from flask import Flask, jsonify
from typing import TypedDict, Any, assert_type, Union

app = Flask(__name__)

# This is our forced API Response that we want to type check.
# Simplified Union data for argument's sake.
class APIResponse(TypedDict):
   success: bool
   data: Union[dict, list]

@app.get("/")
def index():
   response = {
      "success": True,
      "data": { "hello": "World!" }
   }
   assert_type(response, APIResponse)
   return jsonify(response)

if __name__ == "__main__":
   app.run(debug=True)

No errors, as expected. But when changing response to the following:

{
   "success": "notABool",
   "dat": { "hello", "World!" } # a Set, does not satisfy our type union, an easy (but platonic) mistake
}

I also get no errors (warnings) showing up in my IDE.

I've read the following to try to find answers, but some assume MyPy and others create really lax static type checking (dict is a dict, therefore is good):

I understand I could use PyDantic, but I would rather not wait for runtime errors to find out that an API response was not implemented correctly.

Thanks for any help.

1
  • 2
    Without using something like MyPy to enforce static type checking, it will very difficult to find type errors outside of runtime. Another approach is Pydantic + unit testing your API. This will enforce a good standard and catch any errors that might appear at runtime Commented Feb 21, 2024 at 4:37

1 Answer 1

0

To check type or format all api response. You can use register_error_handler and after_request. Example:

class HandleSuccess:
    @classmethod
    def after_request_handler(cls, response):
        # do here all what you need with success responses
        # assert_type(response, APIResponse)
        print('good response %s' % response.status_code)
        return response

class HandleError:
    @classmethod
    def logger_error(cls, e):
        app.logger.error("ERROR: %s" % str(e), exc_info=True)
    
    @classmethod
    def handle(cls, error_return):
        cls.logger_error(error_return)
        return jsonify(success=False, error=str(error_return))

In app.py:

app.register_error_handler(Exception, HandleError.handle)
app.after_request(HandleSuccess.after_request_handler)

Now, you can handle api without manual check in each api

Sign up to request clarification or add additional context in comments.

2 Comments

Hi @TanThien, this is helpful although it describes catching runtime errors (important), rather than indicating errors in the type checker statically inherently for all requests.
@JackHales, what about you add a try/except for assert_type in after_request_handler. if format error it using code from except to override response. In my application, I use after_request_handler to put data to format {susscess: true, data: resposne.data} and controller only return the body data {hello: world!}. It is more standard because I have service name in response and the service name is a os ENV of container name. It is better than you add format return in each api

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.