What is a self-written decorator (like @login_required) actually doing?

Welcome to Python! That's a lot of great questions. Let's take them one at a time. Also, just a point of fair warning. This topic makes your head spin for awhile before it all clicks together.

For reference, here is your example decorator and function being decorated:

# Decorator Function
def login_required(something):
    @wraps(something)
    def wrap(*args, **kwargs):
        if "some_admin_name" in session:
            return something(*args, **kwargs)
        else:
            flash("\"You shall not pass!\" - Gandalf")
            return redirect(url_for("login"))
    return wrap

# Function Being Decorated
@app.route("/some/restricted/stuff")
@login_required
def main():
    return render_template("overview.html",
                       stuff = getstuff() )

What is the argument "something"? Is that the request?!

To answer this question, we first have to answer what a decorator is. The answer can vary a bit based on what type of object that you are decorating. In this case, as you are decorating a function, you can think of a decorator as a method/function which allows a programmer to modified the behavior of the another function.

With that out of the way, we can answer your question. "something" is the function/method you are going to decorate. Yes, it is a function that takes another function as an argument.

Let's change the language of your decorator function to make this more clear:

def login_required(function_to_wrap):
    @wraps(function_to_wrap)
    def wrap(*args, **kwargs):
        if "some_admin_name" in session:
            return function_to_wrap(*args, **kwargs)
        else:
            flash("\"You shall not pass!\" - Gandalf")
            return redirect(url_for("login"))
    return wrap

What are the args and kwargs?

The short answer is that this is Python's way of allowing the parameters programmers to write functions/methods that take a variable number of keyword & non-keyword arguments.

Normally, when you write a function, you specify the parameters explicitly. For example:

def add_these_numbers(number_1, number_2):
    return number_1 + number_2

That is not, however, the only way of doing things. You can also use the *args or **kargs to accomplish the same thing:

def add_these_numbers(*args):
    return args[0] + args[1]

def add_these_numbers_too(**kwargs):
    return kwargs['first_number'] + kwargs['second_number']

As it pertains to your question *args/**kwargs are commonly used in decorators because decorators are often applied to a variety of methods which will take a wide variety of parameters.

Using args/**kwargs allows your decorator method to pass what method were originally required for the method through the decorator function. If that makes your head spin, let me know and I'll try to clarify.

Let's change main() so that this is more clear:

# Function Being Decorated
@app.route("/some/restricted/stuff")
@login_required
def main(html_template):
    return render_template(html_template, stuff = getstuff())

Why do i have to wrap a method INSIDE a method to use this as a decorator?

This is the most tricky part of understanding decorators in my opinion. The key is understanding that at its core, a decorator takes over the name of the original function.

The easiest way to understand this is to apply the decorator without using the handy @ syntax. The following are equivalent:

@login_required
def main():
    ....

main = login_required(main)

Hold on to your horses, this is where is gets AWESOME! What both of these code fragments tell Python is, "the word 'main' should will no longer refer to the main() function, but to the results login_required() function when it was passed the original main() function as a parameter.

WAT!?

Yes. A call to main() now refers to the results of the call to login_required(main()). This is also why login_required returns nested function. The new main() must still be a function, just like the old one was.

The difference is that now the new main function is really an instance of wrap(), customized by the parameter passed to login_required().

So... effectively main() is now equivalent to the following:

def main(*args, **kwargs):
    if "some_admin_name" in session:
        return predecorator_main_function(*args, **kwargs)
    else:
        flash("\"You shall not pass!\" - Gandalf")
        return redirect(url_for("login"))

Is this only usable with Flask? Are there other situations something like that could come in handy?

Definitely not! Decorators are one of the many super-awesome features built right into Python. Decorators are useful in any situation where you want to make modifications (relatively small in my opinion) to existing functions/methods when you don't want to create additional functions to avoid code-duplication


Intro to decorators: you can read it here.

What are the args and kwargs (keyword arguments?)?

args when you're not sure how many arguments might be passed to your function.

random(*args)
random('d', 'c', 'b')
random('a', 'e', 'f','z')

kwargs: named arguments. Basically as above, it's like you're passing a dictionary.

random(**kwargs)
random(a="a", b='test', c='ab')

Why do i have to wrap a method INSIDE a method to use this as a decorator?

Check the link decorator link.

Is this only usable with flask? Are there other situations something like that could come in handy?

No, decorators are available in Python in general, it's not limited to flask only. You can use it for logging, synchronization and a lot of other use. In Django, you can allow a post or get request using a decorator, or for login_required and so on...