Pytest asynchronous monkeypatch applies mock to all tests - python

I have an asynchronous library asynpg which makes requests to an external database.
I want to have a test which mocks syncpg.connect to simulate a database connection, even when there is none:
#pytest.fixture
def mock_asyncpg(monkeypatch, mock_data):
""" Patch asyncpg.connect """
monkeypatch.setattr(asyncpg, "connect", mock_asyncio_connect(mock_data))
If I want to test using the mock I can include it:
#pytest.mark.asyncio
async def test_with_mock(mock_asyncpg):
But I'd also like to test with the real, unmocked, version of asyncpg, but asyncpg has been monkey-patched everywhere, and all tests see the monkey-patched version.
I assume this is happening because my tests are running asynchronously so they all see the same version of asyncpg
I see that there is a library for asynchronous monkey patching, but I wonder if there's a better pattern I should be using. Maybe something to do with CoroutineMock?

Related

pytest: How to execute setup code only if a certain fixture is loaded

I’m developing database based flask application (using flask-sqlalchemy). I use fixtures to define individual pieces of test data.
#pytest.fixture(scope='class')
def model_a(db):
a = ModelA()
db.session.add(a)
db.session.commit()
return a
#pytest.fixture(scope='class')
def model_b(db, a):
b = ModelB(a=a)
db.session.add(b)
db.session.commit()
return b
# …
While it works to call db.session.commit() for each and every test object it would be more efficient to call it only once right before executing the actual tests.
Is there a way to run db.session.commit() before every test, after all fixtures are loaded, but only if the test directly or indirectly needs db?
Things that I don’t think that they will work:
A python_runtest_setup-hook doesn’t seem to be able to access fixtures or to determine whether the db fixture is loaded for/required by the test.
A autouse fixture would need to depend on db and thus make all tests use the db fixture. Also I couldn’t find a way to make it executed last.
You can't specify fixtures ordering other than indirectly (fixtures depending on other fixtures), see discussion in issue #1216. You can access both fixture names and fixture values in hooks though, so using a hook is actually a good idea. However, pytest_runtest_setup is too early for all fixtures to be executed; use pytest_pyfunc_call instead. Example:
from _pytest.fixtures import FixtureRequest
def pytest_pyfunc_call(pyfuncitem):
if 'db' in pyfuncitem.fixturenames:
db = FixtureRequest(pyfuncitem, _ispytest=True).getfixturevalue('db')
db.session.commit()
# ensure this hook returns None, or your underlying test function won't be executed.
Warning: pytest.FixtureRequest() is considered non-public by the pytest maintainers for anything except type-hinting. That’s why its use issues a deprecation warning without the _ispytest=True flag. The API might change without a major release. There is no public API yet that supports this use-case. (Last updated for pytest 6.2.5)

Enhance synchronous software API to allow asynchronous consuming

I have a module in Python 3.5+, providing a function that reads some data from a remote web API and returns it. The function relies on a wrapper function, which in turn uses the library requests to make the HTTP call.
Here it is (omitting on purpose all data validation logic and exception handling):
# module fetcher.py
import requests
# high-level module API
def read(some_params):
resp = requests.get('http://example.com', params=some_params)
return resp.json()
# wrapper for the actual remote API call
def get_data(some_params):
return call_web_api(some_params)
The module is currently imported and used by multiple clients.
As of today, the call to get_data is inherently synchronous: this means that whoever uses the function fetcher.read() knows that this is going to block the thread the function is executed on.
What I would love to achieve
I want to allow the fetcher.read() to be run both in a synchronous and an asynchronous fashion (eg. via an event loop).
This is in order to keep compatibility with existing callers consuming the module and at the same time to offer the possibility
to leverage non-blocking calls to allow a better throughput for callers that do want to call the function asynchronously.
This said, my legitimate wish is to modify the original code as little as possible...
As of today, the only thing I know is that Requests does not support asynchronous operations out of the box and therefore I should switch to an asyncio-friendly HTTP client (eg. aiohttp) in order to provide a non-blocking behaviour
How would the above code need to be modified to meet my desiderata? Which also leads me to ask: is there any best practice about enhancing sync software APIs to async contexts?
I want to allow the fetcher.read() to be run both in a synchronous and an asynchronous fashion (eg. via an event loop).
I don't think it is feasible for the same function to be usable via both sync and async API because the usage patterns are so different. Even if you could somehow make it work, it would be just too easy to mess things up, especially taking into account Python's dynamic-typing nature. (For example, users might accidentally forget to await their functions in async code, and the sync code would kick in, thus blocking their event loop.)
Instead, I would recommend the actual API to be async, and to create a trivial sync wrapper that just invokes the entry points using run_until_complete. Something along these lines:
# new module afetcher.py (or fetcher_async, or however you like it)
import aiohttp
# high-level module API
async def read(some_params):
async with aiohttp.request('GET', 'http://example.com', params=some_params) as resp:
return await resp.json()
# wrapper for the actual remote API call
async def get_data(some_params):
return call_web_api(some_params)
Yes, you switch from using requests to aiohttp, but the change is mechanical as the APIs are very similar in spirit.
The sync module would exist for backward compatibility and convenience, and would trivially wrap the async functionality:
# module fetcher.py
import afetcher
def read(some_params):
loop = asyncio.get_event_loop()
return loop.run_until_complete(afetcher.read(some_params))
...
This approach provides both sync and async version of the API, without code duplication because the sync version consists of trivial trampolines, whose definition can be further compressed using appropriate decorators.
The async fetcher module should have a nice short name, so that the users don't feel punished for using the async functionality. It should be easy to use, and it actually provides a lot of new features compared to the sync API, most notably low-overhead parallelization and reliable cancellation.
The route that is not recommended is using run_in_executor or similar thread-based tool to run requests in a thread pool under the hood. That implementation doesn't provide the actual benefits of using asyncio, but incurs all the costs. In that case it is better to continue providing the synchronous API and leave it to the users to use concurrent.futures or similar tools for parallel execution, where they're at least aware they're using threads.

