Repository: Cog-Creators/Red-DiscordBot

Issue:
[V3] redbot-setup errors when importing data from MongoDB to JSON
# Other bugs

<!-- 
Did you find a bug with something other than a command? Fill out the following:
-->

#### What were you trying to do?

Edit an existing instance using `redbot-setup --edit`, switch from MongoDB to JSON and import the data

#### What were you expecting to happen?

For it to work

#### What actually happened?

Produced an error
```
Would you like to import your data? (y/n) y
Traceback (most recent call last):
  File "/home/red/.pyenv/versions/3.7.1/bin/redbot-setup", line 11, in <module>
    load_entry_point('Red-DiscordBot==3.0.0rc3.post1', 'console_scripts', 'redbot-setup')()
  File "/home/red/.pyenv/versions/3.7.1/lib/python3.7/site-packages/redbot/setup.py", line 378, in main
    loop.run_until_complete(edit_instance())
  File "/home/red/.pyenv/versions/3.7.1/lib/python3.7/asyncio/base_events.py", line 573, in run_until_complete
    return future.result()
  File "/home/red/.pyenv/versions/3.7.1/lib/python3.7/site-packages/redbot/setup.py", line 278, in edit_instance
    await mongo_to_json(current_data_dir, storage_details)
  File "/home/red/.pyenv/versions/3.7.1/lib/python3.7/site-packages/redbot/setup.py", line 201, in mongo_to_json
    collection_names = await db.collection_names(include_system_collections=False)
  File "/home/red/.pyenv/versions/3.7.1/lib/python3.7/site-packages/motor/core.py", line 554, in __call__
    self.delegate.name)
TypeError: MotorCollection object is not callable. If you meant to call the 'collection_names' method on a MotorCollection object it is failing because no such method exists. 
```

#### How can we reproduce this issue?

Have an existing instance configured to use MongoDB. Edit the instance, change the storage backend to JSON, import the data



Discussion:

Resolving patch:
diff --git a/redbot/setup.py b/redbot/setup.py
--- a/redbot/setup.py
+++ b/redbot/setup.py
@@ -114,7 +114,7 @@ def get_storage_type():
         print()
         print("Please choose your storage backend (if you're unsure, choose 1).")
         print("1. JSON (file storage, requires no database).")
-        print("2. MongoDB")
+        print("2. MongoDB (not recommended, currently unstable)")
         storage = input("> ")
         try:
             storage = int(storage)
@@ -198,22 +198,23 @@ async def mongo_to_json(current_data_dir: Path, storage_details: dict):
 
     m = Mongo("Core", "0", **storage_details)
     db = m.db
-    collection_names = await db.collection_names(include_system_collections=False)
-    for c_name in collection_names:
-        if c_name == "Core":
+    collection_names = await db.list_collection_names()
+    for collection_name in collection_names:
+        if collection_name == "Core":
             c_data_path = current_data_dir / "core"
         else:
-            c_data_path = current_data_dir / "cogs/{}".format(c_name)
-        output = {}
-        docs = await db[c_name].find().to_list(None)
-        c_id = None
-        for item in docs:
-            item_id = item.pop("_id")
-            if not c_id:
-                c_id = str(hash(item_id))
-            output[item_id] = item
-        target = JSON(c_name, c_id, data_path_override=c_data_path)
-        await target.jsonIO._threadsafe_save_json(output)
+            c_data_path = current_data_dir / "cogs" / collection_name
+        c_data_path.mkdir(parents=True, exist_ok=True)
+        # Every cog name has its own collection
+        collection = db[collection_name]
+        async for document in collection.find():
+            # Every cog has its own document.
+            # This means if two cogs have the same name but different identifiers, they will
+            # be two separate documents in the same collection
+            cog_id = document.pop("_id")
+            driver = JSON(collection_name, cog_id, data_path_override=c_data_path)
+            for key, value in document.items():
+                await driver.set(key, value=value)
 
 
 async def edit_instance():


Test patch: