I am new to Flask, and I am trying to learn so I can write pytests for applications I am working on.
In my project, there are two applications that are sometimes communicating; one occasionally sends requests to the other using the 'request' library, and the other sends a response back.
In most of the examples of Flask testing I have seen, it is done by using an application's 'test_client' method. The question I have is on how to get two of these test clients to communicate.
For a toy example, here I have two apps:
flask_app1.py
from flask import Flask
import requests
def create_app():
app = Flask(__name__)
#app.route('/send_message/<msg>')
def send_message(msg):
response = requests.post('http://127.0.0.1:5001/receive_msg', json={'message': msg})
print(response.text)
return "sent:" + response.json()['received']
return app
if __name__ == '__main__':
app = create_app()
app.run()
flask_app2.py
from flask import Flask, request
def create_app():
app = Flask(__name__)
#app.route('/receive_msg', methods=["POST"])
def receive():
msg = request.get_json()["message"]
print(msg)
return {'received': msg}
return app
if __name__ == '__main__':
app = create_app()
app.run(port=5001)
Simply, the first app sends a message to the second, which then sends it back to the first.
Now suppose I have the tests:
import flask_app1
import flask_app2
def test_two_clients():
app1 = flask_app1.create_app()
client1 = app1.test_client()
app2 = flask_app2.create_app()
client2 = app2.test_client()
r = client1.get('/send_message/hello_there')
assert r.status_code == 200
def test_one_client():
app1 = flask_app1.create_app()
client1 = app1.test_client()
r = client1.get('/send_message/hello_there')
assert r.status_code == 200
The bottom test works when flask_app2 is running in a terminal, but that's not what I want.
I am not sure how to get client1 to communicate with client2
I am not aware of any pattern to test two Flask applications against each other, without at least running one.
I acknowledge you do not want to run them, but for completeness:
You could spin up some Docker containers, run your applications there, and test them this way. IMHO this makes perfect sense for an end to end test.
As you do not want to do this, you should think about the implementation of your code. There is pattern called Dependency Injection - this enables you to test your applications completely self contained.
There is an excellent blog post about this https://www.cosmicpython.com/blog/2020-01-25-testing_external_api_calls.html
Other possibilities are to mock the call to the other Flask app or to record its output via https://github.com/kevin1024/vcrpy
I know, this was not the expected answer, but I hope you find something useful.
Related
I have a Flask app and I use SQLAlachemy(without Flask extension cause I need to create my own class based SQLAlchemy and so on).
My app has a connection to its database over engine and it works fine but now I need to make my engine dynamically and get db_name from Flask.g
Engine is declared in models.py
models.py
engine = create_engine(f"postgresql://postgres:postgres#localhost:5434/{g['tenant']}", convert_unicode=True)
db_session = scoped_session(sessionmaker(autocommit=False,
autoflush=False,
bind=engine))
Base = declarative_base()
Base.query = db_session.query_property()
For the start app I use wsgi.py:
from app import app
if __name__ == "__main__":
app.run(port=5002)
when I type python wsgi.py I receive an error.
RuntimeError: Working outside of application context.
This typically means that you attempted to use functionality that needed
to interface with the current application object in some way. To solve
this, set up an application context with app.app_context(). See the
documentation for more information.
In general I understand that I use an engine which is outside the context. The issue is - I cannot figure out how to pass my engine variable to context.
I try to make create app func:
def create_app():
app = Flask(__name__)
with app.app_context():
engine = create_engine(f"postgresql://postgres:postgres#localhost:5434/{g['tenant']}", convert_unicode=True)
return app
Also I tried app.app_context().push(engine)
But it doesn't work. How I can solve this issue?
The problem is that the flask object g only ever exists when a request is currently in progress on flask. (Without a request, there is no flask g, because g is specifically a global for individual requests)
what you'd have to do is to create that engine after the request starts, which slows down the route a bit. The #app.before_request decorator might help you here:
#app.before_request
def create_engine_for_request():
engine = create_engine(f"postgresql://postgres:postgres#localhost:5434/{g['tenant']}", convert_unicode=True)
("before_request" is already "during a request" - its just the first thing that flask does "when a request starts")
I'm using blueprints with an application factory, so I cannot import the app instance. Do you guys see anything wrong with setting app to your config?
def create_app():
app = Flask(__name__)
app.config['app'] = app
with app.app_context():
configure_app(app)
configure_blueprints(app)
...
Now app can be accessed from a different module via current_app.config['app']
app = current_app.config['app']
with app.app_context():
...
Here it is in a real example:
from flask import current_app
def send_async_email(current_app, msg):
with current_app.app_context():
mail.send(msg)
def send_email(subject, sender, recipients, text_body, html_body):
msg = Message(subject, sender=sender, recipients=recipients)
msg.body = text_body
msg.html = html_body
Thread(target=send_async_email,
args=(current_app.config['app'], msg)).start()
Using current_app alone in the Thread arguments, I get an error saying I'm working outside of the application context. Using current_app.config['app'] does work, I just want to know if there is a different way, or if there is anything wrong with doing it this way?
That's because current_app is just a proxy to the thread local app. This should solve it:
app= current_app._get_current_object()
This hands you back the original app object. Your config example worked because it also uses the original app not the proxy.
Now you can pass it on the your new thread as such:
Thread(target=send_async_email, args=(app, msg)).start()
That being said, it's a bad idea setting your app as an item of your app.config as it's recursive.
I got a flask application with different apps inside, using BluePrint.
To simplify, I got an API that manages token web authentification (and a lot of other data functions) and a website that should call the API to get the valid token, using a basic auth to start with
The issue is that when the website requests the API, it never gets any feedback from the API.
Requesting the API via POSTMAN works like a charm, but this call below, done from a website route is waiting, waiting, waiting and never ends.
So my assumption is that using the same port for both website and api is the issue
I could of course divides the flask into 2 flask apps with 2 servers, but there are many objects and tools both API and website are sharing, so I dont want to double the job
Thanks.
call from the website
from requests.auth import HTTPBasicAuth
import requests
mod = Blueprint('site', __name__, template_folder='templates/login')
def load_user(username, password):
data = requests.get('http://127.0.0.1:5000/api/login',
auth=HTTPBasicAuth('username', 'password'))
return data
#mod.route('/')
def index():
username = 'jeje'
password = 'jeje'
data = load_user(username, password)
return '<h1>load user<h1>'
the api function
#mod.route('/login')
def login():
resu = True
auth = request.authorization
if not auth or not auth.username or not auth.password:
resu = False
user = USER.query.filter_by(username = auth.username).first()
if not user:
resu = False
if validehash(user.password, auth.password):
period_in_mn = 120
payload = {
'public_id':user.public_id,
'exp' : datetime.datetime.utcnow() + datetime.timedelta(minutes = period_in_mn)
}
token = createtoken(payload, current_app.config['SECRET_KEY'])
if resu:
return jsonify({'token' : token })
else:
return jsonify({'token' : 'unknown'})
I guess your using flask 0.12 instead of 1.0. So whats happening here is that you're requesting a route from within another route.
Your browser requests /, and in your index function, you request /login. But the flask process is still working on the / request from the browser, and can't take any other requests, because flask 0.12 is working on a single core only.
Requesting like this is bad design. You could make a helper function, which returns the same data in different requests (either api or main site), or you could make the browser send another request using AJAX.
Flask 1.0 has multicore support, so I think this might work over there, but I really think you should change your design. This has absolutely nothing to do with blueprints by the way.
from flask import Flask, redirect
import requests
app = Flask(__name__)
#app.route('/')
def index():
result = requests.get('http://127.0.0.1:5000/api/login')
return 'this is the index<br>' + result.text
#app.route('/api/login')
def login():
return 'you are logged in'
Using flask 0.12, times out when visiting http://127.0.0.1:5000/.
Using flask 1.0, returns this is the index
you are logged in when visiting http://127.0.0.1:5000/.
I have been working on implementing PyTeaser as an API so can send requests using my program to get a summary of an article. I have been trying to figure out how to send url requests to the API using flask. I was having trouble routing my url request because I didn't really understand what goes in the ???? under app route section below so that I can route my requests.
from flask import Flask, jsonify
from PyTeaser import SummarizeUrl
from PyTeaser import Summarize
app = Flask(__name__)
#app.route('????', methods=['GET'])
def summary_url(url):
summary = SummarizeUrl(url)
return jsonify({'title': title, 'url': url, 'summaries': summary})
#app.route('????', methods=['GET'])
def summary(title, text):
summary = Summarize(title, text)
return jsonify({'title': title, 'summaries': summary})
if __name__ == '__main__':
app.run(debug=True,app.run(host='0.0.0.0'))
Something like
#app.route('/<url>')
I'm not sure second route as it looks like you want to take in two variables.
But possibly:
#app.route('/<title>/<text>')
I've been working in a Django project for a while and am starting on a new app, trying to do more automated testing with Selenium at the same time. I'm using http://www.tdd-django-tutorial.com/ as a guide.
I'm trying to test the ability to log in to my application. My test can pull up the page and fill in text fields without trouble, but when it clicks on the submit button, it hangs - Firefox keeps trying to load the new page but it never happens. It looks like deadlock to me, but I don't understand it well enough to know what's going on.
Other details: I'm using Django's built-in login view. I have another test that successfully logs in to the admin site. I can log in to my application just fine when I test manually. The application accesses a remote MySQL database.
Here's my test:
from django.test import TestCase, LiveServerTestCase
from selenium import webdriver
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.common.keys import Keys
class BucloudTest(LiveServerTestCase):
"""Tests shared functionality (login, network and app selection)."""
fixtures = ['24aug2012_dev_auth.json']
def setUp(self):
self.browser = webdriver.Firefox()
self.browser.implicitly_wait(5)
def tearDown(self):
self.browser.quit()
def test_good_login(self):
"""Tests that a user can log in using valid credentials."""
self.browser.get(self.live_server_url + "/login/")
user_css = "[placeholder=Username]"
user_field = self.browser.find_element_by_css_selector(user_css)
user_field.send_keys("test_user1")
pw_css = "[placeholder=Password]"
pw_field = self.browser.find_element_by_css_selector(pw_css)
pw_field.send_keys("test")
button = self.browser.find_element_by_css_selector("[value='Sign in']")
button.click()
WebDriverWait(self.browser, 30).until(
lambda driver: driver.find_element_by_tag_name('body'))
body = self.browser.find_element_by_tag_name("body")
self.assertIn("Properties", body.text)
print "ran tests YAY!!"
I run the test with manage.py test functests --liveserver=localhost:8080-8090.
Thanks very much for any suggestions!
It might be due to the fact that the content of your test_good_login() function is not indented