Flask allows users to create custom endpoint decorators, and also to stream responses (basically by returning a generator in a flask.Response).
I would like to make the two concepts work together. Here is a decorator that initializes and closes a context before and after a request is processed. (In real life, it does database connection related stuff):
def mydecorator(f):
#wraps(f)
def decorated_function(*args, **kwargs):
print("Start context")
result = f(*args, **kwargs)
print("End context")
return result
return decorated_function
And now here are two endpoints, the first one is a regular one, and the second one is a streamed one.
#app.route("/regular")
#mydecorator
def regular_endpoint():
print("start normal")
random_function_that_need_the_decorator_context()
print("end normal")
return render_template("whatever.html")
# prints: start context, start normal, end normal, end context
#app.route("/streamed")
#mydecorator
def streamed_endpoint():
def mygenerator():
print("start generator")
random_function_that_need_the_decorator_context()
for _ in range(1000):
yield "something"
print("end generator")
return Response(mygenerator())
# prints: start context, end context, start generator, end generator
The regular endpoint works as expected, but the streamed endpoint fails because the inner generator function needs the decorator context, but the decorator context is closed by the time the generator function is executed.
Is there a way to keep the decorator context opened by the time the generator is executed?
There is a stream_with_context function in flask, but it seems to only provide the flask request context. Playing with after_request does not give better results as the function is called before the generator is executed.
Flask will automatically delete the request context once a response is started on the server. This is mainly to prevent memory leaks. Therefore you have to explicitly tell it to keep it. You can do this with "stream_with_context":
from flask import stream_with_context
#app.route("/streamed")
#mydecorator
def streamed_endpoint():
def mygenerator():
print("start generator")
random_function_that_need_the_decorator_context()
for _ in range(1000):
yield "something"
print("end generator")
return Response(stream_with_context(mygenerator()))
Tested and working in Flask==1.1.1
More info can be found here
Related
I'm currently working on a small Telegram bot library that uses PTB under the hood.
One of the syntactic sugar features is this decorator function I use, to ensure code locality.
Unfortunately, the inner wrapper is never called (in my current library version, I have an additional wrapper called #aexec I place below #async_command, but that's not ideal)
def async_command(name: str):
def decorator(func):
updater = PBot.updater
updater.dispatcher.add_handler(CommandHandler(name, func, run_async=True))
def wrapper(update: Update, context: CallbackContext):
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
loop.run_until_complete(func(update, context))
loop.close()
return wrapper
return decorator
#async_command(name="start")
async def start_command(update: Update, context: CallbackContext) -> None:
update.message.reply_text(text="Hello World")
"def wrapper" inside "async_command" is never called, so the function is never executed.
Any idea how to achieve this without needing an additional decorator to start a new asyncio event loop?
Note: PBot is just a simple class that contains one static "updater" that can be re-used anywhere in the code (PTB uses "updater" instances, which is not ideal for my use cases)
EDIT: I notice that the issue of the inner wrapper not being called only happens on async function. Is this a case of differing calling conventions?
I actually figured it out myself.
I took the existing #aexec and chained the two functions internally, creating a new decorator.
def aexec(func):
def wrapper(update: Update, context: CallbackContext):
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
loop.run_until_complete(func(update, context))
loop.close()
return wrapper
def _async_command(name: str):
def wrapper(func):
updater = PBot.updater
updater.dispatcher.add_handler(CommandHandler(name, func, run_async=True))
return wrapper
def async_command(name: str):
run_first = _async_command(name)
def wrapper(func):
return run_first(aexec(func))
return wrapper
Now I can use #async_command(name="name_of_command") and it will first call _async_command, then aexec
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
I have written a very simple tornado handler intended to test the upload speed of some devices that are deployed remotely. The main test is going to be run in said remote devices where (thanks to cURL), I can get a detailed report on the different times the upload took.
The only thing the Tornado handler has to really do is accept a body with a number of bytes (that's pretty much it)
class TestUploadHandler(tornado.web.RequestHandler):
def post(self):
logging.debug("Testing upload")
self.write("")
So, the code above works, but it is kind of... almost shameful :-D To make it a bit more... showable, I'd like to show a few more useful logs, like the time that the request took to upload or something like that. I don't know... Something a bit juicier.
Is there any way of measuring the upload speed within the Tornado handler itself? I've googled how to benchmark Tornado handlers, but all I seem to be able to find are performance comparisons between different web servers.
Thank you in advance.
Well, it's pretty straightforward to time how long the upload itself took:
import time
class TestUploadHandler(tornado.web.RequestHandler):
def post(self):
logging.debug("Testing upload")
start = time.time()
self.write({})
end = time.time()
print "Time to write was {} seconds.".format(end-start)
You could also move the timing code to a decorator, if you want to use it in more than one handler:
from functools import wrap
import time
def timer(func):
#wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
ret = func(*args, **kwargs)
end = time.time()
print 'Function took {} seconds'.format(end-start)
return ret
return wrapper
class TestUploadHandler(tornado.web.RequestHandler):
#timer
def post(self):
logging.debug("Testing upload")
self.write({})
Edit:
Given that you're trying to measure how long an upload to the server is taking from the server's perspective, the above approach isn't going to work. It looks like the closest you can get with tornado is to use the #tornado.web.stream_request_body decorator, so that you receive the request body as a stream:
#tornado.web.stream_request_body
class ValueHandler(tornado.web.RequestHandler):
def initialize(self):
self.start = None
def post(self):
end = time.time()
print self.request
if self.start:
print("Upload time %s" % end-self.start)
self.write({})
def data_received(self, data):
if not self.start:
self.start = time.time()
When the first chunk of the request body is received, we save the time (as self.start). The post method will be called as soon as the complete body is received, so we get end then.
I had issues getting this to work properly with large file uploads, though. It seems to work ok for smallish files (under 100MB), though.
I'm looking for a way to have all requests going inside a function foo() before going into the routes.
That way I'll be able to read the request.environ before doing the real work.
I'm trying to do this so that I don't repeat code, but cannot find a way to do such a thing in BottlyPy...
My setup is: nginx -> uwsgi -> bottlepy.
That's what plugins are used for.
Here's an example:
import bottle
from bottle import request, response
def foo(callback):
def wrapper(*args, **kwargs):
# before view function execution
print(request.environ) # do whatever you want
body = callback(*args, **kwargs) # this line basically means "call the view normally"
# after view function execution
response.headers['X-Foo'] = 'Bar' # you don't need this, just an example
return body # another 'mandatory' line: return what the view returned (you can change it too)
return wrapper
bottle.install(foo)
I've been using testbed, webtest, and nose to test my Python GAE app, and it is a great setup. I'm now implementing something similar to Nick's great example of using the deferred library, but I can't figure out a good way to test the parts of the code triggered by DeadlineExceededError.
Since this is in the context of a taskqueue, it would be painful to construct a test that took more than 10 minutes to run. Is there a way to temporarily set the taskqueue time limit to a few seconds for the purpose of testing? Or perhaps some other way to elegantly test the execution of code in the except DeadlineExceededError block?
Abstract the "GAE context" for your code. in production provide real "GAE implementation" for testing provide a mock own that will raise the DeadlineExceededError. The test should not depend on any timeout, should be fast.
Sample abstraction (just glue):
class AbstractGAETaskContext(object):
def task_spired(): pass # this will throw exception in mock impl
# here you define any method that you call into GAE, to be mocked
def defered(...): pass
If you don't like abstraction, you can do monkey patching for testing only, also you need to define the task_expired function to be your hook for testing.
task_expired should be called during your task implementation function.
*UPDATED*This the 3rd solution:
First I want to mention that the Nick's sample implementation is not so great, the Mapper class has to many responsabilities(deferring, query data, update in batch); and this make the test hard to made, a lot of mocks need to be defined. So I extract the deferring responsabilities in a separate class. You only want to test that deferring mechanism, what actually is happen(the update, query, etc) should be handled in other test.
Here is deffering class, also this no more depends on GAE:
class DeferredCall(object):
def __init__(self, deferred):
self.deferred = deferred
def run(self, long_execution_call, context, *args, **kwargs):
''' long_execution_call should return a tuple that tell us how was terminate operation, with timeout and the context where was abandoned '''
next_context, timeouted = long_execution_call(context, *args, **kwargs)
if timeouted:
self.deferred(self.run, next_context, *args, **kwargs)
Here is the test module:
class Test(unittest.TestCase):
def test_defer(self):
calls = []
def mock_deferrer(callback, *args, **kwargs):
calls.append((callback, args, kwargs))
def interrupted(self, context):
return "new_context", True
d = DeferredCall()
d.run(interrupted, "init_context")
self.assertEquals(1, len(calls), 'a deferred call should be')
def test_no_defer(self):
calls = []
def mock_deferrer(callback, *args, **kwargs):
calls.append((callback, args, kwargs))
def completed(self, context):
return None, False
d = DeferredCall()
d.run(completed, "init_context")
self.assertEquals(0, len(calls), 'no deferred call should be')
How will look the Nick's Mapper implementation:
class Mapper:
...
def _continue(self, start_key, batch_size):
... # here is same code, nothing was changed
except DeadlineExceededError:
# Write any unfinished updates to the datastore.
self._batch_write()
# Queue a new task to pick up where we left off.
##deferred.defer(self._continue, start_key, batch_size)
return start_key, True ## make compatible with DeferredCall
self.finish()
return None, False ## make it comaptible with DeferredCall
runner = _continue
Code where you register the long running task; this only depend on the GAE deferred lib.
import DeferredCall
import PersonMapper # this inherits the Mapper
from google.appengine.ext import deferred
mapper = PersonMapper()
DeferredCall(deferred).run(mapper.run)