Add support for creating a PATCH body from a before and after object.
Reiewed in http://codereview.appspot.com/4532086/
This commit is contained in:
@@ -299,3 +299,47 @@ class ProtocolBufferModel(BaseModel):
|
||||
@property
|
||||
def no_content_response(self):
|
||||
return self._protocol_buffer()
|
||||
|
||||
|
||||
def makepatch(original, modified):
|
||||
"""Create a patch object.
|
||||
|
||||
Some methods support PATCH, an efficient way to send updates to a resource.
|
||||
This method allows the easy construction of patch bodies by looking at the
|
||||
differences between a resource before and after it was modified.
|
||||
|
||||
Args:
|
||||
original: object, the original deserialized resource
|
||||
modified: object, the modified deserialized resource
|
||||
Returns:
|
||||
An object that contains only the changes from original to modified, in a
|
||||
form suitable to pass to a PATCH method.
|
||||
|
||||
Example usage:
|
||||
item = service.activities().get(postid=postid, userid=userid).execute()
|
||||
original = copy.deepcopy(item)
|
||||
item['object']['content'] = 'This is updated.'
|
||||
service.activities.patch(postid=postid, userid=userid,
|
||||
body=makepatch(original, item)).execute()
|
||||
"""
|
||||
patch = {}
|
||||
for key, original_value in original.iteritems():
|
||||
modified_value = modified.get(key, None)
|
||||
if modified_value is None:
|
||||
# Use None to signal that the element is deleted
|
||||
patch[key] = None
|
||||
elif original_value != modified_value:
|
||||
if type(original_value) == type({}):
|
||||
# Recursively descend objects
|
||||
patch[key] = makepatch(original_value, modified_value)
|
||||
else:
|
||||
# In the case of simple types or arrays we just replace
|
||||
patch[key] = modified_value
|
||||
else:
|
||||
# Don't add anything to patch if there's no change
|
||||
pass
|
||||
for key in modified:
|
||||
if key not in original:
|
||||
patch[key] = modified[key]
|
||||
|
||||
return patch
|
||||
|
||||
71
tests/test_model.py
Normal file
71
tests/test_model.py
Normal file
@@ -0,0 +1,71 @@
|
||||
#!/usr/bin/python2.4
|
||||
#
|
||||
# Copyright 2010 Google Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Model tests
|
||||
|
||||
Unit tests for model utility methods.
|
||||
"""
|
||||
|
||||
__author__ = 'jcgregorio@google.com (Joe Gregorio)'
|
||||
|
||||
import httplib2
|
||||
import unittest
|
||||
|
||||
from apiclient.model import makepatch
|
||||
|
||||
|
||||
TEST_CASES = [
|
||||
# (message, original, modified, expected)
|
||||
("Remove an item from an object",
|
||||
{'a': 1, 'b': 2}, {'a': 1}, {'b': None}),
|
||||
("Add an item to an object",
|
||||
{'a': 1}, {'a': 1, 'b': 2}, {'b': 2}),
|
||||
("No changes",
|
||||
{'a': 1, 'b': 2}, {'a': 1, 'b': 2}, {}),
|
||||
("Empty objects",
|
||||
{}, {}, {}),
|
||||
("Modify an item in an object",
|
||||
{'a': 1, 'b': 2}, {'a': 1, 'b': 3}, {'b': 3}),
|
||||
("Change an array",
|
||||
{'a': 1, 'b': [2, 3]}, {'a': 1, 'b': [2]}, {'b': [2]}),
|
||||
("Modify a nested item",
|
||||
{'a': 1, 'b': {'foo':'bar', 'baz': 'qux'}},
|
||||
{'a': 1, 'b': {'foo':'bar', 'baz': 'qaax'}},
|
||||
{'b': {'baz': 'qaax'}}),
|
||||
("Modify a nested array",
|
||||
{'a': 1, 'b': [{'foo':'bar', 'baz': 'qux'}]},
|
||||
{'a': 1, 'b': [{'foo':'bar', 'baz': 'qaax'}]},
|
||||
{'b': [{'foo':'bar', 'baz': 'qaax'}]}),
|
||||
("Remove item from a nested array",
|
||||
{'a': 1, 'b': [{'foo':'bar', 'baz': 'qux'}]},
|
||||
{'a': 1, 'b': [{'foo':'bar'}]},
|
||||
{'b': [{'foo':'bar'}]}),
|
||||
("Remove a nested item",
|
||||
{'a': 1, 'b': {'foo':'bar', 'baz': 'qux'}},
|
||||
{'a': 1, 'b': {'foo':'bar'}},
|
||||
{'b': {'baz': None}})
|
||||
]
|
||||
|
||||
|
||||
class TestPatch(unittest.TestCase):
|
||||
|
||||
def test_patch(self):
|
||||
for (msg, orig, mod, expected_patch) in TEST_CASES:
|
||||
self.assertEqual(expected_patch, makepatch(orig, mod), msg=msg)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user