Repository: robocorp/rpaframework

Issue:
User understandable error in case optional dependencies for google-oauth not installed
```
bash-5.1$ rpa-google-oauth --help
Traceback (most recent call last):
  File "/Users/kerkko/.robocorp/live/2035990f5bebdaa9/bin/rpa-google-oauth", line 5, in <module>
    from RPA.scripts.google_authenticate import start
  File "/Users/kerkko/.robocorp/live/2035990f5bebdaa9/lib/python3.7/site-packages/RPA/scripts/google_authenticate.py", line 5, in <module>
    from google_auth_oauthlib.flow import InstalledAppFlow
ModuleNotFoundError: No module named 'google_auth_oauthlib'
bash-5.1$
```


Discussion:

Resolving patch:
diff --git a/packages/main/src/RPA/scripts/google_authenticate.py b/packages/main/src/RPA/scripts/google_authenticate.py
--- a/packages/main/src/RPA/scripts/google_authenticate.py
+++ b/packages/main/src/RPA/scripts/google_authenticate.py
@@ -2,8 +2,28 @@
 import base64
 import os
 import pickle
-from google_auth_oauthlib.flow import InstalledAppFlow
+import sys
 
+from RPA.Cloud import Google
+
+
+DESCRIPTION = """
+Command-line utility for converting Google OAuth
+authorization credentials to an access token, which can be kept
+in a secret storage such as Robocorp Vault. This allows calling Google
+APIs without user interaction.
+
+example usage:
+> rpa-google-oauth --credentials drive_credentials.json --service drive
+"""
+
+CREDENTIALS = """
+Copy these credentials into Robocorp Vault:
+
+-------------------------------------------
+{}
+-------------------------------------------
+"""
 
 SERVICE_SCOPES = {
     "drive": ["drive.appdata", "drive.file", "drive.install", "drive"],
@@ -11,73 +31,78 @@
 }
 
 
-def get_arguments(parser):
+def create_parser():
+    parser = argparse.ArgumentParser(
+        description=DESCRIPTION.strip(), formatter_class=argparse.RawTextHelpFormatter
+    )
     parser.add_argument(
         "--credentials",
         dest="credentials_file",
         default="credentials.json",
-        help="project credentials file, by default tries to access file "
-        "from current directory",
+        help="project credentials file (default: %(default)s)",
     )
     parser.add_argument(
         "--scopes",
         dest="scopes",
-        default=None,
         help="authentication scopes as comma separated list",
     )
     parser.add_argument(
         "--service",
         dest="service",
-        default=None,
-        help="set authentication scopes for the given service, "
-        "supported services: drive,apps-script",
+        choices=SERVICE_SCOPES.keys(),
+        help="set authentication scopes for the given service",
     )
     parser.add_argument(
         "--console",
         dest="console_flow",
         action="store_true",
-        default=False,
-        help="use to run console based auth flow",
+        help="run console based auth flow (default: %(default)s)",
     )
-    return parser.parse_args()
+    return parser
 
 
-def start():
-    parser = argparse.ArgumentParser(description="Getting Google OAuth token")
-    args = get_arguments(parser)
+def main():
+    parser = create_parser()
+    args = parser.parse_args()
+
+    def error(msg):
+        parser.print_help()
+        print(f"\nERROR: {msg}\n")
+        sys.exit(1)
+
+    if Google.GOOGLECLOUD_IMPORT_ERROR:
+        error(
+            "Please install the 'google' optional extra to use this script:\n"
+            "> pip install rpaframework[google]"
+        )
+
     if (
         not os.path.exists(args.credentials_file)
         or os.stat(args.credentials_file).st_size == 0
     ):
-        print(
-            "WARNING: Credentials file '%s' does not exist or is empty\n"
-            % args.credentials_file
-        )
-        parser.print_help()
-        return
-    auth_scopes = []
-    if args.service and args.service in SERVICE_SCOPES.keys():
-        auth_scopes.extend(SERVICE_SCOPES[args.service])
+        error(f"Credentials file '{args.credentials_file}' does not exist or is empty")
+
+    scopes = []
+    if args.service and args.service in SERVICE_SCOPES:
+        scopes.extend(SERVICE_SCOPES[args.service])
     if args.scopes:
-        auth_scopes.extend(args.scopes.split(","))
-    if not auth_scopes:
-        print("WARNING: No authentication scopes have been defined!\n")
-        parser.print_help()
-        return
-    googlescopes = [f"https://www.googleapis.com/auth/{scope}" for scope in auth_scopes]
-    print("Google OAuth Flow for scopes: %s" % (",".join(auth_scopes)))
-    flow = InstalledAppFlow.from_client_secrets_file(
-        args.credentials_file, googlescopes
-    )
+        scopes.extend(args.scopes.split(","))
+
+    if not scopes:
+        error("No authentication scopes have been defined")
+
+    uris = [f"https://www.googleapis.com/auth/{scope}" for scope in scopes]
+    print("Google OAuth Flow for scopes: {}".format(", ".join(scopes)))
+
+    flow = Google.InstalledAppFlow.from_client_secrets_file(args.credentials_file, uris)
     if args.console_flow:
         credentials = flow.run_console()
     else:
         credentials = flow.run_local_server()
-    print(
-        "\nCopy these credentials into Robocloud Vault:\n%s%s%s"
-        % (
-            (40 * "-") + "\n",
-            str(base64.b64encode(pickle.dumps(credentials)), "utf-8"),
-            "\n" + (40 * "-") + "\n",
-        )
-    )
+
+    serialized = base64.b64encode(pickle.dumps(credentials)).decode("utf-8")
+    print(CREDENTIALS.format(serialized))
+
+
+if __name__ == "__main__":
+    main()


Test patch: