How to use coroutine as a pytest fixture? - python

Is it possible to write pytest fixtures as tornado coroutines? For example, I want to write a fixture for creating a db, like this:
from tornado import gen
import pytest
#pytest.fixture
#gen.coroutine
def get_db_connection():
# set up
db_name = yield create_db()
connection = yield connect_to_db(db_name)
yield connection
# tear down
yield drop_db(db_name)
#pytest.mark.gen_test
def test_something(get_db_connection):
# some tests
Obviously, this fixture does not work as expected, as it is called as a function, not as coroutine. Is there a way to fix it?

After some research, I came out with this solution:
from functools import partial
from tornado import gen
import pytest
#pytest.fixture
def get_db_connection(request, io_loop): # io_loop is a fixture from pytest-tornado
def fabric():
#gen.coroutine
def set_up():
db_name = yield create_db()
connection = yield connect_to_db(db_name)
raise gen.Return(connection)
#gen.coroutine
def tear_down():
yield drop_db(db_name)
request.addfinalizer(partial(io_loop.run_sync, tear_down))
connection = io_loop.run_sync(set_up)
return connection
return fabric
#pytest.mark.gen_test
def test_something(get_db_connection):
connection = get_db_connection() # note brackets
I'm sure, that it can be done cleaner using some pylint magic.
I found this slides very useful.
EDIT: I found out that above method has a limitation. You can't change scope of get_db_connection fixture, as it uses io_loop fixture with scope "function".

Related

How to use dependencies with yield in FastApi

I am using FastApi and I would like to know if I am using the dependencies correctly.
First, I have a function that yields the database session.
class ContextManager:
def __init__(self):
self.db = DBSession()
def __enter__(self):
return self.db
def __exit__(self):
self.db.close()
def get_db():
with ContextManager() as db:
yield db
I would like to use that function in another function:
def validate(db=Depends(get_db)):
is_valid = verify(db)
if not is is_valid:
raise HTTPException(status_code=400)
yield db
Finally, I would like to use the last functions as a dependency on the routes:
#router.get('/')
def get_data(db=Depends(validate)):
data = db.query(...)
return data
I am using this code and it seems to work, but I would like to know if it is the most appropiate way to use dependencies. Especially, I am not sure if I have to use 'yield db' inside the function validate or it would be better to use return. I would appreciate your help. Thanks a lot

Pytest class-instance level fixture inside parametrized class

