Why python Flask decorator not logged? - python

I have a simple Flask app where i want to validate requests in json format with a decorator like this:
def validate_request(*expected_args):
"""
Validate requests decorator
"""
# print('=======================ENTER VALIDATE_REQUEST================')
def decorator(func):
#wraps(func)
def wrapper(*args, **kwargs):
json_obj = request.get_json()
for expected_arg in expected_args:
if expected_arg not in json_obj or json_obj.get(expected_arg) is None:
return api_response({'error': f"You must call with all request params: {', '.join(expected_args)}"})
return func(*args, **kwargs)
return wrapper
return decorator
And on the route side like this:
#user_api.route('/login', methods=['POST'])
#validate_request('email', 'password')
def login():
req_data = request.get_json()
......................
My question is why the PRINT statement from decorator is not shown when i call '/login' route?
The message is logged only when i start the server(flask run).
Thank you.

Python decorators replace the functions they are decorating. This replacement happens at the start of the program when the interpreter reads the code. This is when your print statement will be executed. After this time, the returned function is the only code that will run. If you want the print to run at every call, you need to put in the actual function that will be called:
def validate_request(*expected_args):
"""
Validate requests decorator
"""
# print('=======================Function Decoration Happening================')
def decorator(func):
#wraps(func)
def wrapper(*args, **kwargs):
print("==== ENTER VALIDATE_REQUEST ===")
json_obj = request.get_json()
for expected_arg in expected_args:
if expected_arg not in json_obj or json_obj.get(expected_arg) is None:
return api_response({'error': f"You must call with all request params: {', '.join(expected_args)}"})
return func(*args, **kwargs)
return wrapper
return decorator

Your print statement is in validate_request. Although validate_request returns a decorator, it is not a decorator itself. It is a decorator factory if you will, and is only executed once to create the decorator and decorate login when python loads your code, hence why your print statement only gets executed once when you boot up your server. Keep in mind that the decorator itself is also executed just once to replace login with whatever the decorator returns, in this case, wrapper. So, wrapper is the actual function that will be called on each request. If you put your print statement there, you'll see it on each request.
def validate_request(*expected_args):
def decorator(func):
#wraps(func)
def wrapper(*args, **kwargs):
print("=== Enter validate request ===")
return func(*args, **kwargs)
return wrapper
return decorator

Related

Python access function attributes with multiple decorators applied to it

Have created a fastapi endpoint which looks something like this
#v1.get("/users")
def fetchusers(token:str):
res = fetchdetailsfromdb()
return res
Now the API has 2 requirements, before running check if the token is valid and log an event by calling another api
To cover the authorization part I have created a decorator as such which runs before the function execution
def apply_auth(func):
#wraps(func)
def check_token(*args, **kwargs):
res = validate_user(kwargs["token"]) # Some function to validate is user is valid or not
if res["result"] != True:
raise Exception("Invalid token")
else:
check_token.authorization_data = res
return func(*args, **kwargs)
return check_token
The decorator sets an attribute named authorization_data which can be accessed during the function execution of the api endpoint
The authorization decorator works perfectly when its the only decorator applied
To cover the second use case I had created another decorator to go along with it
This decorator is supposed to run after function execution
def logdata(func):
#wraps(func)
def log_api(*args, **kwargs):
retval = func(*args, **kwargs)
log_data = {}
log_data['name'] = log_api.EVENT
log_data['user_id'] = log_api.INITIATOR_ID
requests.post(url = "DUMMY_URL", json = log_data, timeout=1)
return retval
return log_api
#v1.get("/users")
#apply_auth # Runs before function execution
#logdata # Runs after function execution
def fetchusers(token:str):
authorization_data = fetchusers.authorization_data # Access authorization related data such as username user email etc....
fetchusers.EVENT = "FETCH_USERS_LIST"
fetchusers.INITIATOR_ID = authorization_data["id"]
res = fetchdetailsfromdb()
return res
However using multiple decorators is giving me issues such as the function attributes EVENT and INITIATIOR_ID is not available for some reason in the logdata decorator
If #apply_auth is not added to the function #logdata can access those attributes
With both decorators added #apply_auth is able to access the function attributes but same is not visible for the #logdata decorator
Any ideas on where I am going wrong here ?

understanding the decorators and need to return inside wrapper function

This is the code from api.py
def route(self,path):
if path in self.routes :
raise AssertionError('Such route already exists')
def wrapper(handler):
self.routes[path] =handler
print(colorama.Fore.GREEN,"handler check",handler)
return handler
return wrapper
And this the code from the app.py
from api import API
app = API(templates_dir="templates")
from middleware import Middleware
#app.route("/home")
def home (request,response):
response.text ='Hello from home function'
#app.route("/about")
def about(request,response):
response.text ='Hello from about function'
#app.route("/hello/{name}")
def greeting(request, response, name):
response.text = f"Hello, {name}"
I am having difficulty in understanding how route decorator works? I see that it takes in a parameter "/about" and "/home" but why do we need to return the reference to the handler in the route decorator.
A clear explanation should really help.
So route is in a class that looks something like this:
class API:
routes = {}
def route(self, path):
if path in self.routes :
raise AssertionError('Such route already exists')
def wrapper(handler):
self.routes[path] =handler
print(colorama.Fore.GREEN,"handler check",handler)
return handler
return wrapper
def handle(self, path, request):
if path in self.routes:
response = Response()
self.routes[path](request, response)
send_to_client(response)
raise NotFound()
When you use
#app.route("/home")
def home(request, response):
response.text ='Hello from home function'
this is the same as doing something like:
def home_function(request, response):
response.text ='Hello from home function'
home = app.route("/home")(home_function)
First we're calling app.route with the argument "/home". First thing this does is check if we already have a route called /home. Then it returns the new function, the one that's called wrapper.
Now we call wrapper with the function we want to handle the /home path with. What this does is add to the dict a mapping from the path (/home) to the function that we want to call to handle this path.
The same thing happens for all decorated functions.
Now when we want to handle an incoming request, you can imaging something a bit like the handle function runs. We know that every key of the routes mapping is going to be a path, and every value is going to be a function that takes a request and a response. So then we just need to get the right function, and call the it appropriately.
Does that make sense?

