-
Notifications
You must be signed in to change notification settings - Fork 397
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Async Factory Support #803
base: master
Are you sure you want to change the base?
Conversation
0303291
to
077c598
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @nadege,
Thanks a lot for taking the time to prepare this patch 🎉.
Async capabilities in factory_boy seems to be interesting to a portion of the user base and would benefit the library. Django is planning to eventually support creating objects asynchronously and SQLAlchemy has GINO or aiopg
, movement in that direction is great.
Thanks for adding tests following the create methods, it helped review a lot. The suggested changes make sense to me, though I am not very familiar with async in Python.
Can tests be run with the Python development mode to catch potential issues in asyncio? Enabled in #812.
The new extension points should be listed in the extension points section https://github.com/FactoryBoy/factory_boy/blob/9b256e48dd58ef7c97e3ddf09d972d1bb1d2df8e/docs/reference.rst.
An entry should be added to the release note.
Thanks for the review! |
780909f
to
5f0c9f6
Compare
I added an implementation for https://github.com/encode/orm ORM in the last commit. Last problem: (I also have documentation spelling errors.) |
5f0c9f6
to
330c5e9
Compare
I resolved the compatibility issues with older python versions and spelling errors in the doc. |
oh I still need to update |
I'll wait for djangoproject.com to be fixed so I can run |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I took a quick look at it today, it seems great! 🎉
I’ll try to dive deeper later this week. Thanks for adding support for SQLite, it helps exercising the new code and increases confidence in the patch.
I don't know how to simply add a postgresql database to the current CI, but when it'll be replaced by github actions it should be easier.
https://docs.travis-ci.com/user/database-setup/#postgresql should be helpful. If that does not work out, I’m happy with the SQLite version to start with. The more test coverage, the better, but IMO PostgreSQL support is not mandatory for landing this patch. Getting started on async benefits the project and we can increase test coverage as we go.
78e378f
to
b005370
Compare
Issues highlighted in previous review should be fixed. Added doc, credits and changelog. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder how async factories relate to post generation hooks https://factoryboy.readthedocs.io/en/latest/reference.html#postgeneration?
The post generation hooks assume the instance was created by the call to instantiate:
factory_boy/factory/builder.py
Lines 268 to 272 in 88b81ea
instance = self.factory_meta.instantiate( | |
step=step, | |
args=args, | |
kwargs=kwargs, | |
) |
Since create_async
returns a task and not an instance, perhaps the post generation hooks should await it before to run? Or register the post generation hooks as add_done_callback
on the Task?
Or do you expect subclasses of AsyncFactory to expect the task as the first positional argument, awaiting it if necessary?
tests/test_using.py
Outdated
def test_create_async(self): | ||
loop = asyncio.get_event_loop() | ||
obj = loop.run_until_complete(factory.create_async(FakeAsyncModel, foo='bar')) | ||
self.assertEqual(obj.id, None) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
id
is None
because create()
was not called.
Is that the expected behavior? I would expect that when the task to create the object is done, the object has been recorded to the database and has an id?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's expected because the make_factory
function will output a factory based on Factory class.
So we end up with model_class(**kwargs) to create the object, not the model async create
function.
I'll add comments to the test.
For comparison, the sync test:
def test_create(self):
obj = factory.create(FakeModel, foo='bar')
self.assertEqual(obj.id, None)
self.assertEqual(obj.foo, 'bar')
If you look at the test test_create_async_custom_base
, it has parameter FACTORY_CLASS=FakeAsyncModelFactory
and the async creation is ok.
Hi, @nadege . Great work you've done. However, I would like to ask will you plan to add async support for post_generation methods ? I haven't found any code changes related to that feature. |
Hello ! |
bb0d472
to
9d94c4f
Compare
I handled the remarks you made in the last review. Now I'll look into post-generation |
Seems that |
@mdczaplicki would you have a test that reproduce the issue ? |
Sure, I'll provide you with something next week. :)
…On Jan 22 2021, at 11:06 am, Nadege Michel ***@***.***> wrote:
@mdczaplicki (https://github.com/mdczaplicki) would you have a test that reproduce the issue ?
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub (#803 (comment)), or unsubscribe (https://github.com/notifications/unsubscribe-auth/ACFPY2QYTE2IMOMIRBU7NE3S3FE2DANCNFSM4TFHREYQ).
|
@nadege @mdczaplicki Any progress on this? |
No, I'm between jobs and my personal computer just broke, so I can't work on this at the moment. |
Hey @nadege the work here is awesome! 🥇 I am really interested on having this feature kicked out of the door, do you have any plans to finish it? If you don't mind I would be happy to jump in and help to get it done. Could you tell what is missing? |
IIRC, the main piece remaining is deciding how to handle the post-generation hooks. |
hi @romulorosa ! I don't have much time (or energy :x) to continue this short term. So I'd be happy to see you take it from here ! |
any updates on this PR? |
@francoisfreitag Can this feature be released in parts? As in, first release the basic functionality and then add the |
The post generation hooks need handling. The handling can probably be to |
My use case doesn't require |
I'd like to see this PR get merged too. @ShipraShalini did you make some progress there on the |
Hello there, I'm back 🙇 Changes on the PR since last time (3 years ago, time flies... 👵)
TODO:
Dropped:
|
03dc0f2
to
e282b2a
Compare
And, post generation is now handled too! 🎉 I played with the suggestion from @francoisfreitag, to use Code is in last commit, it's a bit quick & dirty. Not sure of the cleanest way to declare the |
This is awesome @nadege. I hope it gets merged soon, it's been a few months already since your last commit. |
Great work @nadege , it was very helpful in fixing some of the issues with the solution in my team. I also hope this gets merged soon so we can migrate away from temporary workarounds :) |
Appreciate all the work on this @nadege. :-) @francoisfreitag is there other work needed to get this merged? Thanks! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great work overall, thank you!! ⭐ 🙌
I believe this is ready for merging, pending some minor RST fixes and maybe a couple of test tightening. I’ll try to push a few commits in the upcoming weeks, depending on my newborn’s sleep.
Feel free to beat me to it, if you have time before then!
Pinging @rbarrois, in case you would like to review this work before inclusion.
054a945
to
5f2582d
Compare
@classmethod | ||
async def _save(cls, model_class, session, args, kwargs): | ||
session_persistence = cls._meta.sqlalchemy_session_persistence | ||
|
||
return model | ||
obj = model_class(*args, **kwargs) | ||
session.add(obj) | ||
if session_persistence == SESSION_PERSISTENCE_FLUSH: | ||
await session.flush() | ||
elif session_persistence == SESSION_PERSISTENCE_COMMIT: | ||
await session.commit() | ||
return obj |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code was not actually saving the object to the db. 😬
TODO:
- add a test that would have shown that
- handle
get_or_create
in async create as well, or can we ship without? (I won't have bandwidth for it)
I rebased & addressed the simpler comments, realised we were not committing/flushing so I fixed that. TODO:
I can find time for first 2 items. Edit: Thank you and congrats for the baby! 😁 |
Hey @nadege! How can I help move this PR along? I would love to see this merged in :) I am happy to tackle the |
Hello,
I heard you may be interested in a PR to add support for async Factories, related issue : #679
We use the solution I documented in the issue in a project at work (
aiohttp
+aiopg
) and it works well,but even if we have models with some complexity (lot of
Subfactory
themselves usingSelfAttribute
),I know we don't use a lot of features of Factory Boy...
For this PR I chose to add a new strategy for async creation, and added a new class
AsyncFactory
that default to this strategy.I also added a few methods to
class Factory
, and functions to thehelpers
module,to create, batch create, generate, batch generate... with this new strategy.
There should be no issue with retro-compatibility, it only add new features.
I wrote some tests and documentation but they may be insufficient.
Advice on what to test or document further is welcome of course.
Duplicating all tests to run them with an async factory seems a bit overkill to me,
but maybe we should for some test cases ?
what do you think ?