I have a following simplified code example:
from urllib.parse import urlparse, parse_qs
from selenium import webdriver
import pytest
#pytest.fixture(scope="module")
def driver():
options = webdriver.ChromeOptions()
_driver = webdriver.Chrome(options=options)
yield _driver
_driver.close()
#pytest.mark.parametrize("instance_name", ["instance1", "instance2"])
class TestInstance:
#pytest.fixture
def authorization_code(self, instance_name, driver):
driver.get(f"https://{instance_name}.com")
...do some UI actions here
authorization_code = parse_qs(urlparse(redirected_url).query)["code"][0]
#pytest.fixture
def access_token(self, authorization_code):
...obtain access_token here using authorization code
return "access_token"
def test_case_1(self, access_token):
...do some API calls using access_token
def test_case_2(self, access_token):
...do some API calls using access_token
What I would like to do is to execute UI actions in the authorization_code function once and obtain one access_token per instance.
Currently my UI actions are executed for every test case, leading to the fact that UI actions actually execute 2 * 2 = 4 times.
Is it possible to do with pytest?
Or maybe I am missing something in my setup?
In general I would just change the fixture scope: currently it gets recreated every time it is called, hence the reuse of ui actions. This is by design to ensure fixtures are clean. If your fixture didn't depend on the function-level fixture instance you could just put scope="class". (See the docs on scopes).
In this case I'd be tempted to handle the caching myself:
import pytest
from datetime import datetime
#pytest.mark.parametrize("instance", ("instance1", "instance2"))
class TestClass:
#pytest.fixture()
def authcode(self, instance, authcodes={}, which=[0]):
if not instance in authcodes:
authcodes[
instance
] = f"authcode {which[0]} for {instance} at {datetime.now()}"
which[0] += 1
return authcodes[instance]
def test1(self, authcode):
print(authcode)
def test2(self, authcode):
print(authcode)
(which is just used to prove that we don't regenerate the fixture).
This feels inelegant and I'm open to better ways of doing it.

Run a subcommand inside a context manager

In the context of a python click CLI application, I would like to run a subcommand inside of a context manager that would be setup in a higher level command. How is it possible to do that with click? My pseudo-code looks something like:
import click
from contextlib import contextmanager
#contextmanager
def database_context(db_url):
try:
print(f'setup db connection: {db_url}')
yield
finally:
print('teardown db connection')
#click.group
#click.option('--db',default='local')
def main(db):
print(f'running command against {db} database')
db_url = get_db_url(db)
connection_manager = database_context(db_url)
# here come the mysterious part that makes all subcommands
# run inside the connection manager
#main.command
def do_this_thing()
print('doing this thing')
#main.command
def do_that_thing()
print('doing that thing')
And this would be called like:
> that_cli do_that_thing
running command against local database
setup db connection: db://user:pass#localdb:db_name
doing that thing
teardown db connection
> that_cli --db staging do_this_thing
running command against staging database
setup db connection: db://user:pass#123.456.123.789:db_name
doing this thing
teardown db connection
Edit: note that the above example is forged to better illustrate the missing functionality of click, not that I want to solve this problem in particular. I know I could repeat the same code in all commands and achieve the same effect, which I already do in my real use case. My question is precisely on what could I do only in the main function, that would run all transparently subcommands in a context manager.
Decorating commands
Define a context manager decorator using contextlib.ContextDecorator
Use click.pass_context decorator on main(), so you can explore click context
Create an instance db_context of the context manager
Iterate on commands defined for group main using ctx.command.commands
For each command, replace the original callback (function called by the command) with the same callback decorated with the context manager db_context(cmd)
This way you will programmatically modify each command to behave just like:
#main.command()
#db_context
def do_this_thing():
print('doing this thing')
But without requiring to change your code beyond your function main().
See the code below for a working example:
import click
from contextlib import ContextDecorator
class Database_context(ContextDecorator):
"""Decorator context manager."""
def __init__(self, db_url):
self.db_url = db_url
def __enter__(self):
print(f'setup db connection: {self.db_url}')
def __exit__(self, type, value, traceback):
print('teardown db connection')
#click.group()
#click.option('--db', default='local')
#click.pass_context
def main(ctx, db):
print(f'running command against {db} database')
db_url = db # get_db_url(db)
# here come the mysterious part that makes all subcommands
# run inside the connection manager
db_context = Database_context(db_url) # Init context manager decorator
for name, cmd in ctx.command.commands.items(): # Iterate over main.commands
cmd.allow_extra_args = True # Seems to be required, not sure why
cmd.callback = db_context(cmd.callback) # Decorate command callback with context manager
#main.command()
def do_this_thing():
print('doing this thing')
#main.command()
def do_that_thing():
print('doing that thing')
if __name__ == "__main__":
main()
It does what you describe in your question, hope it will work as expected in real code.
Using click.pass_context
This code below will give you an idea of how to do it using click.pass_context.
import click
from contextlib import contextmanager
#contextmanager
def database_context(db_url):
try:
print(f'setup db connection: {db_url}')
yield
finally:
print('teardown db connection')
#click.group()
#click.option('--db',default='local')
#click.pass_context
def main(ctx, db):
ctx.ensure_object(dict)
print(f'running command against {db} database')
db_url = db #get_db_url(db)
# Initiate context manager
ctx.obj['context'] = database_context(db_url)
#main.command()
#click.pass_context
def do_this_thing(ctx):
with ctx.obj['context']:
print('doing this thing')
#main.command()
#click.pass_context
def do_that_thing(ctx):
with ctx.obj['context']:
print('doing that thing')
if __name__ == "__main__":
main(obj={})
Another solution to avoid explicit with statement could be passing the context manager as a decorator using contextlib.ContextDecorator, but it would likely be more complex to setup with click.
This use case is supported natively in Click from v8.0 by using
ctx.with_resource(context_manager)
https://click.palletsprojects.com/en/8.0.x/api/#click.Context.with_resource
There is a worked example in the Click advanced documentation
https://click.palletsprojects.com/en/8.0.x/advanced/#managing-resources

Combining py.test and trio/curio

I would to combine pytest and trio (or curio, if that is any easier), i.e. write my test cases as coroutine functions. This is relatively easy to achieve by declaring a custom test runner in conftest.py:
#pytest.mark.tryfirst
def pytest_pyfunc_call(pyfuncitem):
'''If item is a coroutine function, run it under trio'''
if not inspect.iscoroutinefunction(pyfuncitem.obj):
return
kernel = trio.Kernel()
funcargs = pyfuncitem.funcargs
testargs = {arg: funcargs[arg]
for arg in pyfuncitem._fixtureinfo.argnames}
try:
kernel.run(functools.partial(pyfuncitem.obj, **testargs))
finally:
kernel.run(shutdown=True)
return True
This allows me to write test cases like this:
async def test_something():
server = MockServer()
server_task = await trio.run(server.serve)
try:
# test the server
finally:
server.please_terminate()
try:
with trio.fail_after(30):
server_task.join()
except TooSlowError:
server_task.cancel()
But this is a lot of boilerplate. In non-async code, I would factor this out into a fixture:
#pytest.yield_fixture()
def mock_server():
server = MockServer()
thread = threading.Thread(server.serve)
thread.start()
try:
yield server
finally:
server.please_terminate()
thread.join()
server.server_close()
def test_something(mock_server):
# do the test..
Is there a way to do the same in trio, i.e. implement async fixtures? Ideally, I would just write:
async def test_something(mock_server):
# do the test..
Edit: the answer below is mostly irrelevant now – instead use pytest-trio and follow the instructions in its manual.
Your example pytest_pyfunc_call code doesn't work becaues it's a mix of trio and curio :-). For trio, there's a decorator trio.testing.trio_test that can be used to mark individual tests (like if you were using classic unittest or something), so the simplest way to write a pytest plugin function is to just apply this to each async test:
from trio.testing import trio_test
#pytest.mark.tryfirst
def pytest_pyfunc_call(pyfuncitem):
if inspect.iscoroutine(pyfuncitem.obj):
# Apply the #trio_test decorator
pyfuncitem.obj = trio_test(pyfuncitem.obj)
In case you're curious, this is basically equivalent to:
import trio
from functools import wraps, partial
#pytest.mark.tryfirst
def pytest_pyfunc_call(pyfuncitem):
if inspect.iscoroutine(pyfuncitem.obj):
fn = pyfuncitem.obj
#wraps(fn)
def wrapper(**kwargs):
trio.run(partial(fn, **kwargs))
pyfuncitem.obj = wrapper
Anyway, that doesn't solve your problem with fixtures – for that you need something much more involved.

Can't mock the class method in python

I have a class that I try to mock in tests. The class is located in server/cache.py and looks like:
class Storage(object):
def __init__(self, host, port):
# set up connection to a storage engine
def store_element(self, element, num_of_seconds):
# store something
def remove_element(self, element):
# remove something
This class is used in server/app.py similar to this one:
import cache
STORAGE = cache.Storage('host', 'port')
STORAGE.store_element(1, 5)
Now the problem arise when I try to mock it in the tests:
import unittest, mock
import server.app as application
class SomeTest(unittest.TestCase):
# part1
def setUp(self):
# part2
self.app = application.app.test_client()
This clearly does not work during the test, if I can't connect to a storage. So I have to mock it somehow by writing things in 'part1, part2'.
I tried to achieve it with
#mock.patch('server.app.cache') # part 1
mock.side_effect = ... # hoping to overwriting the init function to do nothing
But it still tries to connect to a real host. So how can I mock a full class here correctly? P.S. I reviewed many many questions which look similar to me, but in vain.

Categories