Why does my 'Flask' object have no attribute 'get' [duplicate] - python

I am getting working outside of request context when trying to access session in a test. How can I set up a context when I'm testing something that requires one?
import unittest
from flask import Flask, session
app = Flask(__name__)
#app.route('/')
def hello_world():
t = Test()
hello = t.hello()
return hello
class Test:
def hello(self):
session['h'] = 'hello'
return session['h']
class MyUnitTest(unittest.TestCase):
def test_unit(self):
t = tests.Test()
t.hello()

If you want to make a request to your application, use the test_client.
c = app.test_client()
response = c.get('/test/url')
# test response
If you want to test code which uses an application context (current_app, g, url_for), push an app_context.
with app.app_context():
# test your app context code
If you want test code which uses a request context (request, session), push a test_request_context.
with current_app.test_request_context():
# test your request context code
Both app and request contexts can also be pushed manually, which is useful when using the interpreter.
>>> ctx = app.app_context()
>>> ctx.push()
Flask-Script or the new Flask cli will automatically push an app context when running the shell command.
Flask-Testing is a useful library that contains helpers for testing Flask apps.

Related

Generate URLs for Flask test client with url_for function

I'm trying to write unit tests for a Flask app using pytest. I have an app factory:
def create_app():
from flask import Flask
app = Flask(__name__)
app.config.from_object('config')
import os
app.secret_key = os.urandom(24)
from models import db
db.init_app(app)
return app
And a test class:
class TestViews(object):
#classmethod
def setup_class(cls):
cls.app = create_app()
cls.app.testing = True
cls.client = cls.app.test_client()
#classmethod
def teardown_class(cls):
cls.app_context.pop()
def test_create_user(self):
"""
Tests the creation of a new user.
"""
view = TestViews.client.get(url_for('create_users')).status_code == 200
but when I run my tests I get the following error:
RuntimeError: Attempted to generate a URL without the application context being pushed. This has to be executed when application context is available.
Googling this tells me (I think) that using the test client should create an automatic application context. What am I missing?
Making requests with the test client does indeed push an app context (indirectly). However, you're confusing the fact that url_for is visually inside the test request call with the idea that it is actually called inside. The url_for call is evaluated first, the result is passed to client.get.
url_for is typically for generating URLs within the app, unit tests are external. Typically, you just write exactly the URL you're trying to test in the request instead of generating it.
self.client.get('/users/create')
If you really want to use url_for here, you must do it in an app context. Note that when you're in an app context but not a request context, you must set the SERVER_NAME config and also pass _external=False. But again, you should probably just write out the URL you're trying to test.
app.config['SERVER_NAME'] = 'localhost'
with self.app.app_context():
url = url_for(..., _external=False)
self.client.get(url, ...)
You can call url_for() in test request context that created with app.test_request_context() method. There are three methods to achieve this.
With setup and teardown
Since you have created the setup and teardown method, just like what I normally do with unittest, you can just push a test request context in setup method then pop it in teardown method:
class TestViews(object):
#classmethod
def setup_class(cls):
cls.app = create_app()
cls.app.testing = True
cls.client = cls.app.test_client()
cls.context = cls.app.test_request_context() # create the context object
cls.context.push() # push the context
#classmethod
def teardown_class(cls):
cls.context.pop() # pop the context
def test_create_user(self):
"""
Tests the creation of a new user.
"""
view = TestViews.client.get(url_for('create_users')).status_code == 200
With pytest-flask
Besides, you can also just use pytest-flask. With pytest-flask, you can access to context bound objects (url_for, request, session) without context managers:
def test_app(client):
assert client.get(url_for('myview')).status_code == 200
With autouse fixture
If you don't want to install the plugin, you can just use the following fixtures to do similar things (stolen from the source of pytest-flask):
#pytest.fixture
def app():
app = create_app('testing')
return app
#pytest.fixture(autouse=True)
def _push_request_context(request, app):
ctx = app.test_request_context() # create context
ctx.push() # push
def teardown():
ctx.pop() # pop
request.addfinalizer(teardown) # set teardown

How to protect custom endpoints using BasicAuth?

