apiflask / apiflask

A lightweight Python web API framework.

Home Page:https://apiflask.com

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Different behaviour between Flask and APIFlask re: converter & its variable passed in as args, not keyword args

luong-komorebi opened this issue · comments

In Flask, there are converter and variable names, which is part of Variable rules feature.
APIFlask does not have explicit documentation about the adoption or specific change on top of this feature, yet it is discovered that somewhere down the line these variables are passed into decorators differently compared to the original behaviour of Flask.
This affect the migration from Flask to APIFlask because it implicitly change a behaviour which is heavily used in decorators or even in the route handler itself

Here's how to reproduce it:

Take an example of examples/basic/app.py

# add a simple decorator

from functools import wraps


def example_decorator(func):
    @wraps(func)
    def decorated_func(*args, **kwargs):
        print(f'This is an example decorator. Receiving: args {args} and kwargs {kwargs}')
        return func(*args, **kwargs)
    return decorated_func


# insert the newly created decorator to one of the handlers with converter and variable names
@app.get('/pets/<int:pet_id>')
@app.output(PetOut)
@example_decorator
def get_pet(pet_id):
    if pet_id > len(pets) - 1 or pets[pet_id].get('deleted'):
        abort(404)
    return pets[pet_id]

"""Result when visit localhost:5000/pets/1

 * Running on http://127.0.0.1:5000
Press CTRL+C to quit
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 116-044-380
127.0.0.1 - - [29/Mar/2023 17:54:53] "GET / HTTP/1.1" 200 -
This is an example decorator. Receiving: args (1,) and kwargs {}
127.0.0.1 - - [29/Mar/2023 17:54:57] "GET /pets/1 HTTP/1.1" 200 -

"""

as compared to the output by original Flask (2.2.3 and 2.1 tested)

from flask import Flask, abort
from functools import wraps


pets = [
    {'id': 0, 'name': 'Kitty', 'category': 'cat'},
    {'id': 1, 'name': 'Coco', 'category': 'dog'},
    {'id': 2, 'name': 'Flash', 'category': 'cat'}
]

def example_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print(f'This is an example decorator. Receiving: args {args} and kwargs {kwargs}')
        return func(*args, **kwargs)
    return wrapper


custom_app = Flask(__name__)
@custom_app.route('/pets/<int:pet_id>', methods=['GET'])
@example_decorator
def get_pet(pet_id):
    if pet_id > len(pets) - 1 or pets[pet_id].get('deleted'):
        abort(404)
    return pets[pet_id]


"""
This is an example decorator. Receiving: args () and kwargs {'pet_id': 1}
127.0.0.1 - - [29/Mar/2023 18:01:21] "GET /pets/1 HTTP/1.1" 200 -
"""

we can clearly see the difference

This is an example decorator. Receiving: args (1,) and kwargs {}

versus

This is an example decorator. Receiving: args () and kwargs {'pet_id': 1}

Environment:

  • Python version: 3.10.1
  • Flask version: 2.2.3
  • APIFlask version: 1.3.1

It's an intentional change to ensure the arguments of view function is in a natural order. However, it did cause some side effects.

In APIFlask 2.0, all the arguments will be passed as keyword arguments. The input data will be passed with the location value as the keyword. For example:

@app.post('/pets/<pet_id>')
@app.input(PetQueryIn, location='json')
@app.input(PetBodyIn, location='query')
@app.output(PetOut, status_code=201)
def create_pet(pet_id, json, query):
    id = json['id']
    query_number = query['number']

I already working on this. Since this is a breaking change, I will release this with APIFlask 2.0. What do you think about this?

I will close this and track the progress with #427. Thanks!

Thank you so much @greyli