aiohttp: get current running app - python

Currently I'm doing the following to get the current running app
async def handler(request):
app = request.app
Isn't there another way for getting the current running app? Consider the below snippet (the default for author_id):
class Comment(DeclarativeBase):
author_id = Column(Integer, ForeignKey('member.id'), default=Member.current_logged_in())
class Member(DeclarativeBase):
#classmethod
def current_logged_in()
pass
As the session is kept in the current running app and as you can see it is only accessible from the incoming request, how can I get the current running app to use the session for determining the current_logged_in user and thus be used as the default value for Comment's author_id?
I wish I have made my point.

Right now there is no implicit context for aiohttp application.
BTW don't do synchronous calls (SQLAlchemy ORM in your case) from aiohttp code.

Related

Flask session object does not persist between requests despite hardcoded secret key

I am currently running into an issue deploying a Flask app on Amazon's EB2 service. The Flask app works locally. When it is deployed, however, it only works for the first person who clicks the link. After that it throws the following error:
Internal Server Error The server encountered an internal error and was
unable to complete your request. Either the server is overloaded or
there is an error in the application.
The error it is throwing out concerns the Flask session - it becomes empty after routing from one site to another. I also noticed that the before_first_request function detailed below is ran only once, for the first user, and never again - which is even more bewildering.
Here's the minimal example:
from flask import Flask, render_template, request, session, url_for
application = Flask(__name__)
application.secret_key = "mysecretkey"
#application.before_first_request
def before_first_request():
""" these commands are run before the first request"""
# setup logging
application.logger.setLevel(logging.INFO)
application.logger.info('starting up Flask')
# clear session
session.clear()
# load in PID
session['pid'] = 123
# add parameters to the session
params = dict()
params['parameter'] = 0
session['params'] = params
application.logger.info(session) # it is printing the session as expected
return 'OK'
#application.route('/')
def main():
""" landing page """
application.logger.info(session) # empty
application.logger.info(application.secret_key) # as expected
params, results = session.pop('params'), session.pop('results') # throws out the error
return render_template('empty_template.jinja', args = session)
I am wondering if anyone might know what is going on how to resolve the issue?
I managed to solve it.
The error was that #before_first_request wrapper actually only ran once before first request ever made to the app. Hence, the session was actually only created and populated once.
I fixed that error by adding the call to before_first_request function at the top of the main function.

Unit test a listener in Sanic without starting the app server

Assuming I have this listener defined in my Sanic app:
#app.before_server_start
async def db_setup(*args):
# ... set up the DB as I wish for the app
If I want to unit test this function (with pytest) and I import it in a unit test file with from my.app import db_setup, it seems the test actually starts serving the app, as pytest outputs:
[INFO] Goin' Fast # http://0.0.0.0:8000
[INFO] Starting worker [485]
Now, I know that I can remove the effects of the decorator by doing db_setup = db_setup.__wrapped__, but in order to do this I actually need to import db_setup, which is where the Sanic server fires up.
Is there a way of removing the effects of the decorator at import time?
LE: I've tried patching the Sanic app as follows:
async def test_stuff(mocker):
mocker.patch('myapp.app.app') # last '.app' being `app = Sanic('MyApp')`
imp = importlib.import_module('myapp.app')
db_setup = getattr(imp, 'db_setup')
await db_setup()
but now I get a RuntimeError: Cannot run the event loop while another loop is running for the mocker.patch('myapp.app.app') line.
I am going to make a few assumptions here, so I may need to amend this answer if there are some clarifications.
Before starting, it should be noted that the decorator itself will not start your web server. That will run in one of two scenarios:
You are running app.run() somewhere in the global scope
You are using the Sanic TestClient, which specifically operates by running your application's web server
Now, from what I can understand, you are trying to run db_setup in a test manually by calling it as a function, but you do not want it to attach as a listener to the application in your tests.
You can get access to all of your application instance's listeners in the app.listeners property. Therefore one solution would be something like this:
# conftest.py
import pytest
from some.place import app as myapp
#pytest.fixture
def app():
myapp.listeners = {}
return myapp
Like I said earlier, this will just empty out your listeners. It does not actually impact your application starting, so I am not sure it has the utility that you are looking for.
You should be able to have something like this:
from unittest.mock import Mock
import pytest
from sanic import Request, Sanic, json
app = Sanic(__name__)
#app.get("/")
async def handler(request: Request):
return json({"foo": "bar"})
#app.before_server_start
async def db_setup(app, _):
app.ctx.db = 999
#pytest.mark.asyncio
async def test_sample():
await db_setup(app, Mock())
assert app.ctx.db == 999
For the sake of ease, it is all in the same scope, but even if the test functions, the application instance, and the listener are spread across different modules, the end result is the same: You can run db_setup as any other function and it does not matter if it is registered as a listener or not.

