encadre

Encadre, petit framework web Python (Flask) : contrôleurs en greffons, validation, tests
git clone https://git.schurger.org/encadre.git
Log | Files | Refs

__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)