Say I have enabled authentication to the resources using BasicAuth:
class MyBasicAuth(BasicAuth):
def check_auth(self,username,password,allowed_roles,resource,method):
return username == 'secretusername' and password == 'secretpass'
I also have custom routes which are used to manage documents from a HTML view. How do I use the same MyBasicAuth to protect the all the custom routes? I also need to implement logic which authenticates using the above MyBasicAuth.
Please help me with this. It's for personal use, so I preferred hard coding the username and password.
If you are trying to use a custom end-point Authentication you will find it difficult as mentioned here:
https://github.com/pyeve/eve/issues/860
I ended up writing a wrapper to get around the issue of 'resource' not being passed to 'requires_auth':
def auth_resource(resource):
def fdec(f):
#wraps(f)
def wrapped(*args, **kwargs):
return f(resource=resource, *args, **kwargs)
return wrapped
return fdec
This way you can define in your DOMAIN an authentication class:
DOMAIN = {
'testendpoint'= {'authentication':MyCustomAuthetication},
'otherendpoints'=...
And in my app I have wrapped the requires_auth decorator and added this as a authentication resource.
#app.route('/testendpoint/<item>', methods=['GET'])
#auth_resource('testendpoint')
#requires_auth('item')
def my_end_point_function(*args, **kwargs):
dosomthinghere
As long as an authentication class is defined in the settings file for an endpoint, this also allows you to reuse any authentication defined in another endpoint which may be handy if you want to make sure all the endpoints use the same authentication.
You can leverage the requires_auth decorator which is used internally by Eve itself. That way, your auth class will also be used to protect your custom routes:
from eve import Eve
from eve.auth import requires_auth
app = Eve()
#app.route('/hello')
#requires_auth('resource')
def hello_world():
return 'Hello World!'
if __name__ == '__main__':
app.run()
If you are using flask blueprints for your custom routes, you can add a before request function for your blueprint to do that.
First, create a function to check authentication from blueprints. You need to get the Authorization header from the flask request by yourself, like this:
from flask import request, abort, current_app
from werkzeug.http import parse_authorization_header
def check_blueprint_auth():
if 'Authorization' not in request.headers:
print('Authorization header not found for authentication')
return abort(401, 'Authorization header not found for authentication')
header = parse_authorization_header(request.headers['Authorization'])
username = None if header is None else header['username']
password = None if header is None else header['password']
return username == 'secretusername' and password == 'secretpass'
Then, you can set this function to be called before each blueprint's request. Below is an example of a blueprint definition, setting the before_request function:
from flask import Blueprint, current_app as app
# your auth function
from auth import check_blueprint_auth
blueprint = Blueprint('prefix_uri', __name__)
# this sets the auth function to be called
blueprint.before_request(check_blueprint_auth)
#blueprint.route('/custom_route/<some_value>', methods=['POST'])
def post_something(some_value):
# something
Finally, you need to bind the blueprint with your eve app. An example on how to bind blueprints, taken in part from here:
from eve import Eve
# your blueprint
from users import blueprint
from flask import current_app, request
app = Eve()
# register the blueprint to the main Eve application
app.register_blueprint(blueprint)
app.run()
Hope that helps.

how do I make get request directly to a flask app object

I inherited a project that uses flask. This flask application has several APIs, each API has a GET function that returns a json object.
I was asked to implement an additional API that requests information from the other APIs. So my question is how do I make a GET request directly to a flask application? Is it something like....
from flask import request
#app.route('/root_dir/api_number_1/info', methods=['GET'])
def request_info_from_api_number_1():
return request.get_json()
#app.route('/root_dir/api_number_2/info', methods=['GET'])
def request_info_from_api_number_2():
return request.get_json()
When I do this the functions return None. I suppose I could always make an http request to the flask url and specify the address as localhost but it seems strange to have to make an http request when I can directly access the flask app object.
Could you just use the existing api functions?
from flask import Flask
app = Flask(__name__)
#app.route("/foo")
def foo():
return "foo"
#app.route("/foobar")
def foobar():
return foo() + "bar"
if __name__ == "__main__":
app.run(debug=True)

How to access request in Flask Middleware

I want to access request.url in middleware.
Flask app - test.py
from flask import Flask
from middleware import TestMiddleware
app = Flask(__name__)
app.wsgi_app = TestMiddleware(app.wsgi_app)
#app.route('/')
def hello_world():
return 'Hello World!'
if __name__ == '__main__':
app.run()
middleware.py:
from flask import request
class TestMiddleware(object):
def __init__(self, app):
self.app = app
def __call__(self, environ, start_response):
# How do I access request object here.
print "I'm in middleware"
return self.app(environ, start_response)
I understand request can be accessed in Flask application context. We normally use
with app.test_request_context()
But in middleware, I don't have access to Flask app object.
How do I proceed?
Thanks for any help..
It's the application object that constructs the request object: it doesn't exist until the app is called, so there's no way for middleware to look at it beforehand. You can, however, construct your own request object within the middleware (using Werkzeug directly rather than Flask):
from werkzeug.wrappers import Request
req = Request(environ, shallow=True)
You might even be able to construct Flask's own Request object (flask.wrappers.Request, which is a subclass of Werkzeug's Request class) the same way. Looking at the source I don't see anything that should stop you from doing this, but since it isn't designed to be used that way you're probably best off sticking with the Werkzeug one unless you need one of the extra properties added by Flask's subclass.
Middleware stands between your WSGI server and Flask Application. The request object is created in the Flask Application. So there isn't any request object in the middleware.
Perhaps you need a #before_request handler called just before your view?
You can't have a Request object before the application finds an URL rule and creates it. However, after the application has done its thing, you can find the Request object in the environ:
environ['werkzeug.request']

in a Flask unit-test, how can I mock objects on the request-global `g` object?

I have a flask application that is setting up a database connection in a before_filter, very similar to this:
#app.before_request
def before_request():
g.db = connect_db()
Now: I am writing some unit-tests and I do not want them to hit the database. I want to replace g.db with a mock object that I can set expectations on.
My tests are using app.test_client(), as is demonstrated in the flask documentation here. An example test looks something like
def test(self):
response = app.test_client().post('/endpoint', data={..})
self.assertEqual(response.status_code, 200)
...
The tests work and pass, but they are hitting the database and as I said I want to replace db access with mock objects. I do not see any way in test_client to access the g object or alter the before_filters.
This works
test_app.py
from flask import Flask, g
app = Flask(__name__)
def connect_db():
print 'I ended up inside the actual function'
return object()
#app.before_request
def before_request():
g.db = connect_db()
#app.route('/')
def root():
return 'Hello, World'
test.py
from mock import patch
import unittest
from test_app import app
def not_a_db_hit():
print 'I did not hit the db'
class FlaskTest(unittest.TestCase):
#patch('test_app.connect_db')
def test_root(self, mock_connect_db):
mock_connect_db.side_effect = not_a_db_hit
response = app.test_client().get('/')
self.assertEqual(response.status_code, 200)
if __name__ == '__main__':
unittest.main()
So this will print out 'I did not hit the db', rather than 'I ended up inside the actual function'. Obviously you'll need to adapt the mocks to your actual use case.

Categories

Resources