Repository: firebase/firebase-admin-python (Python)

Issue:
Skip validation flag for None in db.update() 
### [REQUIRED] Step 2: Describe your environment

  * Operating System version: Ubuntu 16.04
  * Firebase SDK version: 2.10.0
  * Library version: _____
  * Firebase Product: database

### [REQUIRED] Step 3: Describe the problem
Updating a key with the value null is a valid way to delete data in Node, Java SDK.
However currently you cannot do this in Python as it breaks the validation mechanism. (`Dictionary must not contain None keys or values.`)
This is problematic when you are doing multi-path delete as it doesn't work right now in Python.
Is it possible to add a flag to the .update method to make it skip the validation?


#### Steps to reproduce:

```
update_statement = prepare_multipath_update(args)
db.reference().update(update_statement)

Traceback (most recent call last):
  File "/usr/lib/python3.5/runpy.py", line 184, in _run_module_as_main
    "__main__", mod_spec)
  File "/usr/lib/python3.5/runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "", line 60, in <module>
    db.reference().update(update_statement)
  File "../lib/python3.5/site-packages/firebase_admin/db.py", line 273, in update
    raise ValueError('Dictionary must not contain None keys or values.')
ValueError: Dictionary must not contain None keys or values.
```

I managed to get over the validation by using the internal methods of db.Reference

```
    db_ref = db.reference()
    db_ref._client.request('patch', db_ref._add_suffix(), json=update_statement)
```

It would be nice if there is a flag that can be provided to the update statement that skips the None validation.

E.g: `db.reference().update(update_statement, skip_none_validation=True)`


Discussion:
It is safe to remove the condition for checking `None` in the values set. We can include it in a future release.



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b886d5161..26877b525 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,7 @@
 # Unreleased
 
--
+- [fixed] The `db.Reference.update()` function now accepts dictionaries with
+  `None` values. This can be used to delete child keys from a reference.
 
 # v2.10.0
 
diff --git a/firebase_admin/db.py b/firebase_admin/db.py
index c29c30d72..5613a1e67 100644
--- a/firebase_admin/db.py
+++ b/firebase_admin/db.py
@@ -269,8 +269,8 @@ def update(self, value):
         """
         if not value or not isinstance(value, dict):
             raise ValueError('Value argument must be a non-empty dictionary.')
-        if None in value.keys() or None in value.values():
-            raise ValueError('Dictionary must not contain None keys or values.')
+        if None in value.keys():
+            raise ValueError('Dictionary must not contain None keys.')
         self._client.request('patch', self._add_suffix(), json=value, params='print=silent')
 
     def delete(self):


Test patch:
diff --git a/integration/test_db.py b/integration/test_db.py
index c3ba2e441..7d2726a76 100644
--- a/integration/test_db.py
+++ b/integration/test_db.py
@@ -155,9 +155,14 @@ def test_update_children(self, testref):
 
     def test_update_children_with_existing_values(self, testref):
         python = testref.parent
-        ref = python.child('users').push({'name' : 'Edwin Colbert', 'since' : 1900})
+        value = {'name' : 'Edwin Colbert', 'since' : 1900, 'temp': True}
+        ref = python.child('users').push(value)
         ref.update({'since' : 1905})
-        assert ref.get() == {'name' : 'Edwin Colbert', 'since' : 1905}
+        value['since'] = 1905
+        assert ref.get() == value
+        ref.update({'temp': None})
+        del value['temp']
+        assert ref.get() == value
 
     def test_update_nested_children(self, testref):
         python = testref.parent
diff --git a/tests/test_db.py b/tests/test_db.py
index 145480faa..1bbd4c722 100644
--- a/tests/test_db.py
+++ b/tests/test_db.py
@@ -259,9 +259,9 @@ def test_set_non_json_value(self, value):
         with pytest.raises(TypeError):
             ref.set(value)
 
-    def test_update_children(self):
+    @pytest.mark.parametrize('data', [{'foo': 'bar'}, {'foo': None}])
+    def test_update_children(self, data):
         ref = db.reference('/test')
-        data = {'foo' : 'bar'}
         recorder = self.instrument(ref, json.dumps(data))
         ref.update(data)
         assert len(recorder) == 1
@@ -317,21 +317,15 @@ def test_set_if_unchanged_non_json_value(self, value):
         with pytest.raises(TypeError):
             ref.set_if_unchanged(MockAdapter.ETAG, value)
 
-    def test_update_children_default(self):
-        ref = db.reference('/test')
-        recorder = self.instrument(ref, '')
-        with pytest.raises(ValueError):
-            ref.update({})
-        assert len(recorder) is 0
-
     @pytest.mark.parametrize('update', [
-        None, {}, {None:'foo'}, {'foo': None}, '', 'foo', 0, 1, list(), tuple(), _Object()
+        None, {}, {None:'foo'}, '', 'foo', 0, 1, list(), tuple(), _Object()
     ])
     def test_set_invalid_update(self, update):
         ref = db.reference('/test')
-        self.instrument(ref, '')
+        recorder = self.instrument(ref, '')
         with pytest.raises(ValueError):
             ref.update(update)
+        assert len(recorder) is 0
 
     @pytest.mark.parametrize('data', valid_values)
     def test_push(self, data):