python-gino / gino

GINO Is Not ORM - a Python asyncio ORM on SQLAlchemy core.
https://python-gino.org/
Other
2.68k stars 150 forks source link

Cannot perform operation: another operation is in progress #728

Open erhosen opened 4 years ago

erhosen commented 4 years ago

Description

My task is create some database record (via GINO Model.create()) before running test (typical pytest.fixture usage), and drop everything after.

What I Did

conftest.py

import pytest
from fastapi.testclient import TestClient

from alembic.config import main as alembic_main

from settings import settings
settings.DATABASE_NAME = 'test_db_name'
from main import app_init

@pytest.fixture
def client():
    alembic_main(["--raiseerr", "upgrade", "head"])

    with TestClient(app_init()) as client:
        yield client

    alembic_main(["--raiseerr", "downgrade", "base"])

test.py

import pytest
from models import SpecialModel

@pytest.fixture
async def special_model():
    special_model = await SpecialModel.create(
        internal_name='internal_name',
        comment='comment',
        is_active=True,
        title="Title",
        text="Text"
    )
    yield special_model

def test_get_all_special_models(client, special_model):
    response = client.get("api/get_all_models")
    data = response.json()
    assert data["success"] is True
    assert len(data["result"]) == 1

What I got:

test setup failed
@pytest.fixture
    async def special_model():
>      special_model = await SpecialModel.create(
            internal_name='internal_name',
            comment='comment',
            is_active=True,
            title="Title",
            text="Text"
        )

tests/test.py:8: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../.virtualenvs/deeplink-offer-service/lib/python3.8/site-packages/gino/crud.py:444: in _create_without_instance
    return await cls(**values)._create(bind=bind, timeout=timeout)
../../.virtualenvs/deeplink-offer-service/lib/python3.8/site-packages/gino/crud.py:477: in _create
    row = await bind.first(q)
../../.virtualenvs/deeplink-offer-service/lib/python3.8/site-packages/gino/engine.py:748: in first
    return await conn.first(clause, *multiparams, **params)
../../.virtualenvs/deeplink-offer-service/lib/python3.8/site-packages/gino/engine.py:147: in __aexit__
    await conn.release()
../../.virtualenvs/deeplink-offer-service/lib/python3.8/site-packages/gino/engine.py:279: in release
    await dbapi_conn.release(True)
../../.virtualenvs/deeplink-offer-service/lib/python3.8/site-packages/gino/engine.py:47: in release
    return await self._release()
../../.virtualenvs/deeplink-offer-service/lib/python3.8/site-packages/gino/engine.py:83: in _release
    await self._pool.release(conn)
../../.virtualenvs/deeplink-offer-service/lib/python3.8/site-packages/gino/dialects/asyncpg.py:232: in release
    await self._pool.release(conn)
../../.virtualenvs/deeplink-offer-service/lib/python3.8/site-packages/asyncpg/pool.py:654: in release
    return await asyncio.shield(ch.release(timeout))
../../.virtualenvs/deeplink-offer-service/lib/python3.8/site-packages/asyncpg/pool.py:216: in release
    raise ex
../../.virtualenvs/deeplink-offer-service/lib/python3.8/site-packages/asyncpg/pool.py:206: in release
    await self._con.reset(timeout=budget)
../../.virtualenvs/deeplink-offer-service/lib/python3.8/site-packages/asyncpg/connection.py:1137: in reset
    await self.execute(reset_query, timeout=timeout)
../../.virtualenvs/deeplink-offer-service/lib/python3.8/site-packages/asyncpg/connection.py:295: in execute
    return await self._protocol.query(query, timeout)
asyncpg/protocol/protocol.pyx:301: in query
    ???
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

>   ???
E   asyncpg.exceptions._base.InterfaceError: cannot perform operation: another operation is in progress
uriyyo commented 4 years ago

@ErhoSen I faced the same problem, solution was to use a separate db connection:

@pytest.fixture
async def special_model():
    async with db.acquire():  # use seperate db connection
        special_model = await SpecialModel.create(
            internal_name='internal_name',
            comment='comment',
            is_active=True,
            title="Title",
            text="Text"
        )

    yield special_model

This isn't an issue with gino it's an asyncpg issue. Basically, asyncpg can't make two concurrent queries using the same db connection.

fantix commented 3 years ago

Thanks! This deserves a page in the docs.

kgantsov commented 3 years ago

I have the same exception asyncpg.exceptions._base.InterfaceError: cannot perform operation: another operation is in progress when I run pytest.

Any idea what I did wrong? I would appreciate any help. Thanks in advance.

requirements.txt

fastapi==0.70.0
gino==1.0.1
pytest==6.2.5
pytest-asyncio==0.16.0
requests==2.26.0

test_code.py

import os
from typing import List
import pytest

from gino import Gino
from fastapi import APIRouter
from pydantic import BaseModel
from fastapi import FastAPI
from starlette.testclient import TestClient

router = APIRouter()

db = Gino()

async def init_db():
    await db.set_bind(os.environ['DATABASE_URL'])
    await db.gino.create_all()

class UserModel(db.Model):
    __tablename__ = 'user'

    id = db.Column(db.Integer(), primary_key=True)
    name = db.Column(db.Unicode())
    email = db.Column(db.Unicode(), unique=True, index=True)
    password_hash = db.Column(db.Unicode())

class UserSchema(BaseModel):
    id: int = 0
    name: str
    email: str
    password: str

class UserListSchema(BaseModel):
    objects: List[UserSchema]

@router.get("/users/", response_model=UserListSchema)
async def get_users():
    async with db.acquire():
        users = await UserModel.query.limit(200).gino.all()

    return UserListSchema.parse_obj({
        'objects': [x.to_dict() for x in users]
    })

def get_app():
    print('INIT APP')
    app = FastAPI(title="GINO FastAPI Demo")

    app.include_router(router, prefix='/API/v1')

    @app.on_event("startup")
    async def startup_event():
        print('Initialising DB')
        await init_db()
        print('DB was initialised')

    return app

@pytest.fixture
def client():

    with TestClient(get_app()) as client:
        yield client

@pytest.fixture
@pytest.mark.anyio
async def user(client):

    print('[-------->')
    # await init_db()

    # async with db.acquire():
    user = await UserModel.create(email='test@gmail.com')

    # async with db.acquire():
    users = await UserModel.query.limit(200).gino.all()
    print('.....=', user)
    print('....._', users)

    yield user

def test_users(user, client):
    response = client.get(
        "/API/v1/users",
        headers={},
    )
    print('=====', user, response.text)

    assert response.status_code == 200
    assert response.json() == {}
MarkParker5 commented 2 years ago

I fixed it by setting poolclass=NullPool in create_async_engine