How to pass flask arguments to decorator on flask function

Actually i am trying to write a decorator for logging the userid which is being passed from frontend in json format.
So the requirement is to read the data passed in requests inside the decorator. I am not understanding the flow of how to read the content passed inside decorator.
def logger_testing(func):
#wraps(func)
def log_test(*args, **kwargs):
print ("inside looger")
# logger.info(log("data has been inserted for ",userid))
print ("args",**kwargs)
func(*args, **kwargs)
print ("outside")
return func(*args, **kwargs)
return log_test
#app.route('/hello', methods=['POST'])
#logger_testing
def hello():
content = request.get_json()
print ("content",content)
print ("executed")
return "hello"

Wrapping a decorator, with arguments

I'm trying to replace the marshal_with decorator from flask-restful with a decorator that does something before calling marshal_with. My approach is to try to implement a new decorator that wraps marshal_with.
My code looks like:
from flask.ext.restful import marshal_with as restful_marshal_with
def marshal_with(fields, envelope=None):
def wrapper(f):
print("Do something with fields and envelope")
#wraps(f)
def inner(*args, **kwargs):
restful_marshal_with(f(*args, **kwargs))
return inner
return wrapper
Unfortunately this seems to break things... no error messages but my API returns a null response when it shouldn't be. Any insights on what I'm doing wrong?
I don't know the specifics of marshal_with, but it's entirely possible to use multiple decorators on a single function. For instance:
def decorator_one(func):
def inner(*args, **kwargs):
print("I'm decorator one")
func(*args, **kwargs)
return inner
def decorator_two(text):
def wrapper(func):
def inner(*args, **kwargs):
print(text)
func(*args, **kwargs)
return inner
return wrapper
#decorator_one
#decorator_two("I'm decorator two")
def some_function(a, b):
print(a, b, a+b)
some_function(4, 7)
The output this gives is:
I'm decorator one
I'm decorator two
4 7 11
You can modify this little script by adding print statements after each inner function call to see the exact flow control between each decorator as well.
I was doing a couple things wrong here, first, failing to return the output of restful_marshal_with as jonrsharpe pointed out, secondly, failing to understand a decorator written as a class instead of a function, and how to properly pass values to it. The correct code ended up being:
def marshal_with(fields, envelope=None):
def wrapper(f):
print("Do something with fields and envelope")
#wraps(f)
def inner(*args, **kwargs):
rmw = restful_marshal_with(fields, envelope)
return rmw(f)(*args, **kwargs)
return inner
return wrapper
As you can see, in addition to not returning rmw(), I needed to properly initialize the request_marshal_with class before calling it. Finally, it is important to remember that decorators return functions, therefore the arguments of the original function should be passed to the return value of rmw(f), hence the statement return rmw(f)(*args, **kwargs). This is perhaps more apparent if you take a look at the flask_restful.marshal_with code here.

Python decorator for function completion

Is it possible to develop a decorator to determine if a function successfully completed without crashing ? Something like the decorator below but it must detect if the function ran successfully.
def functionStartDecorator():
print("Function has started")
def decoratorWrapper(functionToBeDecorated):
def wrapper(*args, **kwargs):
#Todo log function has run
return functionToBeDecorated(*args, **kwargs)
return wrapper
return decoratorWrapper
As said in comments, simplest would be to wrap your function in a try/except. If your function returns nothing and simply operate by side-effect, it should be straightforward to have the decorated function return the status of that function run:
def tryfn(f):
def decorated(*args, **kwargs):
try:
f(*args, **kwargs)
return 'Ran successfully'
except Exception as e:
return 'Error: {}'.format(e)
return decorated
You can then tweak the exact return type of decorated: maybe return a boolean, maybe append the status of the function to the return of f, maybe log things... The core principle will probably still be around a try/except within the decorated function. For instance, if you want to return both your return value (or None if it failed) and whether this was a success:
def tryfn(f):
def decorated(*args, **kwargs):
try:
res = f(*args, **kwargs)
return True, res
except:
return False, None
return decorated
Note that in this case, you're losing the information about the exact error, so you might want to expand the return to include success, failure, error, etc... that becomes a trade-off between convenience and completeness, which will depend on your exact problem.
I did something like this. Do you see any issues with this code ?
def functionStartDecorator():
def decoratorWrapper(functionToBeDecorated):
def wrapper(*args, **kwargs):
try:
print("Function has started")
result=functionToBeDecorated(*args, **kwargs)
print("Function has complete")
return result
except:
print ("Function failed")
return wrapper
return decoratorWrapper
#functionStartDecorator()
def simplefunc():
return "somthing"

Categories