How to use pytest-aiohttp fixtures with scope session

I am trying to write tests for aiohttp application. I am using pytest-aiohttp plugin. My intention is to initialize and run the application once before first test execution and tear down after all tests finished. pytest-aiohttp fixtures like 'loop', 'test_client' are very helpful but they have scope='function' which means I can not use them from my own fixture with scope='session'. Is there a way to workaround this? And if not then what would be a proper approach for achieving my goal without using built-in fixtures?
My code is as follows (conftest.py)
#pytest.fixture()
def client(test_client, loop):
app = init(loop)
return loop.run_until_complete(test_client(app))
My tests then use this
class TestGetAlerts:
async def test_get_login_url(self, client):
resp = await client.get('/api/get_login_url')
assert resp.status == 200
So my fixture 'client' runs for every test, which is what i want to avoid
test_client fixture is a simple wrapper around TestServer and TestClient classes from aiohttp.test_utils.
You can craft your own version of the fixture with 'session' scope.
But this way has own problems: tests should be isolated, in practice it means event loop recreation for every test.
But session-level aiohttp application doesn't support such loop recreation. Thus the app should be run in separate thread which makes writing test assertions much harder.
In my practice aiohttp application starts instantly but things like DB schema migration and DB fixtures applying takes time. These activities could be implemented in session scope easy as separate fixtures but app starting/stopping should be performed inside every test.

Mocking decorators in python with mock and pytest

How to patch my custom decorator with monkeypatch or pytest.mock? i manage to mock it by doing (answer to this question):
package.decorator = mytestdecorator
The problem is that it breaks some other tests where i actually need that decorator to work.
You have to control complete lifecycle of your mocked decorator and revert the decorator back to original state.
It can be done in few different ways:
context manager which builds the mocked decorator, and reverts it back by __exit__.
setup and teardown functions for your test, teardown must revert the decorator.
pytest fixture with finalizer
pytest fixture with yield expression.
Personally I like the #pytest.yield_fixture as it keeps the code short and as soon as you realize, that all what comes after yield statement in the fixture code is cleanup code, things are very clear to do.

Why MySQL drivers/clients like MySQLdb or mysqlclient or PyMySQL considered Blocking even when used in Async Web Frameworks like Tornado?

In async web frameworks like Tornado, we can do non-blocking calls using its #gen.coroutine or #tornado.web.asynchronous decorators. For example we can do non-blocking or async calls by using the AsyncHTTPClient module.
So we can use it either by using #gen.coroutine decorator along with the "yield" keyword, like "yield tornado.httpclient.AsyncHTTPClient().fetch(url)" or by using a callback function with #tornado.web.asynchronous. For both of the methods, it works as non-blocking.
Similarly, then why can't it be done the same way for Python's MySQL drivers/clients like MySQLdb, PyMySQL, mysqlclient etc. I mean why can't these drivers be used similarly to how AsyncHTTPClient is used along with either gen.coroutine or callback via web.asynchronous decorators. Why can't the drivers be used with gen.coroutine or web.asynchronous to make the calls non-blocking?
What are the reasons that stop them from being asynchronous, I mean how AsyncHTTPClient is non-blocking but MySQLdb.connect, MySQLdb.connect().cursor.query(), etc are not?
Also I know there are few async Tornado specific MySQL drivers/clients like TorMySQL, Tornado-MySQL (by PyMySQL), asynctorndb, etc. But why can't the normal drivers/clients like mysqlclient or MySQLdb be used directly as non-blocking, or what are the changes made in TorMySQL, Tornado-MySQL (by PyMySQL) that they are defined as async mysql drivers/clients for Tornado?
When we say that Tornado is an asynchronous framework, this is a requirement rather than an effect. To use Tornado effectively, anything you do that is expensive needs to be asynchronous; Tornado cannot magically make synchronous code asynchronous. This means you need to either choose asynchronous libraries which are compatible with Tornado, or run expensive blocking operations in another thread.
See the introduction to asynchronous I/O in the Tornado User's guide for more details.

Categories