I have an application with many threads. One of them is flask, which is used to implement (auxiliary) API. It's used with low load and never exposed to the Internet, so build-in flask web server is perfectly fine.
My current code looks like this:
class API:
# ... all other stuff here, skipped
def run():
app = flask.Flask('API')
#app.route('/cmd1')
def cmd1():
self.cmd1()
#app.route('/cmd2')
def cmd2()
self.cmd2()
app.run()
I feel I done it wrong, because all docs says 'create flask app at module level'. But I don't want to do this - it messes up with my tests, and API is a small part of the larger application, which has own structure and conventions (each 'application' is a separate class running in one or more threads).
How can I use Flask inside class?
Although this works it doesn't feel compliant with the Flask style guide. If you need to wrap a Flask application inside your project, create a separate class to your needs and add functions that should be executed
from flask import Flask, Response
class EndpointAction(object):
def __init__(self, action):
self.action = action
self.response = Response(status=200, headers={})
def __call__(self, *args):
self.action()
return self.response
class FlaskAppWrapper(object):
app = None
def __init__(self, name):
self.app = Flask(name)
def run(self):
self.app.run()
def add_endpoint(self, endpoint=None, endpoint_name=None, handler=None):
self.app.add_url_rule(endpoint, endpoint_name, EndpointAction(handler))
def action():
# Execute anything
a = FlaskAppWrapper('wrap')
a.add_endpoint(endpoint='/ad', endpoint_name='ad', handler=action)
a.run()
Some things to note here:
EndpointAction is supposed to be a wrapper that will execute your function and generate an empty 200 response. If you want you can edit the functionality
The endpoint handler can be anything that has a __call__ method defined
The endpoint name should be unique as it represents a view name
Adding endpoints after the application is not possible as the thread will block once the application starts. You can enable it by running the application on a separate thread but changing the URL map on the fly is not advised, neither thread safe
So I just came across the library Flask-Classful
which was really simple comparatively
To create a simple web app inside a class is this:
from flask import Flask
from flask_classful import FlaskView
app = Flask(__name__)
class TestView(FlaskView):
def index(self):
# http://localhost:5000/
return "<h1>This is my indexpage</h1>"
TestView.register(app,route_base = '/')
if __name__ == '__main__':
app.run(debug=True)
Handling multiple route and dynamic route is also simple
class TestView(FlaskView):
def index(self):
# http://localhost:5000/
return "<h1>This is my indexpage</h1>"
def secondpage(self):
# http://localhost:5000/secondpage
return "<h1>This is my second</h1>"
def thirdpage(self,name):
# dynamic route
# http://localhost:5000/thirdpage/sometext
return "<h1>This is my third page <br> welcome"+name+"</h1>"
TestView.register(app,route_base = '/')
Adding own route name with a different method that is also possible
from flask_classful import FlaskView,route
class TestView(FlaskView):
def index(self):
# http://localhost:5000/
return "<h1>This is my indexpage</h1>"
#route('/diffrentname')
def bsicname(self):
# customized route
# http://localhost:5000/diffrentname
return "<h1>This is my custom route</h1>"
TestView.register(app,route_base = '/')
This gives the potential to create separate class and handlers for a separate dependent and independent process and just import them as a package to run on the main file or wrapper file
from package import Classname
Classname.register(app,route_base = '/')
which is really simple and object-oriented
To complete Kostas Pelelis's answer, because I had some difficulty to find the why the Response wasn't directly using the Action returned value.
Here is another version of FLASK class without decorators :
class EndpointAction(object):
def __init__(self, action):
self.action = action
def __call__(self, *args):
# Perform the action
answer = self.action()
# Create the answer (bundle it in a correctly formatted HTTP answer)
self.response = flask.Response(answer, status=200, headers={})
# Send it
return self.response
class FlaskAppWrapper(object):
def add_all_endpoints(self):
# Add root endpoint
self.add_endpoint(endpoint="/", endpoint_name="/", handler=self.action)
# Add action endpoints
self.add_endpoint(endpoint="/add_X", endpoint_name="/add_X", handler=self.add_X)
# you can add more ...
def add_endpoint(self, endpoint=None, endpoint_name=None, handler=None):
self.app.add_url_rule(endpoint, endpoint_name, EndpointAction(handler))
# You can also add options here : "... , methods=['POST'], ... "
# ==================== ------ API Calls ------- ====================
def action(self):
# Dummy action
return "action" # String that will be returned and display on the webpage
# Test it with curl 127.0.0.1:5000
def add_X(self):
# Dummy action
return "add_X"
# Test it with curl 127.0.0.1:5000/add_X
Here is an example of mixing class and routing that seems reasonable to me. See also https://github.com/WolfgangFahl/pyFlaskBootstrap4/issues/2 (where i am a committer)
This design has been criticized so in the project there are some improvements to this code.
'''
Created on 27.07.2020
#author: wf
'''
from flask import Flask
from frontend.WikiCMS import Frontend
from flask import render_template
import os
class AppWrap:
def __init__(self, host='0.0.0.0',port=8251,debug=False):
self.debug=debug
self.port=port
self.host=host
scriptdir=os.path.dirname(os.path.abspath(__file__))
self.app = Flask(__name__,template_folder=scriptdir+'/../templates')
self.frontend=None
def wrap(self,route):
if self.frontend is None:
raise Exception("frontend is not initialized")
content,error=self.frontend.getContent(route);
return render_template('index.html',content=content,error=error)
def run(self):
self.app.run(debug=self.debug,port=self.port,host=self.host)
pass
def initFrontend(self,wikiId):
frontend=Frontend(wikiId)
frontend.open()
appWrap=AppWrap()
app=appWrap.app
#app.route('/', defaults={'path': ''})
#app.route('/<path:route>')
def wrap(route):
return appWrap.wrap(route)
if __name__ == '__main__':
appWrap.run()
A sidenote/addition to #Kostas Pelelis Answer (Sorry can't comment yet):
For all of you who wonder how to integrate the methods of the endpoint route: have a look at the function description for app.add_url_rule.
As stated there you can use the "methods" parameter to change the default "GET" method.
Kostas Pelelis code changed to a "POST" type method would look like this:
(Example with methods integrated + Endpoint-class that returns whatever your action-function returns [a html for example]
from flask import Flask, Response, render_template
class EndpointAction(object):
def __init__(self, action):
self.action = action
self.response = Response(status=200, headers={})
def __call__(self, *args):
response = self.action()
if response != None:
return response
else
return self.response
class FlaskAppWrapper(object):
app = None
def __init__(self, name):
self.app = Flask(name)
def run(self):
self.app.run()
def add_endpoint(self, endpoint=None, endpoint_name=None, handler=None, t_methods=None):
self.app.add_url_rule(endpoint, endpoint_name, EndpointAction(handler), methods=t_methods)
def action():
# Execute anything
print('i did something')
def returning_action():
# Returning for example an index hello world page
return render_template('index.html')
a = FlaskAppWrapper('wrap')
a.add_endpoint(endpoint='/ad', endpoint_name='ad', handler=action, req_methods=['POST'])
#just a little addition for handling of a returning actionhandler method
#-> i added another endpoint but for a returning method
a.add_endpoint(endpoint='/', endpoint_name='index_page', handler=returning_action, req_methods=['GET']
a.run()
While the templates/index.html could look like this (note render_templates expects a templates-folder in the same location as your py-file with specified htmls in it):
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Index Page</title>
</head>
<body>
<h1>Hello World!</h1>
</body>
</html>
This index page addition is called when the index route 'ip-address-of-the-webapp/' is visited (via usual browser visit -> GET request).
*Edit: to show how it would look like if your action-methods had params (for example from a route param) here an updated version of the endpoint class and the action class
class EndpointAction(object):
def __init__(self, action):
self.action = action
self.response = Response(status=200, headers={})
def __call__(self, *args, **kwargs):
response = self.action(**kwargs)
if response != None:
return response
else
return self.response
def param_action(param):
# Execute something (print param)
print(f'i did {param}')
[...]
a.add_endpoint(endpoint='/<param>', endpoint_name='parametric_action', handler=param_action, req_methods=['GET']
[...]
Related
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?
I am quite new to unit testing and relatively new to RESTful API development as well. I am wondering how to do unit test for functions inside Resource class in flask restful? I can do unit test for the endpoint's response but I don't know how to do testing for the individual functions inside the endpoint's controller class.
Below is my application code. It has 3 files including test:
api.py
controller_foo.py
test_controller_foo.py
# api.py
from flask import Flask
from flask_restful import Api
from .controller_foo import ControllerFoo
def create_app(config=None):
app = Flask(__name__)
app.config['ENV'] ='development'
return app
application = app = create_app()
api = Api(app)
api.add_resource(ControllerFoo, '/ctrl')
if __name__ == "__main__":
app.run(debug=True)
# controller_foo.py
from flask_restful import Resource
from flask import request
class ControllerFoo(Resource):
"""
basically flask-restful's Resource method is a wrapper for flask's MethodView
"""
def post(self):
request_data = self.handle_request()
response = self.process_request(request_data)
return response
def handle_request(self):
json = request.get_json()
return json
def process_request(self, data):
# do some stuffs here
return {'foo': 'bar'}
I am using unittest
# test_controller_foo.py
import unittest
from api import app
from .controller_foo import ControllerFoo
# initiating class to try testing but I don't know how to start
ctrl = ControllerFoo()
class ControllerFooTestCase(unittest.TestCase):
def setUp(self):
self.app = app
self.app.config['TESTING'] = True
self.client = app.test_client()
self.payload = {'its': 'empty'}
def tearDown(self):
pass
def test_get_response(self):
response = self.client.post('/ctrl', json=self.payload)
expected_resp = {
'foo': 'bar'
}
self.assertEqual(response.status_code, 200)
self.assertDictEqual(response.get_json(), expected_resp)
if __name__ == "__main__":
unittest.main()
I want to know how to properly do unit test for handle_request and process_request function
EDIT: Fixing out my buggy code. Thanks Laurent LAPORTE for the highlights.
There are several bugs in your code, so this is not easy to explain.
First of all, the recommended way to do testing with Flask (and Flask-Restful) is to use PyTest instead of unittest, because it is easier to setup and use.
Take a look at the documentation: Testing Flask Applications.
But, you can start with unittest…
note: you can have a confusion with your app module and the app instance in that module. So, to avoid it, I imported the module. Another good practice is to name your test module against the tested module: "app.py" => "test_app.py". You can also have a confusion with the controller module and the controller instance. The best practice is to use a more precise name, like "controller_foo" or something else…
Here is a working unit test:
# test_app.py
import unittest
import app
class ControllerTestCase(unittest.TestCase):
def setUp(self):
self.app = app.app
self.app.config['TESTING'] = True
self.client = self.app.test_client()
self.payload = {'its': 'empty'}
def test_get_response(self):
response = self.client.post('/ctrl', json=self.payload)
expected_resp = {'foo': 'bar'}
self.assertEqual(response.status_code, 200)
self.assertDictEqual(response.get_json(), expected_resp)
if __name__ == "__main__":
unittest.main()
As you can see, I also fixed the posted URL, in your application, the URL is "/ctrl", not "controller".
At this point, the test can run, but you have another error:
Ran 1 test in 0.006s
FAILED (errors=1)
Error
Traceback (most recent call last):
...
TypeError: process_request() takes 1 positional argument but 2 were given
If you take a look at your process_request() method, you can see that you missed the self parameter. Change it like this.
def process_request(self, data):
# do some stuffs here
return {'foo': 'bar'}
Your test should pass.
But, that not the right way to implement Flask-Restful controolers. Read the doc and use get and post methods…
I'm trying to implement a decorator function in Flask that does two things: register a page route and add some things to my DB on definition time, and then on runtime, when a request is made to a page it processes whether a user is authenticated.
I know that I can do these in two separate decorator functions, but this won't work because I eventually need to process the same set of information (app, rule, access groups, etc.) at both definition time and runtime.
The code below is what I have been messing with, but I have not been able to get it to work. It compiles and you are able to see "Definition time" in the console and "index" in the browser, but "Runtime" never gets printed. Can anyone suggest what I may be doing wrong?
from flask import Flask
from functools import wraps
app = Flask(__name__)
def register(app, rule, **options):
def wrapper(f):
print "Definition time"
endpoint = options.pop('endpoint', f.__name__)
access_groups = options.pop('access_groups', None)
methods = options.get('methods', ['GET'])
app.add_url_rule(rule, endpoint, f, **options)
#wraps(f)
def wrapped(*args, **kwargs):
print "Runtime"
return f(*args, **kwargs)
return wrapped
return wrapper
#register(app, '/', methods=['GET', 'POST'], access_groups=['all-access'])
def index():
return "index"
if __name__ == '__main__':
app.run(debug=True)
You've added the original function in add_url_rule, not the wrapped function. Move add_url_rule below wrapped and change the target.
def wrapped(...)
...
app.add_url_rule(rule, endpoint, wrapped, **options)
return wrapped
Here's the full code and the output on the console when run and accessed.
from functools import wraps
from flask import Flask
app = Flask(__name__)
def register(app, rule, **options):
def wrapper(f):
print('Definition time')
endpoint = options.pop('endpoint', f.__name__)
access_groups = options.pop('access_groups', None)
methods = options.pop('methods', ['GET'])
#wraps(f)
def wrapped(*args, **kwargs):
print('Runtime')
return f(*args, **kwargs)
app.add_url_rule(rule, endpoint, wrapped, **options)
return wrapped
return wrapper
#register(app, '/', methods=['GET', 'POST'], access_groups=['all-access'])
def index():
return 'index'
if __name__ == '__main__':
app.run(debug=True)
$ python example.py
Definition time
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
Runtime
127.0.0.1 - - [04/Aug/2015 20:14:46] "GET / HTTP/1.1" 200 -
I've added a couple of BrowserViews through paster, now I'm trying to run them from plone.app.testing, because I like repeatable and consistent tests.
Calling the view manually from the browser works without any problems.
I've tried both importing and initializing views manually, as well as calling the class from restricted traverse. In both cases an object gets initialized just fine but when I try to trigger rendering either calling the instance as a function or using __call__() method, I get the following error:
*** KeyError: 'global_cache_settings
I looked at the example in README.rst in plone.app.testing it doesn't seem to mention the problem, quick googling didn't yield any results either. It could be one of the site customization, however when greping the source, I found 'global_cache_settings' mentioned in Products.CMFPlone, so it's probably a plone thing:
eggs/Products.CMFPlone-4.1.4-py2.6.egg/Products/CMFPlone/skins/plone_templates/main_template.pt
28: <metal:cache use-macro="context/global_cache_settings/macros/cacheheaders">
29: Get the global cache headers located in global_cache_settings.
eggs/plone.app.layout-2.1.13-py2.6.egg/plone/app/layout/presentation/presentation.pt
11: <metal:cache use-macro="here/global_cache_settings/macros/cacheheaders">
12: Get the global cache headers located in global_cache_settings.
eggs/plonetheme.sunburst-1.1.6-py2.6.egg/plonetheme/sunburst/skins/sunburst_templates/main_template.pt
20: <metal:cache use-macro="context/global_cache_settings/macros/cacheheaders">
21: Get the global cache headers located in global_cache_settings.
Here's relevant class declarations:
from zope.interface import implements, Interface
from Products.Five import BrowserView
from Products.CMFCore.utils import getToolByName
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
from Products.MyProduct import MyProductMessageFactory as _
class IPromoBoardHome(Interface):
"""
PromoBoardHome view interface
"""
def __init__(self, context, request):
pass
def __call__(self):
pass
def test():
""" test method"""
class PromoBoardHome(BrowserView):
"""
PromoBoardHome browser view
"""
implements(IPromoBoardHome)
def __init__(self, context, request):
self.context = context
self.request = request
def __call__(self):
return ViewPageTemplateFile('pt/promoboardhome.pt')(self)
#property
def portal_catalog(self):
return getToolByName(self.context, 'portal_catalog')
#property
def portal(self):
return getToolByName(self.context, 'portal_url').getPortalObject()
def test(self):
"""
test method
"""
dummy = _(u'a dummy string')
return {'dummy': dummy}
Template:
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
lang="en"
metal:use-macro="here/main_template/macros/master"
i18n:domain="Products.MyProduct">
<body>
<div metal:fill-slot="main">
<tal:main-macro metal:define-macro="main"
tal:define="testview view/test">
<span tal:content="testview/dummy">test</span>
</tal:main-macro>
</div>
</body>
</html>
configure.zcml declaration:
<browser:page
for="*"
name="promoboardhome"
class=".promoboardhome.PromoBoardHome"
allowed_interface=".promoboardhome.IPromoBoardHome"
permission="zope2.View"
/>
test file:
import unittest2 as unittest
from . import PROD_INTEGRATION_TESTING
from plone.app.testing import setRoles, TEST_USER_ID
from Products.CMFCore.utils import getToolByName
from Products.MyProduct.browser.promoboardhome import PromoBoardHome
class TestPromoBoardHome(unittest.TestCase):
layer = PROD_INTEGRATION_TESTING
def setUp(self):
self.portal = self.layer['portal']
def test_call(self):
pbh = self.portal.restrictedTraverse('##promoboardhome')
try:
pbh()
except KeyError:
print "that didn't work!"
import pdb; pdb.set_trace()
Am I doing something wrong here?
Browser views are nothing but named multi adapters; to test them you have call them on the right context and to manually set up the request to provide the interface declared for the view; in you case is probably something like this:
from zope.interface import alsoProvides
from Products.MyProduct.browser.promoboardhome import IPromoBoardHome
class TestPromoBoardHome(unittest.TestCase):
layer = PROD_INTEGRATION_TESTING
def setUp(self):
self.portal = self.layer['portal']
self.request = self.layer['request']
alsoProvides(self.request, IPromoBoardHome)
I have a bunch of browser views tests on packages like collective.nitf.
Happy testing!
You probably want to test the views with testbrowser instead:
from plone.testing import z2
browser = z2.Browser(self.layer['app'])
browser.open(self.layer['portal'].absolute_url() + '/##promoboardhome')
Or similar.
I'm trying to test tornado using AsyncHTTPTestCase. I want to test handlers marked with the #tornado.web.authenticated annotation. Because this handler requires authentication we must login first or somehow fool it in thinking we are authenticated in our test code
class HandlerToTest(BaseHandler):
#tornado.web.authenticated
def get(self):
self.render("hello.html", user=self.get_current_user() )
According to this article we can fudge the cookies. I have got this working but according to Ben Darnell tornado maintainer this is not recommended. Ben recommends using the CookieLib module but that requires the 'info' part of the response which we don't have.
Another blog post suggests mocking the get_current_user() call using mox. However I can not get the sample code in the blog working.
So my question is:
What is the best way of testing the handlers marked as authenticated? And can someone point me to a sample application?
Eventually got Mocks working. Don't know if this is the 'best way' but it might be useful to someone in the future. This code tests 2 handlers and mocks the get_current_user() call generated by #tornado.web.authenticated:
# encoding: utf-8
import os, os.path, sys
import tornado.web
import tornado.testing
import mox
class BaseHandler(tornado.web.RequestHandler):
def get_login_url(self):
return u"/login"
def get_current_user(self):
user_json = self.get_secure_cookie("user")
if user_json:
return tornado.escape.json_decode(user_json)
else:
return None
class HelloHandler(BaseHandler):
#tornado.web.authenticated
def get(self):
self.render("protected.html")
class Protected(tornado.web.RequestHandler):
def get_current_user(self):
# get an user from somewhere
return "andy"
#tornado.web.authenticated
def get(self):
self.render("protected.html")
class TestAuthenticatedHandlers(tornado.testing.AsyncHTTPTestCase):
def get_app(self):
self.mox = mox.Mox()
app = tornado.web.Application([
(r'/protected', Protected),
(r'/hello', HelloHandler)
])
return app
def tearDown(self):
self.mox.UnsetStubs()
self.mox.ResetAll()
def test_new_admin(self):
self.mox.StubOutWithMock(Protected, 'get_current_user', use_mock_anything=True)
Protected.get_current_user().AndReturn("test_user")
self.mox.ReplayAll()
resp = self.fetch('/protected')
self.assertEqual(resp.code, 200)
self.mox.VerifyAll()
def test_hello_page(self):
self.mox.StubOutWithMock(HelloHandler, 'get_current_user', use_mock_anything=True)
HelloHandler.get_current_user().AndReturn("test_user")
self.mox.ReplayAll()
resp = self.fetch('/hello')
self.assertEqual(resp.code, 200)
self.assertIn( "Hello", resp.body )
self.mox.VerifyAll()
This Torando utils library also allows you to test authenticated handlers: tornado_utils/http_test_client.py
In my case simply works:
BaseHandler.get_current_user = lambda x: {'username': 'user', 'email': 'user#ex.com'}