Flask – RESTPlus build production application of use first released in: blog.ihypo.net/15273247538…

This article is from a practical summary of a project in which sensitive information has been hidden or replaced with the word Resource.

Flask-restplus is easy to use, but it’s easy to use. However, there is always a sense of conflict with the current structure of the project when actually writing the code, so put together a previous review of a revamped project to share and discuss best practices.

Flask-restplus is a popular Flask extension for generating Swagger files, but it requires a bit of a change in the structure of the project. If you have a new project from 0 to 1, it doesn’t matter, but if you have an existing project, it does require a bit of work. This paper concludes by summarizing specific project transformation and further explanation of flask-Restplus.

The blueprint and API

In large Flask projects, in order to prevent the dependency chaos of each module, modules are generally divided and blueprints of each module are registered uniformly in the APP factory method. Flask-restplus as Flask extensions can be bundled with Flask app to host views registered in Flask-RestPlus, as shown in the official documentation:

app = Flask(__name__)
api = Api(app)
Copy the code

Flask-restplus can be used as a Namespace for new projects. However, if you migrate an old project, the cost is still quite high. Therefore, you can bind the blueprints to the Flask-Restplus Api. In this way, the original module division is retained and Namespace can be used for more detailed logical division.

For example, for a project of course, there are multiple blueprints to divide relatively independent modules. Let’s take the Resource module as an example. After the large modules are divided by flask’s blueprint, the details are divided again by Namespace:

desc = """
resource type 1, type 2, type 3, type 4, type 5 api
"""

resource_blueprint = Blueprint("Resource", __name__)
api = Api(resource_blueprint, version='1.0', title='Resource info'. description=desc) api.add_namespace(resource_type_1_api) api.add_namespace(resource_type_2_api) api.add_namespace(resource_type_3_api) api.add_namespace(resource_type_4_api) api.add_namespace(resource_type_5_api)Copy the code

Resource supports five different types of apis, and although these types of apis belong to the same blueprint, they are relatively independent, so you can use Namespace to make a more detailed distinction and then register these five namespaces into the API. So the blueprints directory structure is as follows:

. ├ ─ ─ just set py ├ ─ ─ action │ ├ ─ ─ just set py │ ├ ─ ─ apis. Py │ └ ─ ─ dto. Py ├ ─ ─ health. Py ├ ─ ─ json_schema. Py └ ─ ─ the resource ├ ─ ─ just set py ├ ─ ─ type_1. Py ├ ─ ─ type_2. Py ├ ─ ─ type_3. Py ├ ─ ─ type_4. Py ├ ─ ─ type_5. Py └ ─ ─ dto. PyCopy the code

Parameter check and permission verification

Registration issues have been resolved, and some common facilities need to be modified, such as parameter checking and API permission authentication. In the past it was handled like this:

@resource_blueprint.route("/", methods=['POST'])
@internal_token_validator
@request_json_validator(SEND_TYPE_1_SCHEMA)
@tracing_span("post_type_1:type_1_api")
def op_type_1(a):
    pass
Copy the code

The token validation logic is written in the internal_token_validator decorator. The flask-Restplus API class supports registering decorators, but not all apis require token authentication, so it cannot be registered directly. But there’s a huge percentage of certified apis, so I’m still using decorators, and I’m going to have more than six decorators and I’m going to be pretty ugly writing the same logic all over the place, so I inherited the flask-Restplus view class Resource, and I copied the Dispatch function, If there are methods that require token authentication, the internal_token_validator decorator is dynamically placed in method_decorators, which is called when flask-RestPlus processes view methods.

Flask-restplus provides parameter validation, but it’s not enough for us. DCS has always used JSON-Schema for parameter validation. In the example above, the request_jSON_validator decorator handles the logic, passing in a JSON-Schema rule and validating the JSON body in the request before processing the API function. If the validation fails, a friendly 400 Response is encapsulated.

In order to facilitate jSON-schema validation, I also encapsulated the related logic in the inherited view base class.

class BaseView(Resource):
    json_schemas = {}
    internal_token_required = ['get'.'post'.'delete'.'put'.'patch']

    def dispatch_request(self, *args, **kwargs):
        from message.common.errors import APIError
        try:
            method = request.method.lower()
            self.validate(self.json_schemas.get(method))
            if method in self.internal_token_required:
                self.method_decorators = self.method_decorators + [internal_token_validator, ]
            return super(BaseView, self).dispatch_request(*args, **kwargs)
        except APIError as e:
            rsp = Response(
                response=json.dumps(e.render()), status=e.status_code,
                mimetype='application/json')
            return rsp

    @staticmethod
    def validate(schema):
        from message.common.errors import APIError
        if not schema:
            return
        data = request.json
        try:
            validate(data, schema)
        except ValidationError as e:
            raise APIError("ARGS_ERROR", e.message, 400)
Copy the code

DTO

Finally, I would like to talk about the problem of packet guidance. In the previous article, I also mentioned that flask-Restplus is prone to cross-reference. Unlike demo, this problem can not be avoided by magic techniques, but by more detailed module division. It was the introduction of Dtos in the article “How to Structure a Flask-RESTPlus Web Service for Production Builds” (link below) that led me to a more “structured” solution.

In traditional Flask applications, blueprints are registered in the APP factory method, and packages in blueprints are relatively independent. Flask-restplus introduces namespace. We use it as a blueprint for a more granular existence, so we can refer to the blueprint and encapsulate namespace definitions and dependencies in a class, which avoids circular references and makes the overall structure of the project clearer.

For example, Type1 DTOS:

class Type1Dto:
    api = Namespace("type1", path="/type1", decorators=[internal_token_validator])
    action_model = api.model('ActionModel', action_desc)
    create_model = api.model("CreateType1Model", {
        "type1_title": fields.List(fields.String(description="Type1 title"), required=True),
        "type1_info": fields.Nested(api.model("Type1InfoModel", {
            "content": fields.String(description="Type1 content"),
        }), required=True),
    })
    template_model = api.model("TemplateType1Model", {
        "type1_title": fields.List(fields.String(description="Type1 title", required=True)),
        "content": fields.Nested(api.model("TemplateContent", {}), required=True),
    })
    model = api.model("Type1Model", {
        "total": fields.Integer(readOnly=True, description="action total"),
        "results": fields.List(fields.Nested(action_model)),
    })
Copy the code

This contains the namespace definition, the request format object (flask-RestPlus generates the request document from it), And response (on which flask-RestPlus renders JSON and generates a Response document).

When used, import dtos into the view layer, and the associated model will also come in handy:

from .dto import Type1Dto

api = Type1Dto.api


@api.route("/")
class Type1Api(Resource):
    json_schemas = {"post": SEND_TYPE_1_SCHEMA}

    @tracing_span("post_type_1:type_1_api")
    @api.expect(Type1Dto.create_model)
    @api.param("Token", description="internal token.", _in="header", required=True)
    def post(self):
        actions = deal_with_type_1(api.payload['type_1_title'], api.payload['content'])
        result = {
            "total": len(actions),
            "results": [a.render() for a in actions]
        }
        return result
Copy the code

Finally, the API of view layer is imported to the place defined by blueprint to complete registration, so that the whole project not only achieves reasonable structure classification, but also completes and solves the problem of guide package.


Resources: How to structure a Flask – RESTPlus web service for production builds “: medium.freecodecamp.org/structuring…