Repository: MongoEngine/mongoengine

Issue:
Bug with numerical keys in MapField(EmbeddedDocumentField())
I try to store EmbeddedDocument indexed by numerical strings inside a MapField.

Mongoengine doesn't seem to allow modification of those EmbeddedDocument when they are indexed by numerical strings.

Example:

```
import mongoengine


mongoengine.connect('mongoengine_test')


class EmbeddedDoc(mongoengine.EmbeddedDocument):

    name = mongoengine.StringField()


class Doc(mongoengine.Document):

    maps = mongoengine.MapField(
        mongoengine.EmbeddedDocumentField(EmbeddedDoc)
    )


if __name__ == '__main__':
    doc = Doc()
    doc.maps['234'] = EmbeddedDoc(name='embedded')
    doc.save()
    doc.maps['234'].name = 'embed'
    doc.save()
```

The second save() raise an exception:

```
Traceback (most recent call last):
  File "mongoengine-bug.py", line 21, in <module>
    doc.save()
  File "/usr/local/lib/python2.7/dist-packages/mongoengine/document.py", line 228, in save
    updates, removals = self._delta()
  File "/usr/local/lib/python2.7/dist-packages/mongoengine/base.py", line 1197, in _delta
    d = d[int(p)]
KeyError: 234
```

It seems the problem can be solved by changing code in the `_delta` method of BaseDocument.
Instead of doing:

```
if p.isdigit():
    d = d[int(p)]
```

I think it's possible to do something like:

```
if p.isdigit():
    try:
        d = d[int(p)]
    except IndexError:
        # fall to default dictionary case...
```

I tried something like that and it didn't seem to break the tests.

I can submit a patch to correct the problem, however there might be other issues somewhere else (during django-style query interpretation, for instance)



Discussion:

Resolving patch:
diff --git a/mongoengine/base.py b/mongoengine/base.py
--- a/mongoengine/base.py
+++ b/mongoengine/base.py
@@ -1193,7 +1193,7 @@ def _delta(self):
                 for p in parts:
                     if isinstance(d, DBRef):
                         break
-                    elif p.isdigit():
+                    elif isinstance(d, list) and p.isdigit():
                         d = d[int(p)]
                     elif hasattr(d, 'get'):
                         d = d.get(p)
@@ -1224,7 +1224,7 @@ def _delta(self):
                 parts = path.split('.')
                 db_field_name = parts.pop()
                 for p in parts:
-                    if p.isdigit():
+                    if isinstance(d, list) and p.isdigit():
                         d = d[int(p)]
                     elif (hasattr(d, '__getattribute__') and
                           not isinstance(d, dict)):


Test patch:
diff --git a/tests/test_fields.py b/tests/test_fields.py
--- a/tests/test_fields.py
+++ b/tests/test_fields.py
@@ -965,6 +965,24 @@ class Test(Document):
         doc = self.db.test.find_one()
         self.assertEqual(doc['x']['DICTIONARY_KEY']['i'], 2)
 
+    def test_mapfield_numerical_index(self):
+        """Ensure that MapField accept numeric strings as indexes."""
+        class Embedded(EmbeddedDocument):
+            name = StringField()
+
+        class Test(Document):
+            my_map = MapField(EmbeddedDocumentField(Embedded))
+
+        Test.drop_collection()
+
+        test = Test()
+        test.my_map['1'] = Embedded(name='test')
+        test.save()
+        test.my_map['1'].name = 'test updated'
+        test.save()
+
+        Test.drop_collection()
+
     def test_map_field_lookup(self):
         """Ensure MapField lookups succeed on Fields without a lookup method"""