question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Support for async?

See original GitHub issue

The problem

Coming from Django where we used Factory Boy really a lot to a new, async stack to fully support GraphQL with subscriptions which are really cool (uvicorn + Starlette + Ariadne) we also switched to async ORM (not really an ORM) named GINO. It is based on SQLAlchemy Core and works pretty robust. However, I am struggling to adapt Factory Boy to use GINO models.

Proposed solution

At first glance I thought that I need to implement _create() method in my factory model but the problem is that the create() method for GINO model is a coroutine and can’t be called from a synchronous code. I tried to experiment with asyncio._get_running_loop() but I am really new to async stuff and my attempt failed.

Extra notes

I am using pytest with pytest-asyncio plugin to run tests with async code which works pretty well including working with DB. For that I have this in my conftest.py:

@pytest.fixture(scope="session")
def event_loop():
    """
    This is to make the asyncio event loop shared for the whole test session, otherwise
    it will be recreated for each test which will prevent using the test_db fixture.
    """
    loop = asyncio.get_event_loop()
    yield loop
    loop.close()

@pytest.fixture(autouse=True, scope="session")
async def test_db(request):
    """
    Here is some DB preparation code like (re)creating DB itself, making sure we have all 
    necessary rights etc.
    """
    await db.gino.create_all()  # this is to bind the GINO engine to DB

    yield  # passing context back to the tests

    await db.pop_bind().close()  # unbinding engine and performing other teardown later

I really miss Factory Boy and hope there is an easy solution to start my factories again. I also created an issue for GINO here https://github.com/fantix/gino/issues/608 but decided to open one here too as I think Factory Boy developed a much wider community and I have better chances that someone has the same problem as I do. Thanks all!

Issue Analytics

  • State:open
  • Created 4 years ago
  • Reactions:6
  • Comments:7 (1 by maintainers)

github_iconTop GitHub Comments

18reactions
remarkovcommented, Dec 7, 2019

Sometimes you just need to lay down your thoughts to get the proper idea. Also, the fresh mind helps (I was doing my experiments at 4am yesterday:)

I am not sure this is correct way to proceed and whether there are some unforeseen consequences that will shot me in the knee later but here’s what I did:

import uuid

import factory

from database import models


class UserFactory(factory.Factory):
    class Meta:
        model = models.User

    nickname = factory.Sequence(lambda n: f"Test User {n}")
    uuid = factory.LazyAttribute(lambda _: str(uuid.uuid4()))

    @classmethod
    def _create(cls, model_class, *args, **kwargs):
        async def create_coro(*args, **kwargs):
            return await model_class.create(*args, **kwargs)

        return create_coro(*args, **kwargs)

Then in my test I do

new_user = await UserFactory()

and get my new shiny user object created properly in DB! So far I am very happy with the result.

If more wise and experienced developers won’t see any issues with this approach I think it may worse adding something like this to the recipes section as async stack is getting more and more popular. I am leaving this issue open for now as I hope there will be some comments and/or advice. If not, it is absolutely fine to close it.

16reactions
nadegecommented, Oct 23, 2020

Thanks, that really helped me. I extended your version to support more features.

class AsyncFactory(factory.Factory):
    @classmethod
    def _create(cls, model_class, *args, **kwargs):
        async def maker_coroutine():
            for key, value in kwargs.items():
		# when using SubFactory, you'll have a Task in the corresponding kwarg
		# await tasks to pass model instances instead
                if inspect.isawaitable(value):
                    kwargs[key] = await value
	    # replace as needed by your way of creating model instances
            return await model_class.create_async(*args, **kwargs)

	# A Task can be awaited multiple times, unlike a coroutine.
	# useful when a factory and a subfactory must share a same object
        return asyncio.create_task(maker_coroutine())


    @classmethod
    async def create_batch(cls, size, **kwargs):
        return [await cls.create(**kwargs) for _ in range(size)]



class UserFactory(AsyncFactory):
    ...

class Category(AsyncFactory):
   ...
   creator = factory.SubFactory(UserFactory)

class ArticleFactory(AsyncFactory):
   ...
   author = factory.SubFactory(UserFactory)
   category = factory.SubFactory(CategoryFactory, creator=factory.SelfAttribute(..author))

In the following example:

article = await ArticleFactory.create()
assert article.author == article.category.creator

The _create function of UserFactory is called to create the Article Author, this returns a Task. Then the _create_ function of Category is called, with the User creation Task in its kwarg, which is awaited. The category model creation can use the User instance. Finally the _create function of Article is called, also with the User creation Task. It is awaited again. The user instance is used in the article creation.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Async functions | Can I use... Support tables for HTML5, CSS3 ...
Async functions make it possible to treat functions returning Promise objects as if they were synchronous. Usage % of. all users, all tracked,...
Read more >
async function - JavaScript - MDN Web Docs - Mozilla
The async function declaration declares an async function where the await keyword is permitted within the function body. The async and await ...
Read more >
Async functions: making promises friendly - web.dev
Async functions allow you to write promise-based code as if it were synchronous.
Read more >
Cross Browser Compatibility Score of Async functions
Async functions shows a browser compatibility score of 88. This is a collective score out of 100 to represent browser support of a...
Read more >
Asynchronous Programming with Async and Await - Visual Basic
This topic provides an overview of when and how to use async programming and includes links to support topics that contain details and...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found