If you're building a web-app in Python that requires people to sign in, you are probably pretty familiar with the concept of a @login_required decorator. For those that might not be, it's pretty straight forward: the decorator wraps a handler method with a check for whether the current request is coming from someone who has been through your sign in process.

When I first saw this however many years ago, I was pretty new to Python decorators and thought it was pretty awesome. I still think the whole concept is pretty awesome. This seems to fit the mold of a "decorator" pretty perfectly... almost as if decorators were built with this in mind...

But what if you forget your @login_required decorator? It’s so easy to do. When you're testing that "Edit your account" page, you're authenticated, you assume everything should go to plan. You could (and should) add a functional test to check that an unauthenticated request is redirected, but this is just a quick little side project...

So why don’t we just make @login_required the default? I think that it should be, so here’s some code to save somebody else some time.

It works by wrapping all local methods with your login_required method during object creation (aka, __new__). If you want a method to be public, you decorate it with the @login_not_required decorator -- which sets a flag on the method saying... that login is not required.

Here's the BaseHandler (and Meta class) which does the wrapping:

import types

import webapp2

from auth import login_required

class BaseHandlerMeta(type):  
  """Meta class for all request handlers.

  This automatically wraps all handler methods with the login_required
  decorator. If something should be exposed publicly, it should be wrapped
  with the login_not_required decorator.

  def __new__(cls, name, bases, local):
    if name != 'BaseHandler':
      for func_name, func in local.iteritems():
        if isinstance(func, types.FunctionType):
          local[func_name] = login_required(func)

    return type.__new__(cls, name, bases, local)

class BaseHandler(webapp2.RequestHandler):  
  """Base class for all RequestHandlers."""

  __metaclass__ = BaseHandlerMeta

  def user_is_logged_in(self):
    # Do some magic here to check if someone is logged in
    return False

Here are the login_required and login_not_required decorators:

def login_not_required(handler_method):  
  """Allows a user to *not* be logged in.

  The login_required attribute is inspected by BaseHandlerMeta and is used
  as the flag for whether to wrap a method with login_required or not.
  handler_method.login_required = False
  return handler_method

def login_required(handler_method):  
  """Requires that a user be logged in."""

  required = getattr(handler_method, 'login_required', True)
  already_wrapped = getattr(handler_method, 'wrapped', False)

  # If the method doesn't require a login, or has already been wrapped,
  # just return the original.
  if not required or already_wrapped:
    return handler_method

  def check_login(self, *args, **kwargs):
    if not self.user_is_logged_in():
      uri = self.uri_for('login', redirect=self.request.path)
      self.redirect(uri, abort=True)
      return handler_method(self, *args, **kwargs)

  # Let others know that this method is already wrapped to avoid wrapping
  # it more than once...
  check_login.wrapped = True

  return check_login

This would make your request handlers look something like this:

import base  
from auth import login_not_required

class MyHandler(base.BaseHandler):  
  def my_public_method(self):
    self.response.write('Hello world!')

  def my_public_method_with_a_problem(self):
    # Calling this should force a redirect to the login page!

  def my_other_handler_that_is_protected(self):
    self.response.write('Hello privately!')

  def protected_by_default(self):
    self.response.write('Once you log in, you can view this!')

Notice that if you are in a public method (@login_not_required) and you call a method that does not have that decorator, you’ll get redirected to a login page. If you want something to be public, it and all of the functions it calls (inside the handler) should be explicitly defined as public.