I was writing a little API and I wanted to filter the incoming parameters for things such as offset and limit and return an error if the values were above a certain value.

Here is the basic snippet of the logic.

def get(params):

if params['offset'] > 10000:

print('error')# Return error to user and abort in real life

if params['limit'] > 1000:

print('error')

... do stuff

This is a primitive example simply because I believe that most of the code examples online has way too much details that don’t pertain to the actual problem at hand… probably so people can show off how thorough they are.

Anyway, other problems with the code aside; there are a couple of problems with implementing filtering at this level. First of all; these values are hard coded in the function itself, so any changes would require me to change the inner code of the function.

Secondly, I will most likely have several API endpoints and each one will need similar kind of filtering and I don’t think I should have to replicate this logic everywhere; especially with hard coded values.

A decorator may seem like a pretty good solution, for example:

def filter_params(f):

def check_limit(params):

if params['offset'] > 10000:

print('error')

if params['limit'] > 1000:

print('error')

return f(params)

return check_limit @filter_params

def get(params):

... do stuff

This takes it out of the function and makes it sort of re-usable, but still doesn’t solve the issue of hard coded values. There are a couple of approaches around this. One may be to create a dictionary map of parameters along with their maximum values and use that in conditional logic, however even that wouldn’t be flexible enough for you to specify different values for various requests (multiple get functions throughout your code).

What I really wanted to do is decorate the get function with various filters. This would be the most modular approach. It would look something like this:

@filter_params('offset', 10000)

@filter_params('limit', 1000)

def get(params):

... do stuff

This way I could pick and choose which parameters I wanted to filter on in each handler without having hard-coded values in filter_params. So how do I make this happen?

Initially I assumed that something like this could work:

def filter_params(f, param, limit):

def inner(params):

if params[param] > limit:

print('error')

return inner @filter_params('limit', 1000)

def get(params):

.....

However, it didn’t and after playing around with the code I realized exactly why and finally understood why decorators were such as a kick ass feature of python.

The root of this issue that this didn’t work is that the decorator get executed when the function it is wrapping is declared; not when it is executed. So the outer function filter_params gets first executed when I am defining get, and not when I first run it. You can see it for your self here:

def filter_params(f):

print('Filter_Params is applied')

def inner():

return f()

return inner @filter_params

def get():

pass def get():pass #Output:

Filter_Params is applied

See what happened there? The first time filter_params ran was at declaration of get, not when you first execute get (Which I didn’t in the above example).

So that’s nice, but how do we get a parameter in there? Well, much like they did in Inception; we have to go one level deeper in our decorator and add an extra execution in there that we will do ourselves. See the working example:

def filter_params(param, limit):

def wrapper(f):

def inner(params):

if params[param] > limit:

print('error')

return f(params)

return inner

return wrapper



('limit', 1000)

def get(params):

print(params) @filter_params ('offset', 10000) @filter_params ('limit', 1000)def get(params):print(params) get({'offset':10001,'limit':1001}) #Outputs:

error

error

{'offset': 10001, 'limit': 1001}

So what is going on here? Lets see if we can add some print statements (best debugging tool) to get a better idea. Here is the updated wrapper:

def filter_params(param, limit):

print('FILTER: {}'.format(param))

def wrapper(f):

print('WRAPPER: {}'.format(param))

def inner(params):

print('INNER: {}'.format(param))

if params[param] > limit:

print('error')

return f(params)

return inner

return wrapper

When I run that in my interpreter, nothing happens. But watch what happens when I create get with these wrappers.



('limit', 1000)

def get(params):

print(params) @filter_params ('offset', 10000) @filter_params ('limit', 1000)def get(params):print(params) Output:

FILTER: offset

FILTER: limit

WRAPPER: limit

WRAPPER: offset

So as soon as python sees this declaration, it runs both of the decorators, (@filter_params(‘offset’, 10000) , @filter_params(‘limit’, 1000)). These get ran and technically replaced with their inside wrapper function which python sees as a regular decorator which it does it’s thing with. Hence we have the two WRAPPER print outputs.

Now what happens when I run get? Well, as you probably suspect, I get something like this:

>>> get({'offset':10001,'limit':1001})

INNER: offset

error

INNER: limit

error

{'offset': 10001, 'limit': 1001}

The inner functions are executed; so first the offset filter was ran, then the limit. Both of them printed errors since their values were higher than the values I chose when I decorated the get function.

I hope this makes sense so far, if it doesn’t; we can play with it a bit. If you are familiar with the decorators, you know that using @ to decorate a function is just syntactic sugar for doing this:

get = filter_params(get)

We can try something like this to see what output we get:

get = filter_params('offset', 10000)

FILTER: offset

You can see that just the outer function was executed and returned the wrapper. This is not useful to us now, but we can pass it the get function to get it to do what we want.

>>> def get(params):

print(params)

>>> get = filter_params('offset', 10000)(get)

FILTER: offset

WRAPPER: offset

>>> get({'offset':10001})

INNER: offset

error

{'offset': 10001}

So that’s pretty cool. Now how does that second decorator fit into this? And what is the running order? The second filter, needs to be supplied to the output of the first wrapper like so:

>>> def get(params):

print(params)

>>> get = filter_params('offset', 10000)(filter_params('limit', 1000)(get))

FILTER: offset

FILTER: limit

WRAPPER: limit

WRAPPER: offset

>>> get({'offset':10001,'limit':10001})

INNER: offset

error

INNER: limit

error

{'offset': 10001, 'limit': 10001}

So here is what happens here on this line:

get = filter_params('offset', 10000)(filter_params('limit', 1000)(get))

filter_params for offset runs and returns wrapper filter_params for limit runs and returns the wrapper wrapper from #2 gets executed with get function as argument which returns inner wrapper from #1 gets executed with inner (from #3) as an argument. It returns it’s own inner.

When you actually run get, with these decorators, it will start unwrapping it. First, the inner function from step #4 will run, the function it wraps is the inner function from #3, which actually wraps the get.

Hope this kind of makes sense; if it doesn’t hit me up in the comments.