-
Notifications
You must be signed in to change notification settings - Fork 768
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
Handle database transactions #1039
Handle database transactions #1039
Conversation
…because of backward compatibility
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, definitely accomplishes something we couldn't simply do with a transaction decorator (the grouping of mutations). This appears to be backwards compatible and I am convinced enough of the use case.
It's less clear where ATOMIC_REQUESTS
gets set, perhaps show a quick example of that.
Anyone else like this feature or have feedback on how it can be improved? Please feel free to add your own review to help.
graphene_django/tests/test_views.py
Outdated
|
||
assert Pet.objects.count() == 0 | ||
|
||
finally: |
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.
We can use mock.patch.dict
to temporarily set these values and drop the finally: https://docs.python.org/3/library/unittest.mock.html#unittest.mock.patch.dict
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.
Amazing @zbyte64, I didn't know the feature, thank you very much! I removed all try-finally
in favor of mock.path.dict
.
On the other hand, I always perform a self code review when I am creating a PR. Anyway, I just reviewed it again and the only part I still don't love is the one I commented on the code:
def _set_errors_flag_to_context(info):
# This is not ideal but necessary to keep the response errors empty
if info and info.context:
setattr(info.context, MUTATION_ERRORS_FLAG, True)
So, if you have an idea to do it in a better way, I will improve it.
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 note of that as well but I couldn't purpose any better solution.
Maybe @jkimbo, @ulgens, @ace-han, or @brucedesa can add their opinion because of the discussed issue #835. |
} | ||
} | ||
|
||
But the balance will remain the same. |
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 had a hard time to figure out But the balance will remain the same.
It'd better to be
Assuming prev balance is 0, after the example mutation the balance is 10.
for a much clearer description
Motivation
I was faced with an issue similar to the one described in #835 and I decided to solve both use cases.
I updated the project documentation and I also put it below to explain the PR.
Django Database Transactions
Django gives you a few ways to control how database transactions are managed.
Tying transactions to HTTP requests
A common way to handle transactions in Django is to wrap each request in a transaction. Set
ATOMIC_REQUESTS
settings toTrue
in the configuration of each database for which you want to enable this behavior.It works like this. Before calling
GraphQLView
Django starts a transaction. If the response is produced without problems, Django commits the transaction. If the view, aDjangoFormMutation
or aDjangoModelFormMutation
produces an exception, Django rolls back the transaction.Check the next section for a better solution.
Tying transactions to mutations
A mutation can contain multiple fields, just like a query. There's one important distinction between queries and mutations, other than the name:
This means that if we send two
incrementCredits
mutations in one request, the first is guaranteed to finish before the second begins, ensuring that we don't end up with a race condition with ourselves.On the other hand, if the first
incrementCredits
runs successfully but the second one does not, the operation cannot be retried as it is. That's why is a good idea to run all mutation fields in a transaction, to guarantee all occur or nothing occurs.To enable this behavior for all databases set the graphene
ATOMIC_MUTATIONS
settings toTrue
in your settings file:On the contrary, if you want to enable this behavior for a specific database, set
ATOMIC_MUTATIONS
toTrue
in your database settings:Now, given the following example mutation:
The server is going to return something like:
But the balance will remain the same.