Flask keep live server working for tests (Selenium)

First of all I am aware of flask-testing library with LiveServerTestCase class but it hasn't updated since 2017 and GitHub full of issues of it not working neither on Windows or MacOs and I haven't found any other solutions.
I am trying to write some tests for flask app using selenium to validate FlaskForms inside this app.
Simple test like this:
def test_start(app):
driver.get("http://127.0.0.1:5000/endpoint")
authenticate(driver)
falls on selenium.common.exceptions.WebDriverException: Message: unknown error: net::ERR_CONNECTION_REFUSED error. (As far as I understood in my case app creates in #pytest.fixtures and immediately shuts down and I need to find a way to keep it running for the whole test duration)
My question is: Is it possible to to create some live server in each test that will remain working so I could call API endpoints via selenium?
Simple fixtures if it helps:
#pytest.fixture
def app():
app = create_app()
...
with app.context():
# creating db
...
yield app
also:
#pytest.fixture
def client(app):
"""Test client"""
return app.test_client()
Finally got it all working. My conftest.py
import multiprocessing
import pytest
from app import create_app
#pytest.fixture(scope="session")
def app():
app = create_app()
multiprocessing.set_start_method("fork")
return app
#pytest.fixture
def client(app):
return app.test_client()
Important note that using python <3.8 line multiprocessing.set_start_method("fork") is not necessary (as far as I understood in v.3.8 they refactored multiprocessing module so further upon without this line you would get pickle Error on windows and Mac).
And one simple test looks like
def test_add_endpoint_to_live_server(live_server):
#live_server.app.route('/tests-endpoint')
def test_endpoint():
return 'got it', 200
live_server.start()
res = urlopen(url_for('.te', _external=True))# ".te is a method path I am calling"
assert url_for('.te', _external=True) == "some url"
assert res.code == 200
assert b'got it' in res.read()
Also I am using url_for. The point is every time live server starts on a random port and url_for function generates url with correct port internally. So now live server is running and it is possible to implement selenium tests.

How to mock a rest API in python

I have a application running which some where in the midst uses some rest API call. Now for stress test I want to replace this API call with some mock server. Is there any way to do it.
Let me try to put it programmatically so it gets some clarity. I've a some server running at port say 8080
# main server
from flask import Flask
from myapp import Myapp
app = Flask(__name__)
#app.route("/find_solution", methods=["GET"])
def solution() :
return app.sol.find_solution(), 200
def start():
app.sol = Myapp()
return app
Now this Myapp
#myapp
import requests
class Myapp:
def __init__():
self.session = requests.Session()
def find_solution():
myparameters = {"Some parameter that I filled"}
return self.session.request('GET', 'http://api.weatherstack.com/current', params=myparameters)
Now here I want to replace behavior of http://api.weatherstack.com/current without modifying code. i.e some way where I can replace call to http:api.weatherstack.com/current to my local system server.
Any help of lead is appreciated. I am using ubuntu 20.04
So for your scenario if you want to test your api flask comes with mock test client feature.
test_client = app.test_client()
test_client.post('/find_solution', headers={"Content-Type": "application/json"}, data=data)
So for this scenario you can create test cases and get test client instance inside your test case and perform tests at api level. This is a light weight test method rather than the one proposed by you
Refer to the following link for official flask documentation
https://flask.palletsprojects.com/en/1.1.x/testing/#keeping-the-context-around
Cheers

Using pytest with gaesessions session middleware in appengine

When I run py.test --with-gae, I get the following error (I have pytest_gae plugin installed):
def get_current_session():
"""Returns the session associated with the current request."""
> return _tls.current_session
E AttributeError: 'thread._local' object has no attribute 'current_session'
gaesessions/__init__.py:50: AttributeError
I'm using pytest to test my google appengine application. The application runs fine when run in the localhost SDK or when deployed to GAE servers. I just can't figure out how to make pytest work with gaesessions.
My code is below:
test_handlers.py
from webtest import TestApp
import appengine_config
def pytest_funcarg__anon_user(request):
from main import app
app = appengine_config.webapp_add_wsgi_middleware(app)
return TestApp(app)
def test_session(anon_user):
from gaesessions import get_current_session
assert get_current_session()
appengine_config.py
from gaesessions import SessionMiddleware
def webapp_add_wsgi_middleware(app):
from google.appengine.ext.appstats import recording
app = recording.appstats_wsgi_middleware(app)
app = SessionMiddleware(app, cookie_key="replaced-with-this-boring-text")
return app
Relevant code from gaesessions:
# ... more code are not show here ...
_tls = threading.local()
def get_current_session():
"""Returns the session associated with the current request."""
return _tls.current_session
# ... more code are not show here ...
class SessionMiddleware(object):
"""WSGI middleware that adds session support.
``cookie_key`` - A key used to secure cookies so users cannot modify their
content. Keys should be at least 32 bytes (RFC2104). Tip: generate your
key using ``os.urandom(64)`` but do this OFFLINE and copy/paste the output
into a string which you pass in as ``cookie_key``. If you use ``os.urandom()``
to dynamically generate your key at runtime then any existing sessions will
become junk every time your app starts up!
``lifetime`` - ``datetime.timedelta`` that specifies how long a session may last. Defaults to 7 days.
``no_datastore`` - By default all writes also go to the datastore in case
memcache is lost. Set to True to never use the datastore. This improves
write performance but sessions may be occassionally lost.
``cookie_only_threshold`` - A size in bytes. If session data is less than this
threshold, then session data is kept only in a secure cookie. This avoids
memcache/datastore latency which is critical for small sessions. Larger
sessions are kept in memcache+datastore instead. Defaults to 10KB.
"""
def __init__(self, app, cookie_key, lifetime=DEFAULT_LIFETIME, no_datastore=False, cookie_only_threshold=DEFAULT_COOKIE_ONLY_THRESH):
self.app = app
self.lifetime = lifetime
self.no_datastore = no_datastore
self.cookie_only_thresh = cookie_only_threshold
self.cookie_key = cookie_key
if not self.cookie_key:
raise ValueError("cookie_key MUST be specified")
if len(self.cookie_key) < 32:
raise ValueError("RFC2104 recommends you use at least a 32 character key. Try os.urandom(64) to make a key.")
def __call__(self, environ, start_response):
# initialize a session for the current user
_tls.current_session = Session(lifetime=self.lifetime, no_datastore=self.no_datastore, cookie_only_threshold=self.cookie_only_thresh, cookie_key=self.cookie_key)
# create a hook for us to insert a cookie into the response headers
def my_start_response(status, headers, exc_info=None):
_tls.current_session.save() # store the session if it was changed
for ch in _tls.current_session.make_cookie_headers():
headers.append(('Set-Cookie', ch))
return start_response(status, headers, exc_info)
# let the app do its thing
return self.app(environ, my_start_response)
The problem is that your gae sessions is not yet called until the app is also called. The app is only called when you make a request to it. Try inserting a request call before you check for the session value. Check out the revised test_handlers.py code below.
def test_session(anon_user):
anon_user.get("/") # get any url to call the app to create a session.
from gaesessions import get_current_session
assert get_current_session()

Categories