Jack Hales
Jack Hales

Reputation: 1654

How to static type check dicts for standard API Response types?

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.

Upvotes: 0

Views: 118

Answers (1)

TanThien
TanThien

Reputation: 752

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

Upvotes: 0

Related Questions