Passed
Push — master ( 6277dc...c802a6 )
by Mingyu
02:00
created

app.views   A

Complexity

Total Complexity 22

Size/Duplication

Total Lines 148
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 22
eloc 82
dl 0
loc 148
rs 10
c 0
b 0
f 0

5 Functions

Rating   Name   Duplication   Size   Complexity  
A after_request() 0 8 1
A exception_handler() 0 7 2
B gzipped() 0 24 4
D json_required() 0 28 8
A auth_required() 0 17 2

4 Methods

Rating   Name   Duplication   Size   Complexity  
A Router.init_app() 0 9 1
A BaseResource.__init__() 0 2 1
A BaseResource.unicode_safe_json_dumps() 0 14 1
A Router.__init__() 0 3 2
1
from functools import wraps
2
import gzip
3
import time
4
5
import ujson
6
7
from flask import Response, abort, after_this_request, g, request
8
from flask_jwt_extended import jwt_required, get_jwt_identity
9
from flask_restful import Resource
10
from werkzeug.exceptions import HTTPException
11
12
13
def after_request(response):
14
    """
15
    Set header - X-Content-Type-Options=nosniff, X-Frame-Options=deny before response
16
    """
17
    response.headers['X-Content-Type-Options'] = 'nosniff'
18
    response.headers['X-Frame-Options'] = 'deny'
19
20
    return response
21
22
23
def exception_handler(e):
24
    print(e)
25
26
    if isinstance(e, HTTPException):
27
        return e.description, e.code
28
    else:
29
        return '', 500
30
31
32
def gzipped(fn):
33
    """
34
    View decorator for gzip compress the response body
35
    """
36
    @wraps(fn)
37
    def wrapper(*args, **kwargs):
38
        @after_this_request
39
        def zipper(response):
40
            if 'gzip' not in request.headers.get('Accept-Encoding', '')\
41
                    or not 200 <= response.status_code < 300\
42
                    or 'Content-Encoding' in response.headers:
43
                # 1. Accept-Encoding에 gzip이 포함되어 있지 않거나
44
                # 2. 200번대의 status code로 response하지 않거나
45
                # 3. response header에 이미 Content-Encoding이 명시되어 있는 경우
46
                return response
47
48
            response.data = gzip.compress(response.data)
49
            response.headers['Content-Encoding'] = 'gzip'
50
            response.headers['Vary'] = 'Accept-Encoding'
51
            response.headers['Content-Length'] = len(response.data)
52
53
            return response
54
        return fn(*args, **kwargs)
55
    return wrapper
56
57
58
def auth_required(model):
59
    def decorator(fn):
60
        """
61
        View decorator for access control
62
        """
63
        @wraps(fn)
64
        @jwt_required
65
        def wrapper(*args, **kwargs):
66
            user = model.objects(id=get_jwt_identity()).first()
67
            if not user:
68
                abort(403)
69
70
            g.user = user
71
72
            return fn(*args, **kwargs)
73
        return wrapper
74
    return decorator
75
76
77
def json_required(required_keys):
78
    """
79
    View decorator for JSON validation.
80
81
    - If content-type is not application/json : returns status code 406
82
    - If required_keys are not exist on request.json : returns status code 400
83
84
    Args:
85
        required_keys (dict): Required keys on requested JSON payload
86
    """
87
    def decorator(fn):
88
        if fn.__name__ == 'get':
89
            print('[WARN] JSON with GET method? on "{}()"'.format(fn.__qualname__))
90
91
        @wraps(fn)
92
        def wrapper(*args, **kwargs):
93
            if not request.is_json:
94
                abort(406)
95
96
            for key, typ in required_keys.items():
97
                if key not in request.json or not isinstance(request.json[key], typ):
98
                    abort(400)
99
                if typ is str and not request.json[key]:
100
                    abort(400)
101
102
            return fn(*args, **kwargs)
103
        return wrapper
104
    return decorator
105
106
107
class BaseResource(Resource):
108
    """
109
    BaseResource with some helper functions based flask_restful.Resource
110
    """
111
    def __init__(self):
112
        self.now = time.strftime('%Y-%m-%d %H:%M:%S')
113
114
    @classmethod
115
    def unicode_safe_json_dumps(cls, data, status_code=200, **kwargs) -> Response:
116
        """
117
        Helper function which processes json response with unicode using ujson
118
119
        Args:
120
            data (dict and list): Data for dump to JSON
121
            status_code (int): Status code for response
122
        """
123
        return Response(
124
            ujson.dumps(data, ensure_ascii=False),
125
            status_code,
126
            content_type='application/json; charset=utf8',
127
            **kwargs
128
        )
129
130
131
class Router:
132
    """
133
    REST resource routing helper class like standard flask 3-rd party libraries
134
    """
135
    def __init__(self, app=None):
136
        if app is not None:
137
            self.init_app(app)
138
139
    def init_app(self, app):
140
        """
141
        Routes resources. Use app.register_blueprint() aggressively
142
        """
143
        app.after_request(after_request)
144
        app.register_error_handler(Exception, exception_handler)
145
146
        from app.views import sample
147
        app.register_blueprint(sample.api.blueprint)