Add a prefix to all Flask routes

后端 未结 10 832
你的背包
你的背包 2020-11-22 09:40

I have a prefix that I want to add to every route. Right now I add a constant to the route at every definition. Is there a way to do this automatically?

PR         


        
相关标签:
10条回答
  • 2020-11-22 10:19

    This is more of a python answer than a Flask/werkzeug answer; but it's simple and works.

    If, like me, you want your application settings (loaded from an .ini file) to also contain the prefix of your Flask application (thus, not to have the value set during deployment, but during runtime), you can opt for the following:

    def prefix_route(route_function, prefix='', mask='{0}{1}'):
      '''
        Defines a new route function with a prefix.
        The mask argument is a `format string` formatted with, in that order:
          prefix, route
      '''
      def newroute(route, *args, **kwargs):
        '''New function to prefix the route'''
        return route_function(mask.format(prefix, route), *args, **kwargs)
      return newroute
    

    Arguably, this is somewhat hackish and relies on the fact that the Flask route function requires a route as a first positional argument.

    You can use it like this:

    app = Flask(__name__)
    app.route = prefix_route(app.route, '/your_prefix')
    

    NB: It is worth nothing that it is possible to use a variable in the prefix (for example by setting it to /<prefix>), and then process this prefix in the functions you decorate with your @app.route(...). If you do so, you obviously have to declare the prefix parameter in your decorated function(s). In addition, you might want to check the submitted prefix against some rules, and return a 404 if the check fails. In order to avoid a 404 custom re-implementation, please from werkzeug.exceptions import NotFound and then raise NotFound() if the check fails.

    0 讨论(0)
  • 2020-11-22 10:21

    You can put your routes in a blueprint:

    bp = Blueprint('burritos', __name__,
                            template_folder='templates')
    
    @bp.route("/")
    def index_page():
      return "This is a website about burritos"
    
    @bp.route("/about")
    def about_page():
      return "This is a website about burritos"
    

    Then you register the blueprint with the application using a prefix:

    app = Flask(__name__)
    app.register_blueprint(bp, url_prefix='/abc/123')
    
    0 讨论(0)
  • 2020-11-22 10:21
    from flask import Flask
    
    app = Flask(__name__)
    
    app.register_blueprint(bp, url_prefix='/abc/123')
    
    if __name__ == "__main__":
        app.run(debug='True', port=4444)
    
    
    bp = Blueprint('burritos', __name__,
                            template_folder='templates')
    
    @bp.route('/')
    def test():
        return "success"
    
    0 讨论(0)
  • 2020-11-22 10:29

    The answer depends on how you are serving this application.

    Sub-mounted inside of another WSGI container

    Assuming that you are going to run this application inside of a WSGI container (mod_wsgi, uwsgi, gunicorn, etc); you need to actually mount, at that prefix the application as a sub-part of that WSGI container (anything that speaks WSGI will do) and to set your APPLICATION_ROOT config value to your prefix:

    app.config["APPLICATION_ROOT"] = "/abc/123"
    
    @app.route("/")
    def index():
        return "The URL for this page is {}".format(url_for("index"))
    
    # Will return "The URL for this page is /abc/123/"
    

    Setting the APPLICATION_ROOT config value simply limit Flask's session cookie to that URL prefix. Everything else will be automatically handled for you by Flask and Werkzeug's excellent WSGI handling capabilities.

    An example of properly sub-mounting your app

    If you are not sure what the first paragraph means, take a look at this example application with Flask mounted inside of it:

    from flask import Flask, url_for
    from werkzeug.serving import run_simple
    from werkzeug.wsgi import DispatcherMiddleware
    
    app = Flask(__name__)
    app.config['APPLICATION_ROOT'] = '/abc/123'
    
    @app.route('/')
    def index():
        return 'The URL for this page is {}'.format(url_for('index'))
    
    def simple(env, resp):
        resp(b'200 OK', [(b'Content-Type', b'text/plain')])
        return [b'Hello WSGI World']
    
    app.wsgi_app = DispatcherMiddleware(simple, {'/abc/123': app.wsgi_app})
    
    if __name__ == '__main__':
        app.run('localhost', 5000)
    

    Proxying requests to the app

    If, on the other hand, you will be running your Flask application at the root of its WSGI container and proxying requests to it (for example, if it's being FastCGI'd to, or if nginx is proxy_pass-ing requests for a sub-endpoint to your stand-alone uwsgi / gevent server then you can either:

    • Use a Blueprint, as Miguel points out in his answer.
    • or use the DispatcherMiddleware from werkzeug (or the PrefixMiddleware from su27's answer) to sub-mount your application in the stand-alone WSGI server you're using. (See An example of properly sub-mounting your app above for the code to use).
    0 讨论(0)
  • 2020-11-22 10:32

    Another completely different way is with mountpoints in uwsgi.

    From the doc about Hosting multiple apps in the same process (permalink).

    In your uwsgi.ini you add

    [uwsgi]
    mount = /foo=main.py
    manage-script-name = true
    
    # also stuff which is not relevant for this, but included for completeness sake:    
    module = main
    callable = app
    socket = /tmp/uwsgi.sock
    

    If you don't call your file main.py, you need to change both the mount and the module

    Your main.py could look like this:

    from flask import Flask, url_for
    app = Flask(__name__)
    @app.route('/bar')
    def bar():
      return "The URL for this page is {}".format(url_for('bar'))
    # end def
    

    And a nginx config (again for completeness):

    server {
      listen 80;
      server_name example.com
    
      location /foo {
        include uwsgi_params;
        uwsgi_pass unix:///temp/uwsgi.sock;
      }
    }
    

    Now calling example.com/foo/bar will display /foo/bar as returned by flask's url_for('bar'), as it adapts automatically. That way your links will work without prefix problems.

    0 讨论(0)
  • 2020-11-22 10:32

    For people still struggling with this, the first example does work, but the full example is here if you have a Flask app that is not under your control:

    from os import getenv
    from werkzeug.middleware.dispatcher import DispatcherMiddleware
    from werkzeug.serving import run_simple
    from custom_app import app
    
    application = DispatcherMiddleware(
        app, {getenv("REBROW_BASEURL", "/rebrow"): app}
    )
    
    if __name__ == "__main__":
        run_simple(
            "0.0.0.0",
            int(getenv("REBROW_PORT", "5001")),
            application,
            use_debugger=False,
            threaded=True,
        )
    
    0 讨论(0)
提交回复
热议问题