get the returned value from decorated function in endpoint (flask)

1k Views Asked by At

I did not understand well using decorators in flasks and would like to use it to clean up my code.

Particularly, I don't understand if the decorator applied to an endpoint must return a view, that is passed to the template, or can be an arbitrary object that is then used in the function that will return a response for the endpoint.

Let me show by example.

I want to enable POST APIs and some endpoints only to authenticated users. I am using a oauth authentication library (flask-dance) that handle the login with Google.

I must use the credentials obtained by a successful log in to do some things, before passing the object user to the rest of the function of the endpoint.

I am doing something like this:

@app.route("/")
def index():
    # get the response from the google authentications and do something
    # if auth is successful, will return a user, if not, will redirect to a view
    
    def work_on_user_auth(resp):

        user = myClass.something(resp) // pseudo code

        return user

    // here I could not find a best approach than this: if auth fails, than redirect
    if not google.authorized:
        return redirect(url_for("google.login"))
    try:
        resp = get_user_auth()
    except TokenExpiredError as e:
        return redirect(url_for("google.login"))
    user = work_on_user_auth(resp)

    // and here will use by object user, based on the google auth, and eventually ship it to the view
    return render_template('index.html', **user)

I am now copy pasting the logic in each endpoint, that check if user is logged via Oauth.

I would like to simplify, and use the work_on_user_auth(respFromGoogle) as a decorator for the endpoints: if successful, it must pass the object user, if not, a redirect will ask user to login.

I tried with something like this:

from functools import wraps
def logged_user_required(f):
    @wraps(f)
    def work_on_user_auth(self, *args, **kwargs):
        //  if not authorized or auth fails, redirect
        if not google.authorized:
            return redirect(url_for("google.login"))
        try:
            resp = get_user_auth()
        except TokenExpiredError as e:
            return redirect(url_for("google.login"))
        print('user logged in', resp)

        // if everything ok, create an object user and return it
        
        return user
   return work_on_user_auth

and would like to use it like that:

@app.route("/")
@logged_user_required
def index():
    // here I want to access the `user` object from my decorated function, if failes, it will redirect 
    // how to:
    user  = logged_user_required // ??
    return render_template('index.html', **user)

But getting different types of errors (last one: logged_user_required() missing 1 required positional argument: 'f')

Can you show the logic for getting a value returned by a decorated function, which, if fails, will tell the endpoint to redirect instead ?

Might there be a straight forward approach with flask-dance, good to know it, but I would like to have an answer to elucidate how to use decorators properly also in the future. Thanks!

1

There are 1 best solutions below

0
rzlvmp On

Let's say you have user that determined somewhere outside view by Google auth Moon phase. And you want to pass this authenticated user into view.

After that you want to do something inside view based on what user is passed.

So your code should be like:

from functools import wraps
from random import choice

users = ("Aragorn", "Gimli", "Legolas", "Balrog")


def determine_user_by_moon_phase():
  return choice(users)


def moon_phased_auth(f):
  @wraps(f)
  def wrapper(*args, **kwargs):
    """A wrapper function"""
    kwargs['moon_phased_user'] = determine_user_by_moon_phase()
    return f(*args, **kwargs)
  return wrapper


@moon_phased_auth
def index(moon_phased_user):
  print('the hero is:', moon_phased_user)
  if moon_phased_user == 'Balrog':
    print("You shall not pass!!!")
    return "Abyss"
  else:
    print("Welcome to Rivendell")
    return "Rivendell"

response = index()

print("response is:", response)

This code will print:

the hero is: Aragorn
Welcome to Rivendell
response is: Rivendell

In other way, if you don't need to know what is authenticated user inside index view, you may replace all check logic inside moon_phased_auth decorator:

from functools import wraps
from random import choice

users = ("Aragorn", "Gimli", "Legolas", "Balrog")


def determine_user_by_moon_phase():
  return choice(users)


def moon_phased_auth(f):
  @wraps(f)
  def wrapper(*args, **kwargs):
    moon_phased_user = determine_user_by_moon_phase()
    print('the hero is:', moon_phased_user)
    if moon_phased_user == 'Balrog':
      print("You shall not pass!!!")
      return "Abyss"
    else:
      print("Welcome to Rivendell")
      return f(*args, **kwargs)
  return wrapper


@moon_phased_auth
def index():
    return "Rivendell"

response = index()

print("response is:", response)
the hero is: Balrog
You shall not pass!!!
response is: Abyss

In this case index don't care about authentication. Abyss response will be returned to client before index call