Repository: ynput/OpenPype
Issue: Ftrack delete action cause circular error

## Issue
Ftrack action `Delete Asset/Subsets` may cause `CircularDependencyError`. Not sure why is this really happening.

## Theory: 
Deletion of entity will mark for deletion it's assets -> asset versions -> component and also it's tasks. If task is assigned to same assets as task's parent entity then maybe ftrack api will mark to unset task from that asset which may cause circular error because asset is marked to delete and change in one call. Seems like can be fixed with deletion of tasks first, to avoid this issue. Require testing...

## Traceback:
```
Traceback (most recent call last):
  File "K:\\prod\\pype-setup/repos/pype/pype/ftrack/actions\\action_delete_asset.py", line 555, in launch
      self.session.commit()  File "K:\\prod\\pype-setup\\vendor\\python\\ftrack-python-api\\ftrack_api\\session.py", line 1260, in commit
          result = self.call(batch)  File "K:\\prod\\pype-setup\\vendor\\python\\ftrack-python-api\\ftrack_api\\session.py", line 1638, in call
              raise ftrack_api.exception.ServerError(error_message)ftrack_api.exception.ServerError: Server reported error: CircularDependencyError(Circular dependency detected. (DeleteState(<AssetVersion at 0x7f58f966c6d0>), DeleteState(<Task at 0x7f58f96d8590>), DeleteState(<FileComponent at 0x7f58f966c1d0>), DeleteState(<Asset at 0x7f58f96d8fd0>)))
```

Changed files:
openpype/modules/ftrack/event_handlers_user/action_delete_asset.py

diff --git a/openpype/modules/ftrack/event_handlers_user/action_delete_asset.py b/openpype/modules/ftrack/event_handlers_user/action_delete_asset.py
index ff39db4383..c20491349f 100644
--- a/openpype/modules/ftrack/event_handlers_user/action_delete_asset.py
+++ b/openpype/modules/ftrack/event_handlers_user/action_delete_asset.py
@@ -11,23 +11,28 @@ from avalon.api import AvalonMongoDB
 class DeleteAssetSubset(BaseAction):
     '''Edit meta data action.'''
 
-    #: Action identifier.
+    # Action identifier.
     identifier = "delete.asset.subset"
-    #: Action label.
+    # Action label.
     label = "Delete Asset/Subsets"
-    #: Action description.
+    # Action description.
     description = "Removes from Avalon with all childs and asset from Ftrack"
     icon = statics_icon("ftrack", "action_icons", "DeleteAsset.svg")
 
     settings_key = "delete_asset_subset"
-    #: Db connection
-    dbcon = AvalonMongoDB()
+    # Db connection
+    dbcon = None
 
     splitter = {"type": "label", "value": "---"}
     action_data_by_id = {}
     asset_prefix = "asset:"
     subset_prefix = "subset:"
 
+    def __init__(self, *args, **kwargs):
+        self.dbcon = AvalonMongoDB()
+
+        super(DeleteAssetSubset, self).__init__(*args, **kwargs)
+
     def discover(self, session, entities, event):
         """ Validation """
         task_ids = []
@@ -446,7 +451,14 @@ class DeleteAssetSubset(BaseAction):
         if len(assets_to_delete) > 0:
             map_av_ftrack_id = spec_data["without_ftrack_id"]
             # Prepare data when deleting whole avalon asset
-            avalon_assets = self.dbcon.find({"type": "asset"})
+            avalon_assets = self.dbcon.find(
+                {"type": "asset"},
+                {
+                    "_id": 1,
+                    "data.visualParent": 1,
+                    "data.ftrackId": 1
+                }
+            )
             avalon_assets_by_parent = collections.defaultdict(list)
             for asset in avalon_assets:
                 asset_id = asset["_id"]
@@ -537,11 +549,13 @@ class DeleteAssetSubset(BaseAction):
                 ftrack_proc_txt, ", ".join(ftrack_ids_to_delete)
             ))
 
-            ftrack_ents_to_delete = (
+            entities_by_link_len = (
                 self._filter_entities_to_delete(ftrack_ids_to_delete, session)
             )
-            for entity in ftrack_ents_to_delete:
-                session.delete(entity)
+            for link_len in sorted(entities_by_link_len.keys(), reverse=True):
+                for entity in entities_by_link_len[link_len]:
+                    session.delete(entity)
+
                 try:
                     session.commit()
                 except Exception:
@@ -600,29 +614,11 @@ class DeleteAssetSubset(BaseAction):
                 joined_ids_to_delete
             )
         ).all()
-        filtered = to_delete_entities[:]
-        while True:
-            changed = False
-            _filtered = filtered[:]
-            for entity in filtered:
-                entity_id = entity["id"]
-
-                for _entity in tuple(_filtered):
-                    if entity_id == _entity["id"]:
-                        continue
-
-                    for _link in _entity["link"]:
-                        if entity_id == _link["id"] and _entity in _filtered:
-                            _filtered.remove(_entity)
-                            changed = True
-                            break
-
-            filtered = _filtered
-
-            if not changed:
-                break
+        entities_by_link_len = collections.defaultdict(list)
+        for entity in to_delete_entities:
+            entities_by_link_len[len(entity["link"])].append(entity)
 
-        return filtered
+        return entities_by_link_len
 
     def report_handle(self, report_messages, project_name, event):
         if not report_messages: