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
Optimistic concurrency control using ETags #171
Conversation
I really appreciate your pr. allow me to review 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.
also docs with some release notes are needed
@@ -6,3 +6,4 @@ __pycache__/ | |||
.idea | |||
env | |||
dist | |||
.DS_Store |
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.
You should probably add .DS_Store
to your global .gitignore
file, so you don't always have to ignore it for every repo.
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.
Thx for the hint, was missing that on this machine.
@auvipy sure, go ahead. i will work on some docs and include release notes. |
…ng key bit from DefaultKeyConstructor
…ying API resources
@auvipy Docs in the development version has also been updated according to the guidelines. |
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.
docs looks good. you could make the development version to 0.3.2 👍
@@ -106,15 +106,6 @@ class DefaultListKeyConstructor(DefaultKeyConstructor): | |||
pagination = bits.PaginationKeyBit() | |||
|
|||
|
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.
can this removal go through deprecation cycle?
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.
Sorry, but could you be more specific on that?
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.
@auvipy I'm pretty sure this only existed in the context of this pull request.
This class is responsible for calculating the ETag value given (a list of) model instance(s). | ||
|
||
The difference to the base class `ETAGProcessor` is that it cannot be used as `@api_etag()` decorator. |
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.
The difference to the base class
ETAGProcessor
is that it cannot be used as@api_etag()
decorator.
I can't quite figure out what this is trying to say, mostly because this is being aliased to api_etag
later in the file.
The rest of the docstring makes sense, as this is basically the ETAGProcessor
with the requirement that the function for building the etag must be specified.
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.
Yes you are right, this is a relict of the development phase and misleading. I removed that in a new commit.
I'm definitely missing something when looking over the changes for this pull request. The follow changes make sense.
All of these should in one way or another fix the current issue that we have with ETags, where they aren't being generated properly. But what I can't figure out is why there is a whole new set of settings, mixins, and views being added on top of these changes? They allow for the new changes to be rolled out quickly, but they don't add much on top of the existing ETag settings, mixins, and views as it appears that you could realistically swap them both without much issue. Wouldn't it make more sense to recommend that these new key bits and constructors are used within the documentation, and eventually (when a version change allows it) make these the default? |
The main idea was not to break any existing functionality in the package and provide the new behaviour out of the box in You can as well use the following code that has the same effect as the REST_FRAMEWORK_EXTENSIONS = {
'DEFAULT_OBJECT_ETAG_FUNC': 'rest_framework_extensions.utils.default_api_object_etag_func',
'DEFAULT_LIST_ETAG_FUNC': 'rest_framework_extensions.utils.default_api_list_etag_func',
} In from rest_framework import viewsets
from rest_framework_extensions.etag.mixins import ETAGMixin
from my_app.models import Model
from my_app.serializer import ModelSerializer
class SomeView(ETAGMixin, viewset.ModelViewSet):
queryset = Model.objects.all()
serializer = ModelSerializer The alternative option is to keep the default as is and define it on a per-view(set) basis: from rest_framework import viewsets
from rest_framework_extensions.etag.mixins import ETAGMixin
from rest_framework_extensions.utils import (default_api_object_etag_func, default_api_list_etag_func)
from my_app.models import Model
from my_app.serializer import ModelSerializer
class SomeView(ETAGMixin, viewset.ModelViewSet):
object_etag_func = default_api_object_etag_func
list_etag_func = default_api_list_etag_func
queryset = Model.objects.all()
serializer = ModelSerializer Whichever you prefer works without the |
We could do that by adding example configurations (for instance as in #171 (comment)) on how to use the new |
@auvipy @kevin-brown |
I would rather interested to get it in ASAP. but still willing to see others opinion before get merged. I would request you to be patient. and thanks a lot for all this work. |
@pkainz do you think anything could be done for better in this patch? otherwise i will merge it and wait for new pr's |
Yes, I have some additions. Will commit soon. |
…s, a new exception type
@auvipy I added a new commit that adds support for mandatory conditional requests, and returns |
Hi, back again to ask more questions. Is there any reason why the precondition checks were wrapped in with etags instead of being broken out as their own decorator? The docs allude to additional use cases (which do exist, like |
@pkainz could you address the issues raised by @kevin-brown |
Fair point, @kevin-brown. No there is not, I simply did not go through all other use cases that may require headers. A dedicated decorator, e.g. |
…al, added more tests
Following the input of @kevin-brown, I removed the The principal functionality for checking custom headers is now provided. However, I will not integrate this new pattern into the existing bulk-operations. Maybe someone else will submit a PR for this refactoring. @auvipy, could you merge that PR? |
@kevin-brown could you provide a final feedback as it seems he addressed some of your suggestions? @pkainz Could you please open one/more relevant issues describing the possible refactors? |
going to reinvestigate this again |
API resources: Optimistic concurrency control using ETags
The Problem
As @mbox already mentioned, the default implementation in
drf-extensions
does not allow a correct optimistic locking usingETag
s and conditional requests. TheETag
ties each request to a particular HTTP method, view, and response. While this works as expected for server-side caching (correctly returns status304
, and200
), it does not work to identify the requested resources when retrieving an object, altering it and putting it back to the server:Related discussion in the DRF issues: encode/django-rest-framework#32
Proposed Changes
I have implemented the following changes/additions to enable optimistic locking using conditional requests using
ETag
,If-Match
,If-None-Match
HTTP headers in DRF-extensions. It is based on the requirement that we need anETag
value to represent the current semantic version of individual model instances rather than using it as caching function for a dynamic page.KeyBit
s for retrieving and listing model instances: decoupled from the views and methods, the entries in theQuerySet
are tuples represented by their mere field values.KeyConstructor
s for API views that use the newKeyBit
s.ETag
s for API views (insettings
andutils
)API{method}ETAGMixin
s:List
,Retrieve
,Update
,Destroy
, as well as combined mixins forReadOnly
andAPIETAGMixin
.The new features have been tested in unit tests where applicable and there exists a new functional test app in
tests_app/tests/functional/concurrency
. It tests a standardAPIClient
from therest_framework
to queryBook
models.Additional Changes
Some other changes have been performed to ensure that all tests pass in django 1.10 and 1.9, DRF 3.5.3:
fields = '__all__'
to serializers (ensures DRF>3.3 compatibility)rest_framework_extensions/utils.py
I documented the source code. The usage of the new mixins and decorators is identical to the current implementation that exists for
@etag
.