I'm trying to make the Gino mock, but I keep seeing the error
gino.exceptions.UninitializedError: Gino engine is not initialized.
My code is formed like this:
# __init__.py
#functools.lru_cache
def get_db_service():
db = Gino(dsn=settings.get_settings().postgresql_conn_url)
return db
# model
_db = get_db_service()
class EdaTableInstance(_db.Model):
__tablename__ = "eda_table_instance"
#...
#classmethod
async def get_all(cls) -> List['EdaTableInstance']:
async with _db.acquire():
return await EdaTableInstance.query.gino.all()
How I'm writing the tests (various attempts):
# conftest.py
#pytest.fixture(autouse=True)
def mock_get_db_service(mocker):
db = Gino(dsn="sqlite//:memory:")
async_mock = AsyncMock(db)
mocker.patch("gateway_api.services.get_db_service", return_value=async_mock)
yield
or
# conftest.py
#pytest.fixture
async def db_initialize():
await db.set_bind('sqlite:///:memory:')
await db.gino.create_all()
await EdaTableInstance.create_eda_table_instance(
EdaTableInstanceInputOnCreate({"name":"table_server1", "host":"123.123.123.123"})
)
yield
or
# test_models.py
#pytest.fixture
def mock_gino_get_all(mocker):
mocker.patch("gino.api.GinoExecutor.all", return_value=[])
#pytest.mark.asyncio
#pytest.mark.parametrize("id, expected", [(None, None)])
async def test_01_table_instance_get_all(id, expected):
mock_cursor = MagicMock()
mock_cursor.configure_mock(
**{
"get_one.return_value":[id]
}
)
res = await EdaTableInstance().get_one(mock_cursor)
assert res == expected
I would like to use SQLite in memory, so I don't have to connect from a database, if you know better methods to mock the database, it would help me so much.
Related
I currently have the following code for my classification model server. The classifier is passed as a dependency to the index (/) function.
# classifier.py
import asyncio
import httpx
class Classifier():
def __init__(
self,
concurrency_limit,
) -> None:
self.client = httpx.AsyncClient()
self.semaphore = asyncio.Semaphore(concurrency_limit)
async def download_async(self, url):
async with self.semaphore:
response = await self.client.get(url)
return await response.aread()
async def run(
self, image_urls
):
image_list = await asyncio.gather(
*[self.download_async(url) for i, url in enumerate(image_urls)]
)
# Infer Images
pass
# api.py
async def classifier_dependency() -> Classifier:
return Classifier(
concurrency_limit=constants.CONCURRENCY_LIMIT,
)
#server.post("/")
async def index(
data, classifier = Depends(classifier_dependency)
) -> Dict:
results = await classifier.run(data.images)
I am trying to write tests for the API which can be run offline. I basically want to much the response from httpx.get(). Here is what I am currently doing.
# test_api.py
class AsyncMock(MagicMock): # Not needed if using Python 3.8
async def __call__(self, *args, **kwargs):
return super(AsyncMock, self).__call__(*args, **kwargs)
def update_mock_dependency(image_bytes):
response = AsyncMock(name="Response")
response.aread.return_value = image_bytes
async def override_dependency():
classifier = Classifier(
concurrency_limit=constants.CONCURRENCY_LIMIT,
)
async def f(_):
return response
classifier.client.get = f
return classifier
server.dependency_overrides[classifier_dependency] = override_dependency
def test_successful_inference(image_bytes, image_urls):
"""
Test that the model output is similar to the expected output.
"""
update_mock_dependency(image_bytes)
data = {"images": image_urls}
response = client.post("/", json=data)
assert response.status_code == 200,
I'm not sure how to go about it right now in a clean way. Is there a better alternative using mock.patch instead of manually overriding the httpx.get function?
I am not able to run this test, i always have the same error
RuntimeError: Event loop is closed
What i need to add to this code?
from motor.motor_asyncio import AsyncIOMotorClient
import pytest
import asyncio
client = AsyncIOMotorClient("mongodb://mongo:mongo#192.168.0.11:27017/admin?retryWrites=false")
db = client['app']
aux = db['users']
async def create_user_db(a: dict):
x = await aux.insert_one(a)
return x
#pytest.mark.asyncio
async def test_create():
form = {'username': 'c3', 'password': 'c3'}
res = await create_user_db(form)
assert res != None
This is the error
In your example, your opening the database during "import" time, but we still have no eventloop. The event loop is created when the test case runs.
You could define your database as fixture and provide it to the testing functions, e.g.:
#pytest.fixture
def client():
return AsyncIOMotorClient("mongodb://localhost:27017/")
#pytest.fixture
def db(client):
return client['test']
#pytest.fixture
def collection(db):
return db['test']
async def create_user_db(collection, a: dict):
x = await collection.insert_one(a)
return x
#pytest.mark.asyncio
async def test_create(collection):
form = {'username': 'c3', 'password': 'c3'}
res = await create_user_db(collection, form)
assert res != None
What's wrong with implementation of passing class instance as a dependency in FastAPI router or is it a bug?
1) I have defined router with dependency
app = FastAPI()
dbconnector_is = AsyncDBPool(conn=is_cnx, loop=None)
app.include_router(test_route.router, dependencies=[Depends(dbconnector_is)])
#app.on_event('startup')
async def startup():
app.logger = await AsyncLogger().getlogger(log)
await app.logger.warning('Webservice is starting up...')
await app.logger.info("Establishing RDBS Integration Pool Connection...")
await dbconnector_is.connect()
#app.on_event('startup')
async def startup():
await app.logger.getlogger(log)
await app.logger.warning('Webservice is starting up...')
await dbconnector_is.connect()
router
#router.get('/test')
async def test():
data = await dbconnector_is.callproc('is_processes_get', rows=-1, values=[None, None])
return Response(json.dumps(data, default=str))
custom class for passing it's instance as callable.
class AsyncDBPool:
def __init__(self, conn: dict, loop: None):
self.conn = conn
self.pool = None
self.connected = False
self.loop = loop
def __call__(self):
return self
async def connect(self):
while self.pool is None:
try:
self.pool = await aiomysql.create_pool(**self.conn, loop=self.loop, autocommit=True)
except aiomysql.OperationalError as e:
await asyncio.sleep(1)
continue
else:
return self.pool
And when I send request I receive this error.
data = await dbconnector_is.callproc('is_processes_get', rows=-1, values=[None, None])
NameError: name 'dbconnector_is' is not defined
I want to mock the json() coroutine from the aiohttp.ClientSession.get method. It looks to return an async generator object, which is where I'm confused on how to mock in my example. Here is my code:
async def get_access_token():
async with aiohttp.ClientSession(auth=auth_credentials) as client:
async with client.get(auth_path, params={'grant_type': 'client_credentials'}) as auth_response:
assert auth_response.status == 200
auth_json = await auth_response.json()
return auth_json['access_token']
This is my test case to mock the get method:
json_data = [{
'access_token': 'HSG9hsf328bJSWO82sl',
'expires_in': 86399,
'token_type': 'bearer'
}]
class AsyncMock:
async def __aenter__(self):
return self
async def __aexit__(self, *error_info):
return self
#pytest.mark.asyncio
async def test_wow_api_invalid_credentials(monkeypatch, mocker):
def mock_client_get(self, auth_path, params):
mock_response = AsyncMock()
mock_response.status = 200
mock_response.json = mocker.MagicMock(return_value=json_data)
return mock_response
monkeypatch.setattr('wow.aiohttp.ClientSession.get', mock_client_get)
result = await wow.get_access_token()
assert result == 'HSG9hsf328bJSWO82sl'
I think the problem might be that mock_response.json() is not awaitable. In my example I can't call await from a non async function so I'm confused on how I would do that. I would like to keep the test libraries to a minimum which is pytest and pytest-asyncio for the learning experiencing and to rely less on 3rd party libraries.
I was making it more complicated than it needed to be. I simply defined json as an awaitable attribute of AsyncMock which returns the json_data. The complete code looks like this:
json_data = {
'access_token': 'HSG9hsf328bJSWO82sl',
'expires_in': 86399,
'token_type': 'bearer'
}
class AsyncMock:
async def __aenter__(self):
return self
async def __aexit__(self, *error_info):
return self
async def json(self):
return json_data
#pytest.mark.asyncio
async def test_wow_api_invalid_credentials(monkeypatch):
def mock_client_get(self, auth_path, params):
mock_response = AsyncMock()
mock_response.status = 200
return mock_response
monkeypatch.setattr('wow.aiohttp.ClientSession.get', mock_client_get)
result = await wow.get_access_token()
assert result == 'HSG9hsf328bJSWO82sl'
This is part 1, but i suggest you watch part2.
Im not sure i understand your question totally, because using async def or #asyncio.coroutine can help you do this. Actually, i want to write it as comment, however there are so many differences that i can't put it into comment.
import asyncio
json_ = [{
'access_token': 'HSG9hsf328bJSWO82sl',
'expires_in': 86399,
'token_type': 'bearer'
}]
async def response_from_sun():
return json_
class AsyncMock:
async def specify(self):
return self.json[0].get("access_token")
async def __aenter__(self):
return self
async def __aexit__(self, *error_info):
return self
async def mock_client_get():
mock_response = AsyncMock()
mock_response.status = 200
mock_response.json = await response_from_sun()
return mock_response
async def go():
resp = await mock_client_get()
result = await resp.specify()
assert result == 'HSG9hsf328bJSWO82sl'
asyncio.get_event_loop().run_until_complete(go())
PART2
After adding my answer, i found there is a problem about your mock_response content. Becausemock_response does not contain variable and function which ClientResponse have.
Edit: I try many times and watch ClientSession's code, then i found you can specify a new response class by its parameter. Note: connector=aiohttp.TCPConnector(verify_ssl=False) is unnecessary
import asyncio
import aiohttp
class Mock(aiohttp.ClientResponse):
print("Mock")
async def specify(self):
json_ = (await self.json()).get("hello")
return json_
async def go():
async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(verify_ssl=False),response_class=Mock) as session:
resp = await session.get("https://www.mocky.io/v2/5185415ba171ea3a00704eed")
result = await resp.specify()
print(result)
assert result == 'world'
asyncio.get_event_loop().run_until_complete(go())
The toy script shows an application using a class that is dependent on an implementation that is not asyncio-aware, and obviously doesn't work.
How would the fetch method of MyFetcher be implemented, using the asyncio-aware client, while still maintaining the contract with the _internal_validator method of FetcherApp? To be very clear, FetcherApp and AbstractFetcher cannot be modified.
To use async fetch_data function inside fetch both fetch and is_fetched_data_valid functions should be async too. You can change them in child classes without modify parent:
import asyncio
class AsyncFetcherApp(FetcherApp):
async def is_fetched_data_valid(self): # async here
data = await self.fetcher_implementation.fetch() # await here
return self._internal_validator(data)
class AsyncMyFetcher(AbstractFetcher):
def __init__(self, client):
super().__init__()
self.client = client
async def fetch(self): # async here
result = await self.client.fetch_data() # await here
return result
class AsyncClient:
async def fetch_data(self):
await asyncio.sleep(1) # Just to sure it works
return 1
async def main():
async_client = AsyncClient()
my_fetcher = AsyncMyFetcher(async_client)
fetcherApp = AsyncFetcherApp(my_fetcher)
# ...
is_valid = await fetcherApp.is_fetched_data_valid() # await here
print(repr(is_valid))
if __name__ == "__main__":
loop = asyncio.get_event_loop()
loop.run_until_complete(main())