Вопрос

Is it possible in flask to define url routers that handle a certain part of the url and pass on the url for further processing to the next url handler?

A use case would be a static part in a url that repeats a lot and always requires same processing. /user/1/something

/user/1/something-else

/user/2/...

Ideally, a handler would handle the /user/<id> part (load database records, etc) and store the result to the local context. Another handler would then process the remaining url. This would enable me also to replace the user part (for instance with /user/<name/ without touching all other routers.

Is this possible in flask, and if so, how?

Это было полезно?

Решение

I'm not sure you can do what you want. However, from the usage you've described above, I think you simply need a converter.

Whats A Converter?

It's that magic that lets you define routes like so:

@app.route('/double/<int:number>')
def double(number):
    return '%d' % (number * 2)

Above, the route only accepts /double/ints. Even better is that whatever is passed in the portion of the url is converted to an int. Hence, the converter moniker :D.

Converter's follow the <converter:variable_name> format. You can read about Flask's built in converters here.

The best part about converters is that you can write your own for custom data types! To do this, simply derive from werkzeug.routing.BaseConverter and fill in to_python and to_url. Below, I've created a simple converter for the ubiquitous User object.

class UserConverter(BaseConverter):
    """Converts Users for flask URLs."""

    def to_python(self, value):
        """Called to convert a `value` to its python equivalent.

        Here, we convert `value` to a User.
        """
        # Anytime an invalid value is provided, raise ValidationError.  Flask
        # will catch this, and continue searching the other routes.  First,
        # check that value is an integer, if not there is no way it could be
        # a user.
        if not value.isdigit():
            raise ValidationError()
        user_id = int(value)

        # Look up the user in the database.
        if user_id not in _database:
            raise ValidationError()

        # Otherwise, return the user.
        return _database[user_id]

    def to_url(self, value):
        """Called to convert a `value` to its `url` equivalent.

        Here we convert `value`, the User object to an integer - the user id.
        """
        return str(value.id)

When does the converter get called?

When flask matches your route and needs to fill in the spots handled by your converter. So, given the following route - /find/<user:user>, all of the URL's below are processed by our converter.

  1. /find/1
  2. /find/2
  3. /find/jaime
  4. /find/zasdf123

For the url's above, the UserConverter.to_python method is called with '1', '2', 'jaime' and 'zasdf123'. It is up to the method to determine if the values translate into valid users. If so, the valid user, is passed in directly to the route's user parameter.

However, flask still needs to know about your new converter. That is simple:

app.url_map.converters['user'] = UserConverter

The last cool feature of custom converters is that they make building URL's a natural, easy process. To create a url, just do:

url_for('find_user', user=user)

Finally, a simple program to tie all of this together.

from flask import Flask, url_for
from werkzeug.routing import BaseConverter, ValidationError


class User:

    def __init__(self, id, name):
        self.id = id
        self.name = name


class UserConverter(BaseConverter):
    """Converts Users for flask URLs."""

    def to_python(self, value):
        """Called to convert a `value` to its python equivalent.

        Here, we convert `value` to a User.
        """
        # Anytime an invalid value is provided, raise ValidationError.  Flask
        # will catch this, and continue searching the other routes.  First,
        # check that value is an integer, if not there is no way it could be
        # a user.
        if not value.isdigit():
            raise ValidationError()
        user_id = int(value)

        # Look up the user in the database.
        if user_id not in _database:
            raise ValidationError()

        # Otherwise, return the user.
        return _database[user_id]

    def to_url(self, value):
        """Called to convert a `value` to its `url` equivalent.

        Here we convert `value`, the User object to an integer - the user id.
        """
        return str(value.id)


# Create a `database` of users.
_database = {
    1: User(1, 'Bob'),
    2: User(2, 'Jim'),
    3: User(3, 'Ben')
}

app = Flask(__name__)
app.url_map.converters['user'] = UserConverter

@app.route('/find/<user:user>')
def find_user(user):
    return "User: %s" % user.name

@app.route('/find/<user:user>/extrapath')
def find_userextra(user):
    return 'User extra: %s' % user.name

@app.route('/users')
def list_users():
    # Return some broken HTML showing url's to our users.
    s = ''
    for user in _database.values():
        s += url_for('find_user', user=user) + '<br/>'
    return s

if __name__ == '__main__':
    app.run(debug=True)

If something is not clear, or gasp a bug exists, let me know :D. This applies to your question because you mention a series of urls that begin the same --

/user/1/something

/user/1/something-else

/user/2/

For these routes you'd apply a custom converter like so:

@app.route('/user/<user:user>/something')

@app.route('/user/<user:user>/something-else')

@app.route('/user/<user:user>/)
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top