I created a simple library project in microservices to study and implement FastAPI.
Docker starts 5 main services:
.
โโโ author-service
โ โโโ app
โ โ โโโ api
โ โ โโโ main.py
โ โ โโโ __pycache__
โ โโโ Dockerfile
โ โโโ requirements.txt
โ โโโ tests
โ โโโ __init__.py
โ โโโ __pycache__
โ โโโ test_author.py
โโโ book-service
โ โโโ app
โ โ โโโ api
โ โ โโโ __init__.py
โ โ โโโ main.py
โ โ โโโ __pycache__
โ โโโ Dockerfile
โ โโโ requirements.txt
โ โโโ tests
โ โโโ __init__.py
โ โโโ __pycache__
โ โโโ test_book.py
โโโ docker-compose.yml
โโโ nginx_config.conf
โโโ README.md
โโโ requirements.txt
I added a test directory where I test endpoints.
from starlette.testclient import TestClient
from app.main import app
from app.api.author import authors
import logging
log = logging.getLogger('__name__')
import requests
client = TestClient(app)
def test_get_authors():
response = client.get("/")
assert response.status_code == 200
def test_get_author():
response = client.get("/1")
assert response.status_code == 200
$> docker-compose exec author_service pytest .
returns this
============================================================================================================= test session starts =============================================================================================================
platform linux -- Python 3.8.3, pytest-5.3.2, py-1.9.0, pluggy-0.13.1
rootdir: /app
collected 2 items
tests/test_author.py FF [100%]
================================================================================================================== FAILURES ===================================================================================================================
______________________________________________________________________________________________________________ test_get_authors _______________________________________________________________________________________________________________
def test_get_authors():
response = client.get("/")
> assert response.status_code == 200
E assert 404 == 200
E + where 404 = <Response [404]>.status_code
tests/test_author.py:12: AssertionError
_______________________________________________________________________________________________________________ test_get_author _______________________________________________________________________________________________________________
def test_get_author():
response = client.get("/1")
> assert response.status_code == 200
E assert 404 == 200
E + where 404 = <Response [404]>.status_code
tests/test_author.py:16: AssertionError
============================================================================================================== 2 failed in 0.35s ==============================================================================================================
I tried to start the tests directly from the container shell but nothing the same.
This problem occurs only with tests that are done following the documentation (using starlette / fastapi) and with requests
I would like to add another thing, following the FastAPI documentation there is written to import
from fastapi.testclient import TestClient
This gives me the following error
============================================================================================================= test session starts =============================================================================================================
platform linux -- Python 3.8.3, pytest-5.3.2, py-1.9.0, pluggy-0.13.1
rootdir: /app
collected 0 items / 1 error
=================================================================================================================== ERRORS ====================================================================================================================
____________________________________________________________________________________________________ ERROR collecting tests/test_author.py ____________________________________________________________________________________________________
ImportError while importing test module '/app/tests/test_author.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
tests/test_author.py:1: in <module>
from fastapi.testclient import TestClient
E ModuleNotFoundError: No module named 'fastapi.testclient'
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
============================================================================================================== 1 error in 0.19s ===============================================================================================================
You can find the complete project here
Library Microsrevices example
@Plaoo its not fastapi issues, it's the way the imports work, hope this link helps: https://docs.pytest.org/en/stable/pythonpath.html
Some points:
docker exec library-microservices-example_author_service_1 pip freezeapi/v1/authors/, then you need to have this in your request on the tests.I didn't check more than that.
* You need to use the full URL in the tests, if your prefix is `api/v1/authors/`, then you need to have this in your request on the tests.I didn't check more than that.
Made an update of FastAPI now is fastapi==0.58.1, I also tried to add full url, what I use with postman this is the result:
============================================================================================================= test session starts =============================================================================================================
platform linux -- Python 3.8.3, pytest-5.3.2, py-1.9.0, pluggy-0.13.1
rootdir: /app
collected 2 items
tests/test_author.py FF [100%]
================================================================================================================== FAILURES ===================================================================================================================
______________________________________________________________________________________________________________ test_get_authors _______________________________________________________________________________________________________________
def test_get_authors():
> response = client.get("http://0.0.0.0:8080/api/v1/authors/")
tests/test_author.py:11:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/usr/local/lib/python3.8/site-packages/requests/sessions.py:543: in get
return self.request('GET', url, **kwargs)
/usr/local/lib/python3.8/site-packages/starlette/testclient.py:413: in request
return super().request(
/usr/local/lib/python3.8/site-packages/requests/sessions.py:530: in request
resp = self.send(prep, **send_kwargs)
/usr/local/lib/python3.8/site-packages/requests/sessions.py:643: in send
r = adapter.send(request, **kwargs)
/usr/local/lib/python3.8/site-packages/starlette/testclient.py:243: in send
raise exc from None
/usr/local/lib/python3.8/site-packages/starlette/testclient.py:240: in send
loop.run_until_complete(self.app(scope, receive, send))
/usr/local/lib/python3.8/asyncio/base_events.py:616: in run_until_complete
return future.result()
/usr/local/lib/python3.8/site-packages/fastapi/applications.py:171: in __call__
await super().__call__(scope, receive, send)
/usr/local/lib/python3.8/site-packages/starlette/applications.py:102: in __call__
await self.middleware_stack(scope, receive, send)
/usr/local/lib/python3.8/site-packages/starlette/middleware/errors.py:181: in __call__
raise exc from None
/usr/local/lib/python3.8/site-packages/starlette/middleware/errors.py:159: in __call__
await self.app(scope, receive, _send)
/usr/local/lib/python3.8/site-packages/starlette/exceptions.py:82: in __call__
raise exc from None
/usr/local/lib/python3.8/site-packages/starlette/exceptions.py:71: in __call__
await self.app(scope, receive, sender)
/usr/local/lib/python3.8/site-packages/starlette/routing.py:550: in __call__
await route.handle(scope, receive, send)
/usr/local/lib/python3.8/site-packages/starlette/routing.py:227: in handle
await self.app(scope, receive, send)
/usr/local/lib/python3.8/site-packages/starlette/routing.py:41: in app
response = await func(request)
/usr/local/lib/python3.8/site-packages/fastapi/routing.py:196: in app
raw_response = await run_endpoint_function(
/usr/local/lib/python3.8/site-packages/fastapi/routing.py:147: in run_endpoint_function
return await dependant.call(**values)
app/api/author.py:10: in get_authors
return await db_manager.get_all_authors()
app/api/db_manager.py:12: in get_all_authors
return await database.fetch_all(query=query)
/usr/local/lib/python3.8/site-packages/databases/core.py:130: in fetch_all
async with self.connection() as connection:
/usr/local/lib/python3.8/site-packages/databases/core.py:199: in __aenter__
await self._connection.acquire()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <databases.backends.postgres.PostgresConnection object at 0x7f9a4eb2bf40>
async def acquire(self) -> None:
assert self._connection is None, "Connection is already acquired"
> assert self._database._pool is not None, "DatabaseBackend is not running"
E AssertionError: DatabaseBackend is not running
/usr/local/lib/python3.8/site-packages/databases/backends/postgres.py:132: AssertionError
_______________________________________________________________________________________________________________ test_get_author _______________________________________________________________________________________________________________
def test_get_author():
> response = client.get("http://0.0.0.0:8080/api/v1/authors/1")
tests/test_author.py:15:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/usr/local/lib/python3.8/site-packages/requests/sessions.py:543: in get
return self.request('GET', url, **kwargs)
/usr/local/lib/python3.8/site-packages/starlette/testclient.py:413: in request
return super().request(
/usr/local/lib/python3.8/site-packages/requests/sessions.py:530: in request
resp = self.send(prep, **send_kwargs)
/usr/local/lib/python3.8/site-packages/requests/sessions.py:643: in send
r = adapter.send(request, **kwargs)
/usr/local/lib/python3.8/site-packages/starlette/testclient.py:243: in send
raise exc from None
/usr/local/lib/python3.8/site-packages/starlette/testclient.py:240: in send
loop.run_until_complete(self.app(scope, receive, send))
/usr/local/lib/python3.8/asyncio/base_events.py:616: in run_until_complete
return future.result()
/usr/local/lib/python3.8/site-packages/fastapi/applications.py:171: in __call__
await super().__call__(scope, receive, send)
/usr/local/lib/python3.8/site-packages/starlette/applications.py:102: in __call__
await self.middleware_stack(scope, receive, send)
/usr/local/lib/python3.8/site-packages/starlette/middleware/errors.py:181: in __call__
raise exc from None
/usr/local/lib/python3.8/site-packages/starlette/middleware/errors.py:159: in __call__
await self.app(scope, receive, _send)
/usr/local/lib/python3.8/site-packages/starlette/exceptions.py:82: in __call__
raise exc from None
/usr/local/lib/python3.8/site-packages/starlette/exceptions.py:71: in __call__
await self.app(scope, receive, sender)
/usr/local/lib/python3.8/site-packages/starlette/routing.py:550: in __call__
await route.handle(scope, receive, send)
/usr/local/lib/python3.8/site-packages/starlette/routing.py:227: in handle
await self.app(scope, receive, send)
/usr/local/lib/python3.8/site-packages/starlette/routing.py:41: in app
response = await func(request)
/usr/local/lib/python3.8/site-packages/fastapi/routing.py:196: in app
raw_response = await run_endpoint_function(
/usr/local/lib/python3.8/site-packages/fastapi/routing.py:147: in run_endpoint_function
return await dependant.call(**values)
app/api/author.py:20: in get_author
author = await db_manager.get_author(id)
app/api/db_manager.py:17: in get_author
return await database.fetch_one(query=query)
/usr/local/lib/python3.8/site-packages/databases/core.py:136: in fetch_one
async with self.connection() as connection:
/usr/local/lib/python3.8/site-packages/databases/core.py:199: in __aenter__
await self._connection.acquire()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <databases.backends.postgres.PostgresConnection object at 0x7f9a4e510dc0>
async def acquire(self) -> None:
assert self._connection is None, "Connection is already acquired"
> assert self._database._pool is not None, "DatabaseBackend is not running"
E AssertionError: DatabaseBackend is not running
/usr/local/lib/python3.8/site-packages/databases/backends/postgres.py:132: AssertionError
============================================================================================================== 2 failed in 0.71s ==============================================================================================================
If this issue is not directly related to FastAPI, please let me know that I will close immediately. Or recommend a site to talk about it.
By full url kludex meant this:
def test_get_authors():
response = client.get('/api/v1/authors/')
assert response.status_code == 200
def test_get_author():
response = client.get('/api/v1/authors/1')
assert response.status_code == 200
The url is like that because you add a prefix to this router in main.py
app.include_router(authors, prefix='/api/v1/authors', tags=['authors'])
You can add a http://0.0.0.0:8000 if you want but fastapi auto adds it (not sure if that's exactly the case if someone more competent can confirm i would be happy ๐ )
def test_get_authors():
response = client.get('http://0.0.0.0:8000/api/v1/authors/')
assert response.status_code == 200
def test_get_author():
response = client.get('http://0.0.0.0:8000/api/v1/authors/1')
assert response.status_code == 200
Thanks for the help here everyone! :clap: :bow:
If that solves the original problem, then you can close this issue :heavy_check_mark:
Otherwise:
E ModuleNotFoundError: No module named 'fastapi.testclient'
From the URL: client.get("http://0.0.0.0:8080/api/v1/authors/"), that would work to communicate from outside of the container to the a mapped port. Probably not from inside the container. But especially, if you are using the TestClient you shouldn't add the http://0.0.0.0:8080, as @Kludex and @ArcLightSlavik mention.
If you just started the stack, the DB is probably not ready yet for the time you run the tests. You would have to wait for the DB to be ready or use something like tenacity.
Thanks to all of the answers. I was able to start the tests using simply requests and the ip address of the docker0 network 172.17.0.1. I can close the issue, thanks again even if a little OT