-
Notifications
You must be signed in to change notification settings - Fork 427
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
Exposing control over return_values in Model.update() #1050
base: master
Are you sure you want to change the base?
Conversation
:raises ModelInstance.DoesNotExist: if the object to be updated does not exist | ||
:raises pynamodb.exceptions.UpdateError: if the `condition` is not met | ||
""" | ||
if not isinstance(actions, list) or len(actions) == 0: | ||
raise TypeError("the value of `actions` is expected to be a non-empty list") | ||
if read_back not in (ALL_NEW, NONE): | ||
raise ValueError("expected `read_back` to be `ALL_NEW` or `NONE`, but was: {}".format(read_back)) |
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.
raise ValueError("expected `read_back` to be `ALL_NEW` or `NONE`, but was: {}".format(read_back)) | |
raise ValueError(f"expected `read_back` to be `ALL_NEW` or `NONE`, but was: '{read_back}'") |
@@ -921,6 +921,28 @@ def test_update(self, mock_time): | |||
assert item.views is None | |||
self.assertEqual({'bob'}, item.custom_aliases) | |||
|
|||
def test_update_readback(self): | |||
self.init_table_meta(SimpleUserModel, SIMPLE_MODEL_TABLE_DATA) |
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.
self.init_table_meta(SimpleUserModel, SIMPLE_MODEL_TABLE_DATA) |
No longer needed 😅
'ExpressionAttributeValues': {':0': {'S': '[email protected]'}}, | ||
'ReturnConsumedCapacity': 'TOTAL' | ||
} | ||
args = req.call_args[0][1] |
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.
args = req.call_args[0][1] | |
args = req.call_args[0].args |
actions: List[Action], | ||
condition: Optional[Condition] = None, | ||
settings: OperationSettings = OperationSettings.default, | ||
read_back: str = ALL_NEW, |
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 if we should make it an enum. This library isn't awfully modern, but maybe there's an opportunity to change that (especially as we're establishing a new API here).
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'd say an enum is a better choice, too. Let's start to use strong typing whenever possible.
Sorry for overlooking this PR for so long. This is a very valid addition! |
I definitely should merge this before 6.x. |
Problem
Currently, invoking
.update()
on a model will always read back the entire document from the DB with no option to override this behavior. If one's object is very large (e.g., contains long lists), one may not wish to receive everything (or anything at all) back after performing an update (e.g., in the common use case of a REST API handler). That's a use case for us at Intheon.DynamoDB has of course a feature for that, the ReturnValues request parameter (which can take the values
NONE
,ALL_NEW
,ALL_OLD
,UPDATED_NEW
andUPDATED_OLD
), and that feature is already exposed in PynamoDB, but so far only in the low-levelTableConnection.update_item()
method. Unfortunately, falling back to that in user code would require the user to duplicate some internal PynamoDB logic and/or to invoke non-public methods of the model, so it's not really an alternative that can be recommended.This PR
Currently,
Model.update()
simply hard-codes this value toALL_NEW
. This PR proposes to make that a new parameter instead, which can be overridden as initem.update(actions=[...], read_back=NONE)
to read nothing back from the DB. At this time, the PR is trivial and permits only eitherALL_NEW
(default, and equivalent to the current behavior) orNONE
, which reads nothing back and consequently returnsNone
and leaves the local item unaltered.I've pondered whether it should be called
return_values
instead, but that seems rather confusing to the user, who may assume that the flag affects only the return values of theupdate()
method, while the local item might still be updated in-place as usual. Calling itread_back
makes that clearer since it talks about what information comes back from the DB. That interpretation also holds up when in the future someone decides to add support for other DynamoDB modes likeALL_OLD
, in which case we'd expect that the local copy will be populated with whatever the DB state was before the update happened (rather than only the return value receiving the old data).While this PR is very simple, in the future someone could implement support for partial updates of the local record (via
UPDATED_NEW
), and this argument would allow the user to selectively choose one read-back mode or another, which would be an application-dependent choice (e.g., in the presence of concurrency), so there's some future-proofing in there.A unit test that checks these semantics is provided.