__init__.py (7330B)
1 import os 2 import sys 3 import re 4 import logging 5 from flask import Flask, request, send_file, abort, g 6 from flask_cors import CORS 7 from flask_jwt_extended import JWTManager 8 from encadre import Framework 9 10 logger = logging.getLogger(__name__) 11 12 13 class FlaskFramework(Framework): 14 def __init__(self, application, config): 15 Framework.__init__(self, application, config) 16 self.versions = {} 17 self._endpoints = set() 18 self.application = application 19 self.flask_app = Flask(application) 20 self.flask_app.url_map.strict_slashes = False 21 self.flask_app.register_error_handler(404, self.handle_exception) 22 for k, v in (self.config.get('flask') or {}).items(): 23 _v = v 24 if v.lower() == 'false': 25 _v = False 26 if v.lower() == 'true': 27 _v = True 28 if v.isdigit(): 29 _v = int(v) 30 self.flask_app.config[k.upper()] = _v 31 if not (self.config.get('flask') or {}).get('external_cors'): 32 logger.debug("We handle CORS.") 33 CORS(self.flask_app, supports_credentials=True) 34 else: 35 logger.debug("CORS must be handheld upstream.") 36 if (self.config.get('flask') or {}).get('JWT_SECRET_KEY'.lower()): 37 self.jwt_manager = JWTManager(self.flask_app) 38 39 def setup_application(self, instance): 40 Framework.setup_application(self, instance) 41 for fct in self.before_call_chain: 42 self.flask_app.before_request(fct) 43 for fct in self.after_call_chain: 44 self.flask_app.after_request(fct) 45 self.flask_app.after_request(self._set_cookies) 46 47 def _add_method(self, ctrl_name, method_name): 48 def accessor(**args): 49 import flask 50 v = None 51 if self.versioning_use_prefix: 52 v = flask.request.environ.get('PATH_INFO').lstrip('/').split( 53 '/')[0] 54 logger.debug("Got version '%s' from URL." % v) 55 else: 56 # flask handle case and underscores 57 h = 'X-%s-Version' % self.application 58 v = flask.request.headers.get(h) 59 logger.debug("Got version '%s' from header '%s'." % (v, h)) 60 if v and not v.isdigit(): 61 flask.abort(405) 62 v = int(v) if v else sys.maxsize 63 v = v if v in self.controllers[ctrl_name] else max( 64 self.controllers[ctrl_name].keys()) 65 verb = flask.request.environ['REQUEST_METHOD'] 66 cls = self.controllers[ctrl_name][v]['cls'] 67 try: 68 m = self.controllers[ctrl_name][v]['methods'][method_name][ 69 verb]['fct'].__name__ 70 except KeyError: 71 logger.error("Cannot found method for: controller: '%s', " 72 "method: '%s', version: '%s', verb: '%s'" % 73 (ctrl_name, method_name, v, verb)) 74 flask.abort(405) 75 flask.g._encadre_cookies = [] 76 try: 77 ret = getattr(cls(), m)(**args) 78 except Exception as e: 79 ret = self.handle_exception(e) 80 return ret 81 82 setattr(self, '_m_%s_%s' % (ctrl_name, method_name), accessor) 83 84 def handle_exception(self, e): 85 message, code = self.format_exception(e) 86 if not message: 87 abort(code) 88 return message, code 89 90 def _mk_endpoint(self, ctrl_name, method_name, args, version): 91 ep = "/%s/%s%s" % (ctrl_name.lower(), 92 method_name if method_name != 'index__' else '', 93 ('/' + '/'.join(['<%s>' % a 94 for a in args]) if args else '')) 95 return '/%s%s' % (version, ep) if version else ep 96 97 def _setup_routes(self): 98 """ Add routes to flask """ 99 endpoints = {} 100 methods = {} 101 for c in sorted(self.controllers): 102 for v in self.controllers[c]: 103 for m in self.controllers[c][v]['methods']: 104 for _v in self.controllers[c][v]['methods'][m]: 105 fct = self.controllers[c][v]['methods'][m][_v]['fct'] 106 args = self.controllers[c][v]['methods'][m][_v]['args'] 107 e = self._mk_endpoint( 108 c, 109 m, 110 args, 111 version=v if self.versioning_use_prefix else None) 112 endpoints.setdefault(e, set()) 113 if isinstance(fct.__defaults__, tuple): 114 url = re.search(r'^(.*[\\\/])', e).group() 115 endpoints.setdefault(url, set()) 116 endpoints[url].add(_v) 117 methods[url] = (c, m) 118 endpoints[e].add(_v) 119 methods[e] = (c, m) 120 for e, verbs in endpoints.items(): 121 self._add_method(*methods[e]) 122 logger.debug("Adding endpoint: '%s' allowing with verbs: '%s'." % 123 (e, verbs)) 124 route = e 125 if route.startswith('/__root__'): 126 route = e.replace('/__root__', '', 1) 127 self.flask_app.add_url_rule(route, 128 route, 129 getattr(self, '_m_%s_%s' % methods[e]), 130 methods=verbs) 131 132 def serve(self, **kwargs): 133 self._setup_routes() 134 SSL_CERT_FILE = os.getenv('SSL_CERT_FILE') 135 SSL_KEY_FILE = os.getenv('SSL_KEY_FILE') 136 if SSL_CERT_FILE and SSL_KEY_FILE: 137 kwargs['ssl_context'] = ( 138 os.getenv('SSL_CERT_FILE'), 139 os.getenv('SSL_KEY_FILE')) 140 self.flask_app.run( 141 host=self.flask_app.config.get('HOST', '0.0.0.0'), 142 port=self.flask_app.config.get('PORT', 5001), 143 debug=self.flask_app.config.get('DEBUG', True), 144 threaded=self.flask_app.config.get('THREADED', False), 145 **kwargs) 146 147 def get_wsgi_app(self): 148 self._setup_routes() 149 return self.flask_app 150 151 def _set_cookies(self, response): 152 # Pending cookies live on flask.g (request-local): a framework 153 # attribute is shared by every thread of the worker, so a 154 # concurrent request would flush another user's Set-Cookie. 155 for args, kwargs in getattr(g, '_encadre_cookies', ()): 156 response.set_cookie(*args, **kwargs) 157 return response 158 159 def request_json(self): 160 return request.json 161 162 def request_environ(self): 163 return request.environ 164 165 def request_headers(self): 166 return request.headers 167 168 def request_args(self): 169 return request.args 170 171 def request_form(self): 172 return request.form 173 174 def request_files(self): 175 return request.files 176 177 def get_cookie(self, key): 178 return request.cookies.get(key) 179 180 def set_cookie(self, *args, **kwargs): 181 if not hasattr(g, '_encadre_cookies'): 182 g._encadre_cookies = [] 183 g._encadre_cookies.append((args, kwargs)) 184 185 def send_bytes(self, bytes_like, **kwargs): 186 return send_file(bytes_like, **kwargs)