Passed
Push — master ( dd146f...81cb0a )
by Mingyu
01:18
created

app.views.gzipped()   B

Complexity

Conditions 4

Size

Total Lines 24
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

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