I am developing a service with FastAPI and Tortoise-ORM.
When I use the interface generated by Swagger UI or curl, I can add and read the data successfully.
However, when I run pytest, tests fail with the following error message: tortoise.exceptions.ConfigurationError: No DB associated to model
Bearing in mind that the error only occurs when pytest is used, I believe that the problem is some configuration that is wrong or is missing from the test scripts, but I can't find the cause.
Does anyone have any ideas ?
My structure is as follows:
src /
+--api /
| +-__ init__.py
| +-app.py
| +-main.py
| +-models.py
| +-routers.py
| +-schemas.py
+--tests /
+-__ init__.py
+-test_subjects.py
The test_1.py file is as follows:
import pytest
from fastapi.testclient import TestClient
from api.main import app
client = TestClient(app)
def test_create_subject():
response = await client.post(
'/api/subject/',
json={
'name': 'Programming',
},
)
def test_read_subjects():
response = client.get("/api/subjects/")
assert response.status_code == 200
app.py:
from fastapi import FastAPI
from tortoise.contrib.fastapi import register_tortoise
from tortoise import Tortoise
def get_application():
_app = FastAPI(title='MyProject')
_app.add_middleware(
CORSMiddleware,
allow_credentials=True,
allow_methods=['*'],
allow_headers=['*'],
)
return _app
app = get_application()
#app.on_event('startup')
async def startup():
register_tortoise(
app,
db_url='sqlite://db.sqlite3',
modules={'models': ['api.models']},
generate_schemas=True,
add_exception_handlers=True,
)
main.app:
import uvicorn
from .app import app
from .routers import subjects
from .schemas.subjects import SubjectSchema
app.include_router(subjects.router)
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000)
In my case it wasn't the register_tortoise function that needed to be in the on_event('startup') function, rather another part of my code that was trying to use the db before it was initialised. I moved this piece of code (the instantiation of the class that had the query) inside an on_event('startup') block and everything started working. Basically, if you have any db queries that fire before register_tortoise, it will fail. (That's why it works with Swagger)
Related
Would you please tell me what's wrong with this code?
app.py
import uvicorn
from fastapi import FastAPI
from fastapi import status
from fastapi.testclient import TestClient
from app_dep import resp
app = FastAPI()
client = TestClient(app)
def test_create_item(mocker):
mocker.patch(
'app_dep.resp',
return_value={'name', 'mocked'}
)
r = client.post("/ehsan")
print(r.json())
#app.post("/ehsan", status_code=status.HTTP_201_CREATED)
async def create():
return resp()
if __name__ == "__main__":
uvicorn.run("boot:app", host="0.0.0.0", port=8100, reload=True)
app_dep.py
def resp():
return {"name": "original"}
This is a whole code including a FastAPI app and a test. It is expected when I run pytest app.py -s see this output:
{'name': 'mocked'}
But I see:
{'name': 'original'}
I am trying to configure the tests. According to the tortoise orm documentation I create this test configuration file:
import pytest
from fastapi.testclient import TestClient
from tortoise.contrib.test import finalizer, initializer
import app.main as main
from app.core.config import settings
#pytest.fixture(scope="session", autouse=True)
def initialize_tests(request):
db_url = "postgres://USERNAME_HERE:SECRET_PASS_HERE#127.0.0.1:5432/test"
initializer(
[
"app.models",
],
db_url=db_url,
app_label="models"
)
print("initialize_tests")
request.add_finaliser(finalizer)
#pytest.fixture(scope="session")
def client():
app = main.create_application()
with TestClient(app) as client:
print("client")
yield client
And the test file looks like this:
def test_get(client):
response = client.get("/v1/url/")
assert response.status_code == 200
I try to run the tests, but I get this error:
asyncpg.exceptions._base.InterfaceError: cannot perform operation: another operation is in progress
I have found that some users don't use initializer and finalizer and do everything manually.
Testing in FastAPI using Tortoise-ORM
https://stackoverflow.com/a/66907531
But that doesn't look like the clear solution.
Question: Is there a way to make the tests work using initializer and finalizer?
Is there any way to make fast api routes acknowledge root_path while using api versioning Sample Code to reproduce:
from fastapi import FastAPI
from mangum import Mangum
import os
from fastapi.middleware.cors import CORSMiddleware
from fastapi_versioning import VersionedFastAPI
from fastapi.openapi.docs import (
get_redoc_html,
get_swagger_ui_html,
get_swagger_ui_oauth2_redirect_html,
)
from fastapi.openapi.utils import get_openapi
stage = os.environ.get('STAGE', None)
openapi_prefix = f"/{stage}" if stage else "/"
app = FastAPI(title="MyAwesomeApp",
root_path="/dev/")
#app.get("/")
def get_root():
return { app.root_path+'ee'}
#app.get("/info")
def get_root():
return {"message": "TestInfo"}
app=VersionedFastAPI(app, enable_latest=True,
description='MyAwesomeApp')
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# return openapi
handler = Mangum(app)
Here I have given root_path as /dev while creating the app .I expect to see it while opening docs where as I see it's trying to access directly /openapi.json instead of dev/openapi.json which was unexpected.
This same stuff works fine without VersionedFastAPI but fails when I start using it .
That's the reason I am unable to host OpenAPI docs via Lambda because the openapi.json path isn't respecting the base URL prefix.
Let me know If I am missing anything here
I'm using a application factory pattern, and when I tried to run my test, I get "Attempted to generate a URL without the application context being". I created a fixture to create the application:
#pytest.fixture
def app():
yield create_app()
but when I run my test
def test_get_activation_link(self, app):
user = User()
user.set_password(self.VALID_PASS)
generated_link = user.get_activation_link()
I get the above error (from the line of code url = url_for("auth.activate")). I'm also trying to figure out to have the app creation run for every test, without having to import it into every test, but I can't seem to find if that's possible.
This works for my app
import pytest
from xxx import create_app
#pytest.fixture
def client():
app = create_app()
app.config['TESTING'] = True
with app.app_context():
with app.test_client() as client:
yield client
def smoke_test_homepage(client):
"""basic tests to make sure test setup works"""
rv = client.get("/")
assert b"Login" in rv.data
So, you missed the application context.
At this year's Flaskcon there was an excellent talk about the Flask context - I highly recommend this video.
https://www.youtube.com/watch?v=fq8y-9UHjyk
I've found similar questions, but they seem to only cover mocking MongoDB and don't mention Flask.
I have a Flask app and I'm trying to unit test it with PyTest (including PyTest-Mongo and PyTest-Flask). However, before I can even get to the point of writing any tests, my test script crashes. The crash happens when importing the script with my Flash app: It's trying to create the PyMongo object without a url.
My question is: How can I ensure that PyMongo is mocked correctly at this point? According to the PyTest-Mongo documentation, the MongoDB test fixture should be passed to each of the test functions, but that doesn't help me if it's crashing on import.
test_app.py:
import pytest
import pytest_mongodb
from app import app
#pytest.fixture
def client():
app.config['TESTING'] = True
return client
app.py:
import ...
app = Flask(__name__)
app.config["MONGO_DBNAME"] = os.environ.get('DB_NAME')
app.config["MONGO_URI"] = os.environ.get('MONGO_URI')
app.secret_key = os.environ.get('SECRET')
mongo = PyMongo(app)
...
if __name__ == '__main__':
app.run(host=os.environ.get('IP'),
port=int(os.environ.get('PORT')),
debug=False)
we can wrap app and mongo in a function
This works because mongo is used as a local variable.
app.py
from flask import Flask
from flask_pymongo import PyMongo
def get_app_with_config(config):
app = Flask(__name__)
app.config.from_object(config)
mongo = PyMongo(app)
#app.route("/")
def index():
pass
.
.
return app, mongo
then we can create a test file and an application execution file with different databases:
test_app.py
from app import get_app_with_config
from config import TestConfig
app, mongo = get_app_with_config(TestConfig)
run.py
from app import get_app_with_config
from config import RunConfig
app, mongo = get_app_with_config(RunConfig)
if __name__ == '__main__':
app.run(port=8000)
Sample of config.py file:
class RunConfig:
MONGO_HOST = '192.168.1.37'
MONGO_PORT = 27017
MONGO_DBNAME = 'my_database'
MONGO_URI = f"mongodb://{MONGO_HOST}:{MONGO_PORT}/{MONGO_DBNAME}"
class TestConfig:
MONGO_HOST = '192.168.1.37'
MONGO_PORT = 27017
MONGO_DBNAME = 'my_database_test'
MONGO_URI = f"mongodb://{MONGO_HOST}:{MONGO_PORT}/{MONGO_DBNAME}"
TESTING = True
Needed a quick fix so I edited app.py so that it only hard-fails if PyMongo doesn't initialise when the file is executed (i.e. it ignores PyMongo's failed initialisation when running unit-tests.)
app = Flask(__name__)
app.config["MONGO_DBNAME"] = os.environ.get('DB_NAME')
app.config["MONGO_URI"] = os.environ.get('MONGO_URI')
app.secret_key = os.environ.get('SECRET')
try:
mongodb = PyMongo(app).db
except ValueError:
"""We don't provide a URI when running unit tests, so PyMongo will fail to initialize.
This is okay because we replace it with a version for testing anyway. """
print('PyMongo not initialized!')
mongodb = None
.
.
.
if __name__ == '__main__':
if not mongodb:
print('Cannot run. PyMongo failed to initialize. Double check environment variables.')
exit(1)
app.run(host=os.environ.get('IP'),
port=int(os.environ.get('PORT')),
debug=False)
In my tests file, I just assign the mocked mongoDB client to the app in the tests that need it. Definitely not the ideal solution.
def test_redacted(client, mongodb):
app.mongodb = mongodb
...