Repository: OpenMined/PySyft

Issue:
Add ability to load multiple libs in one call
## Description
`sy.load()` should accept any valid sequence of strings, variadic args, a list, tuple or set of strings like:
```
sy.load("pydp", "opacus")
sy.load(["pydp", "opacus"])
sy.load(("pydp", "opacus"))
sy.load({"pydp", "opacus"})
```

## Definition of Done
Loading multiple libs in one go is possible with tests to show it works.


Discussion:

Resolving patch:
diff --git a/src/syft/lib/__init__.py b/src/syft/lib/__init__.py
--- a/src/syft/lib/__init__.py
+++ b/src/syft/lib/__init__.py
@@ -5,7 +5,11 @@
 from typing import Any
 from typing import Any as TypeAny
 from typing import Dict as TypeDict
+from typing import Iterable
+from typing import List as TypeList
 from typing import Optional
+from typing import Set as TypeSet
+from typing import Tuple as TypeTuple
 from typing import Union as TypeUnion
 import warnings
 
@@ -142,20 +146,42 @@ def _load_lib(*, lib: str, options: TypeDict[str, TypeAny] = {}) -> None:
             _regenerate_unions(lib_ast=lib_ast, client=client)
 
 
-def load(lib: str, options: TypeDict[str, TypeAny] = {}) -> None:
+def load(
+    *libs: TypeUnion[TypeList[str], TypeTuple[str], TypeSet[str], str],
+    options: TypeDict[str, TypeAny] = {},
+    **kwargs: str,
+) -> None:
     """
     Load and Update Node with given library module
 
     Args:
-        lib: name of library to load and update Node with
+        *libs: names of libraries to load and update Node with (can be variadic, tuple, list, set)
         options: external requirements for loading library successfully
+        **kwargs: for backward compatibility with calls like `syft.load(lib = "opacus")`
     """
-    try:
-        _load_lib(lib=lib, options=options)
-    except VendorLibraryImportException as e:
-        critical(e)
-    except Exception as e:
-        critical(f"Unable to load package support for: {lib}. {e}")
+    # For backward compatibility with calls like `syft.load(lib = "opacus")`
+    if "lib" in kwargs.keys():
+        libs += tuple(kwargs["lib"])
+
+    if isinstance(libs[0], Iterable):
+        if not isinstance(libs[0], str):
+            libs = tuple(libs[0])
+        for lib in libs:
+            if isinstance(lib, str):
+                try:
+                    _load_lib(lib=str(lib), options=options)
+                except VendorLibraryImportException as e:
+                    critical(e)
+                except Exception as e:
+                    critical(f"Unable to load package support for: {lib}. {e}")
+            else:
+                critical(
+                    f"Unable to load package support for: {lib}. Pass lib name as string object."
+                )
+    else:
+        critical(
+            "Unable to load package support for any library. Iterable object not found."
+        )
 
 
 def load_lib(lib: str, options: TypeDict[str, TypeAny] = {}) -> None:


Test patch:
diff --git a/tests/syft/api/load_lib_test.py b/tests/syft/api/load_lib_test.py
new file mode 100644
--- /dev/null
+++ b/tests/syft/api/load_lib_test.py
@@ -0,0 +1,36 @@
+# stdlib
+import logging
+
+# third party
+import _pytest
+
+# syft absolute
+import syft as sy
+
+
+def test_load_lib() -> None:
+    assert sy.lib.load(lib="tenseal") is None
+    assert sy.lib.load("tenseal", "opacus") is None
+    assert sy.lib.load(["tenseal", "opacus"]) is None
+    assert sy.lib.load(("tenseal", "opacus")) is None
+    assert sy.lib.load({"tenseal"}) is None
+
+
+def test_load_errors(caplog: _pytest.logging.LogCaptureFixture) -> None:
+    # Error if a non-supported library is loaded
+    with caplog.at_level(logging.CRITICAL, logger="syft.logger"):
+        sy.lib.load("non_compatible")
+    assert "Unable to load package support for: non_compatible." in caplog.text
+    caplog.clear()
+
+    # Error if non-string object type is attempted to be loaded
+    with caplog.at_level(logging.CRITICAL, logger="syft.logger"):
+        sy.lib.load([True])
+    assert "Unable to load package support for: True." in caplog.text
+    caplog.clear()
+
+    # Error if a non-iterable object is passed
+    with caplog.at_level(logging.CRITICAL, logger="syft.logger"):
+        sy.lib.load(True)
+    assert "Unable to load package support for any library." in caplog.text
+    caplog.clear()