kuzxnia / async_factory_boy

factory_boy extension with asynchronous ORM support
https://pypi.org/project/async-factory-boy/
MIT License
18 stars 7 forks source link

Endless execution creating an instance #7

Closed greatlaki closed 6 months ago

greatlaki commented 6 months ago

pytohn =3.11.4 fastapi = "^0.103.2" async-factory-boy = "^1.0.1" fastapi-users = {extras = ["sqlalchemy"], version = "^12.1.2"}

Here is my settings for testing FastApi app


engine_test = create_async_engine(settings.TEST_DB.ADDRESS)
async_session = sessionmaker(
    engine_test,
    class_=AsyncSession,
    expire_on_commit=False,
    autocommit=False,
    autoflush=False,
)
sc_session = scoped_session(async_session)

async def override_get_async_session() -> AsyncGenerator[AsyncSession, None]:
    async with async_session() as session:
        yield session

@pytest.fixture(scope='session')
def event_loop(request):
    loop = asyncio.get_event_loop_policy().new_event_loop()
    yield loop
    loop.close()

@pytest_asyncio.fixture(autouse=True, scope='function')
async def db_session():
    async with engine_test.begin() as conn:
        await conn.run_sync(Base.metadata.drop_all)
        await conn.run_sync(Base.metadata.create_all)

        async with async_session(bind=conn) as session:
            yield session
            await session.flush()
            await session.rollback()

client = TestClient(app)

@pytest.fixture(scope='function')
async def ac() -> AsyncGenerator[AsyncClient, None]:
    app.dependency_overrides[get_async_session] = override_get_async_session
    async with AsyncClient(app=app, base_url='http://test') as ac:
        yield ac

class UserFactory(AsyncSQLAlchemyFactory):
    class Meta:
        model = User
        sqlalchemy_session = sc_session

    id = UUID(int=random.getrandbits(128))
    email = factory.Faker('email')
    password = 'password'
    is_active = True
    is_superuser = False
    is_verified = False

    @classmethod
    def _create(cls, model_class, *args, **kwargs):
        kwargs['hashed_password'] = password_helper.hash(kwargs.pop("password"))
        return super()._create(model_class, *args, **kwargs)

I tried to test user login

async def test_it_authorizes_user(ac: AsyncClient):
    user = await UserFactory(email='test@example.com', password='test_pass!')

    response = await ac.post(
        '/backend/registration/auth/jwt/login',
        data={
            'username': user.email,
            'password': user.password,
        },
    )
    assert response.status_code == 204

As a result is endless execution creating an instance

the part of library code (async_factory_boy.factory.sqlalchemy.AsyncSQLAlchemyFactory.create)

@classmethod
async def create(cls, **kwargs):
    session = cls._meta.sqlalchemy_session

    instance = await super().create(**kwargs) <-- the line that has endless execution
    # one commit per build to avoid share the same connection
    await session.commit()
    return instance
kuzxnia commented 6 months ago

I've tried to reproduce, but you didn't provide full code to reproducer error. I've tried using

# user model
class TestUserModel(Base):
    __tablename__ = "TestUserModelTable"

    id = Column(Integer(), primary_key=True)
    email = Column(Unicode(200))
    password = Column(Unicode(200))
    hashed_password = Column(Unicode(200))

# factory model
class TestUserModelFactory(AsyncSQLAlchemyFactory):
    class Meta:
        model = models.TestUserModel
        sqlalchemy_session = sc_session

    id = factory.Sequence(lambda n: n)
    email = factory.Sequence(lambda n: "text%s" % n)
    password = factory.Sequence(lambda n: "password%s" % n)

    @classmethod
    def _create(cls, model_class, *args, **kwargs):
        kwargs['hashed_password'] = kwargs.pop("password")
        return super()._create(model_class, *args, **kwargs)

# user creation test
async def test_user_creation(db_session):
    test_user = await TestUserModelFactory(password="password")

    found_user = (await db_session.execute(sqlalchemy.select(TestUserModel).filter_by(id=test_user.id))).scalars().one()

    assert found_user.id == test_user.id
    assert found_user.hashed_password == test_user.hashed_password

It did work.

greatlaki commented 6 months ago

@kuzxnia, I think that you didn't use two libs to reproduce my bug. What else should I provide?

fastapi = "^0.103.2"
fastapi-users = {extras = ["sqlalchemy"], version = "^12.1.2"}
kuzxnia commented 6 months ago

You're correct, I overlooked that. While I'm unable to debug and provide support for every SQLAlchemy wrapper library individually, I recommend reaching out to the maintainers of fastapi-users or the SQLAlchemy community for assistance. Feel free to ask if you have any other questions. Apologies and thank you!

greatlaki commented 6 months ago

Okay, thank you for the information

On Fri, Feb 9, 2024 at 1:23 AM Kacper Kuźniarski @.***> wrote:

You're correct, I overlooked that. While I'm unable to debug and provide support for every SQLAlchemy wrapper library individually, I recommend reaching out to the maintainers of fastapi-users or the SQLAlchemy community for assistance. Feel free to ask if you have any other questions. Apologies and thank you!

— Reply to this email directly, view it on GitHub https://github.com/kuzxnia/async_factory_boy/issues/7#issuecomment-1935028061, or unsubscribe https://github.com/notifications/unsubscribe-auth/AU2W2ZX47H43DSZ3G3274YLYSVF6XAVCNFSM6AAAAABCOBP3ECVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMYTSMZVGAZDQMBWGE . You are receiving this because you authored the thread.Message ID: @.***>