Repository: log2timeline/dftimewolf (language: python)

Issue:
Create Turbinia API processor modules
Create new processor modules to integrate dftimewolf with Turbinia using Turbinia's new API server.


Discussion / hints:
General approach will be to use the ```turbinia-api-lib``` library to make API calls to the Turbinia API server and keep dependencies to a minimum. 

As a first step, I intend to create new processor modules instead of ripping and replacing the existing ones to provide backwards compatibility for existing clients/users. One idea is to expose a flag in the recipes that use Turbinia modules to indicate whether the new or current/existing client would be used.

This will allow us to easily switch clients if needed. The only drawback is that we willl not be able to immediately remove```turbinia``` or the various GCP packages pinning from the dependencies. That would be possible once we deprecate the current Turbinia client and dftimewolf turbinia processors.

@tomchop thoughts?
> One idea is to expose a flag in the recipes that use Turbinia modules to indicate whether the new or current/existing client would be used.

This wouldn't work, as flags can't really decide which module is used - you'd have to create a module which uses the two clients and choses which one to use depending on the flag (which defeats the purpose of not having to import legacy modules).

At this point I think that we're getting more brekages because of the existence of the legacy turbinia client than what we would get if we decided to migrate everything at once. (see https://github.com/log2timeline/dftimewolf/issues/660) I'd suggest we move forwrad and default to the new Turbinia API for all Turbinia modules.

To preserve backwards compatibility for users who have a working setup with the old turbinia modules, we can rename existing recipes to something like `_legacy` and keep the old Turbinia modules in the codebase with a deprecation note. Additionally, we can tweak `pyproject.toml` to add an `extras` section for the old turbinia modules: https://python-poetry.org/docs/pyproject/#extras


Fix patch:
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 321417977..3c10a0660 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -7,5 +7,7 @@
         "*.py"
     ],
     "python.testing.pytestEnabled": false,
-    "python.testing.unittestEnabled": true
+    "python.testing.unittestEnabled": true,
+    "python.linting.pylintEnabled": true,
+    "python.linting.enabled": true
 }
diff --git a/data/config.json b/data/config.json
index 8cb963aa1..3dee24df9 100644
--- a/data/config.json
+++ b/data/config.json
@@ -1,5 +1,5 @@
 {
-  "ts_username": "your_username",
-  "ts_password": "your_password",
-  "ts_endpoint": "http://127.0.0.1:80"
-}
+  "ts_username": "timesketch",
+  "ts_password": "timesketch",
+  "ts_endpoint": "http://127.0.0.1:5000"
+}
\ No newline at end of file
diff --git a/data/recipes/aws_turbinia_ts.json b/data/recipes/aws_turbinia_ts.json
index 19858c4d8..aed258f10 100644
--- a/data/recipes/aws_turbinia_ts.json
+++ b/data/recipes/aws_turbinia_ts.json
@@ -63,11 +63,14 @@
       "wants": ["GCEDiskFromImage"],
       "name": "TurbiniaGCPProcessor",
       "args": {
-        "turbinia_config_file": null,
         "disk_names": null,
-        "project": "@gcp_project",
+        "request_ids": "@request_ids",
+        "project": "@analysis_project_name",
+        "turbinia_auth": "@turbinia_auth",
         "turbinia_recipe": "@turbinia_recipe",
         "turbinia_zone": "@turbinia_zone",
+        "turbinia_api": "@turbinia_api",
+        "incident_id": "@incident_id",
         "sketch_id": "@sketch_id"
       }
     }, {
@@ -94,8 +97,11 @@
     ["--incident_id", "Incident ID (used for Timesketch description).", null],
     ["--sketch_id", "Timesketch sketch to which the timeline should be added.", null, {"format": "regex", "regex": "^\\d+$"}],
     ["--token_password", "Optional custom password to decrypt Timesketch credential file with.", ""],
+    ["--request_ids", "Comma separated Turbinia request identifiers to process.", null, {"format": "regex", "comma_separated": true, "regex": "^[a-f0-9]{32}$"}],
     ["--turbinia_recipe", "The Turbinia recipe name to use for evidence processing.", null],
     ["--turbinia_zone", "Zone Turbinia is located in", "us-central1-f", {"format": "gcp_zone"}],
+    ["--turbinia_auth", "Flag to indicate whether Turbinia API server requires authentication.", false],
+    ["--turbinia_api", "Turbinia API server endpoint.", "http://127.0.0.1:8000"],
     ["--wait_for_timelines", "Whether to wait for Timesketch to finish processing all timelines.", true]
   ]
 }
diff --git a/data/recipes/aws_turbinia_ts_legacy.json b/data/recipes/aws_turbinia_ts_legacy.json
new file mode 100644
index 000000000..283069ffb
--- /dev/null
+++ b/data/recipes/aws_turbinia_ts_legacy.json
@@ -0,0 +1,101 @@
+{
+  "name": "aws_turbinia_ts_legacy",
+  "short_description": "Copies EBS volumes from within AWS, transfers them to GCP, analyses with Turbinia and exports the results to Timesketch.",
+  "description": "Copies EBS volumes from within AWS, uses buckets and cloud-to-cloud operations to transfer the data to GCP. Once in GCP, a persistent disk is created and a job is added to the Turbinia queue to start analysis. The resulting Plaso file is then exported to Timesketch.",
+  "preflights": [
+    {
+      "wants": [],
+      "name": "AWSAccountCheck",
+      "args": {
+        "profile_name": "@aws_profile"
+      }
+    },{
+      "wants": [],
+      "name": "GCPTokenCheck",
+      "args": {
+        "project_name": "@gcp_project"
+      }
+    }
+  ],
+  "modules": [
+    {
+      "wants": [],
+      "name": "AWSVolumeSnapshotCollector",
+      "args": {
+        "volumes": "@volumes",
+        "region": "@aws_region"
+      }
+    },{
+      "wants": ["AWSVolumeSnapshotCollector"],
+      "name": "AWSSnapshotS3CopyCollector",
+      "args": {
+        "snapshots": "",
+        "bucket": "@aws_bucket",
+        "region": "@aws_region",
+        "subnet": "@subnet"
+      }
+    },{
+      "wants": ["AWSSnapshotS3CopyCollector"],
+      "name": "S3ToGCSCopy",
+      "args": {
+        "s3_objects": "",
+        "aws_region": "@aws_region",
+        "dest_project": "@gcp_project",
+        "dest_bucket": "@gcp_bucket",
+        "object_filter": ".+/image.bin$"
+      }
+    }, {
+      "wants": ["S3ToGCSCopy"],
+      "name": "GCSToGCEImage",
+      "args": {
+        "source_objects": "",
+        "dest_project": "@gcp_project"
+      }
+    }, {
+      "wants": ["GCSToGCEImage"],
+      "name": "GCEDiskFromImage",
+      "args": {
+        "source_images": "",
+        "dest_project": "@gcp_project",
+        "dest_zone": "@gcp_zone"
+      }
+    }, {
+      "wants": ["GCEDiskFromImage"],
+      "name": "TurbiniaGCPProcessorLegacy",
+      "args": {
+        "turbinia_config_file": null,
+        "disk_names": null,
+        "project": "@gcp_project",
+        "turbinia_recipe": "@turbinia_recipe",
+        "turbinia_zone": "@turbinia_zone",
+        "sketch_id": "@sketch_id"
+      }
+    }, {
+      "wants": ["TurbiniaGCPProcessorLegacy"],
+      "name": "TimesketchExporter",
+      "args": {
+        "incident_id": "@incident_id",
+        "token_password": "@token_password",
+        "sketch_id": "@sketch_id",
+        "analyzers": null,
+        "wait_for_timelines": "@wait_for_timelines"
+      }
+    }
+  ],
+  "args": [
+    ["aws_region", "AWS region containing the EBS volumes.", null, {"format": "aws_region"}],
+    ["gcp_zone", "Destination GCP zone in which to create the disks.", null, {"format": "gcp_zone"}],
+    ["volumes", "Comma separated list of EBS volume IDs (e.g. vol-xxxxxxxx).", null, {"format": "regex", "comma_separated": true, "regex": "^vol-[0-9a-f]{8,17}$"}],
+    ["aws_bucket", "AWS bucket for image storage.", null, {"format": "regex", "comma_separated": false, "regex": "^(s3:\/\/)?[0-9a-z][-\\.0-9a-z]{1,61}[0-9a-z]$"}],
+    ["gcp_bucket", "GCP bucket for image storage.", null, {"format": "regex", "comma_separated": false, "regex": "^(gs:\/\/)?[0-9a-z][-\\.0-9a-z_]{1,61}[0-9a-z]$"}],
+    ["--subnet", "AWS subnet to copy instances from, required if there is no default subnet in the volume region.", null, {"format": "regex", "comma_separated": false, "regex": "^subnet-[0-9a-f]{8,17}$"}],
+    ["--gcp_project", "Destination GCP project.", null, {"format": "regex", "comma_separated": false, "regex": "^[a-z][-a-z0-9]{4,28}[a-z0-9]$"}],
+    ["--aws_profile", "Source AWS profile.", null],
+    ["--incident_id", "Incident ID (used for Timesketch description).", null],
+    ["--sketch_id", "Timesketch sketch to which the timeline should be added.", null, {"format": "regex", "regex": "^\\d+$"}],
+    ["--token_password", "Optional custom password to decrypt Timesketch credential file with.", ""],
+    ["--turbinia_recipe", "The Turbinia recipe name to use for evidence processing.", null],
+    ["--turbinia_zone", "Zone Turbinia is located in", "us-central1-f", {"format": "gcp_zone"}],
+    ["--wait_for_timelines", "Whether to wait for Timesketch to finish processing all timelines.", true]
+  ]
+}
diff --git a/data/recipes/gcp_turbinia_disk_copy_ts.json b/data/recipes/gcp_turbinia_disk_copy_ts.json
index cd331cb2c..30a8c0c6b 100644
--- a/data/recipes/gcp_turbinia_disk_copy_ts.json
+++ b/data/recipes/gcp_turbinia_disk_copy_ts.json
@@ -48,11 +48,14 @@
         "wants": ["GCEDiskCopy"],
         "name": "TurbiniaGCPProcessor",
         "args": {
-            "turbinia_config_file": null,
             "disk_names": null,
+            "request_ids": "@request_ids",
             "project": "@analysis_project_name",
+            "turbinia_auth": "@turbinia_auth",
             "turbinia_recipe": "@turbinia_recipe",
             "turbinia_zone": "@turbinia_zone",
+            "turbinia_api": "@turbinia_api",
+            "incident_id": "@incident_id",
             "sketch_id": "@sketch_id"
         }
     }, {
@@ -69,8 +72,11 @@
     "args": [
         ["source_project_name", "Name of the project containing the instance / disks to copy.", null, {"format": "regex", "comma_separated": false, "regex": "^[a-z][-a-z0-9]{4,28}[a-z0-9]$"}],
         ["analysis_project_name", "Name of the project containing the Turbinia instance.", null, {"format": "regex", "comma_separated": false, "regex": "^[a-z][-a-z0-9]{4,28}[a-z0-9]$"}],
+        ["--request_ids", "Comma separated Turbinia request identifiers to process.", null, {"format": "regex", "comma_separated": true, "regex": "^[a-f0-9]{32}$"}],
         ["--turbinia_recipe", "The Turbinia recipe name to use for evidence processing.", null],
         ["--turbinia_zone", "The GCP zone the disk to process and Turbinia workers are in.", "us-central1-f", {"format": "gcp_zone"}],
+        ["--turbinia_auth", "Flag to indicate whether Turbinia API server requires authentication.", false],
+        ["--turbinia_api", "Turbinia API server endpoint.", "http://127.0.0.1:8000"],
         ["--incident_id", "Incident ID (used for Timesketch description and to label the VM with).", null],
         ["--sketch_id", "Timesketch sketch to which the timeline should be added.", null, {"format": "regex", "regex": "^\\d+$"}],
         ["--token_password", "Optional custom password to decrypt Timesketch credential file with.", ""],
diff --git a/data/recipes/gcp_turbinia_disk_copy_ts_legacy.json b/data/recipes/gcp_turbinia_disk_copy_ts_legacy.json
new file mode 100644
index 000000000..95a80a93f
--- /dev/null
+++ b/data/recipes/gcp_turbinia_disk_copy_ts_legacy.json
@@ -0,0 +1,89 @@
+{
+    "name": "gcp_turbinia_disk_copy_ts_legacy",
+    "short_description": "Imports a remote GCP persistent disk, processes it with Turbinia and sends results to Timesketch.",
+    "description": "Imports a remote GCP persistent disk into an analysis GCP project and sends the result of Turbinia processing to Timesketch.\n\n- Copies a disk from a remote GCP project into an analysis project\n- Creates Turbinia processing request to process the imported disk\n- Downloads and sends results of the Turbinia processing to Timesketch.\n\nThis recipe will also start an analysis VM in the destination project with the attached disk (the same one that Turbinia will have processed). If the target disk is already in the same project as Turbinia, you can use the `gcp_turbinia_ts` recipe.",
+    "preflights": [{
+      "wants": [],
+      "name": "GCPTokenCheck",
+      "runtime_name": "GCPTokenCheck-analysis",
+      "args": {
+          "project_name": "@analysis_project_name"
+      }
+    },
+    {
+      "wants": [],
+      "name": "GCPTokenCheck",
+      "runtime_name": "GCPTokenCheck-source",
+      "args": {
+          "project_name": "@source_project_name"
+      }
+    }],
+    "modules": [{
+        "wants": [],
+        "name": "GCEDiskCopy",
+        "args": {
+          "destination_project_name": "@analysis_project_name",
+          "source_project_name": "@source_project_name",
+          "destination_zone": "@turbinia_zone",
+          "remote_instance_names": "@instances",
+          "disk_names": "@disks",
+          "all_disks": "@all_disks",
+          "stop_instances": "@stop_instances"
+        }
+      }, {
+        "wants": ["GCEDiskCopy"],
+        "name": "GCEForensicsVM",
+        "args": {
+          "project_name": "@analysis_project_name",
+          "incident_id": "@incident_id",
+          "zone": "@turbinia_zone",
+          "boot_disk_size": "@boot_disk_size",
+          "boot_disk_type": "@boot_disk_type",
+          "cpu_cores": "@cpu_cores",
+          "image_project": "ubuntu-os-cloud",
+          "image_family": "ubuntu-1804-lts",
+          "create_analysis_vm": "@create_analysis_vm"
+        }
+      }, {
+        "wants": ["GCEDiskCopy"],
+        "name": "TurbiniaGCPProcessorLegacy",
+        "args": {
+            "turbinia_config_file": null,
+            "disk_names": null,
+            "project": "@analysis_project_name",
+            "turbinia_recipe": "@turbinia_recipe",
+            "turbinia_zone": "@turbinia_zone",
+            "sketch_id": "@sketch_id"
+        }
+    }, {
+        "wants": ["TurbiniaGCPProcessorLegacy"],
+        "name": "TimesketchExporter",
+        "args": {
+            "incident_id": "@incident_id",
+            "token_password": "@token_password",
+            "sketch_id": "@sketch_id",
+            "analyzers": null,
+            "wait_for_timelines": "@wait_for_timelines"
+        }
+    }],
+    "args": [
+        ["source_project_name", "Name of the project containing the instance / disks to copy.", null, {"format": "regex", "comma_separated": false, "regex": "^[a-z][-a-z0-9]{4,28}[a-z0-9]$"}],
+        ["analysis_project_name", "Name of the project containing the Turbinia instance.", null, {"format": "regex", "comma_separated": false, "regex": "^[a-z][-a-z0-9]{4,28}[a-z0-9]$"}],
+        ["--turbinia_recipe", "The Turbinia recipe name to use for evidence processing.", null],
+        ["--turbinia_zone", "The GCP zone the disk to process and Turbinia workers are in.", "us-central1-f", {"format": "gcp_zone"}],
+        ["--incident_id", "Incident ID (used for Timesketch description and to label the VM with).", null],
+        ["--sketch_id", "Timesketch sketch to which the timeline should be added.", null, {"format": "regex", "regex": "^\\d+$"}],
+        ["--token_password", "Optional custom password to decrypt Timesketch credential file with.", ""],
+        ["--create_analysis_vm", "Create an analysis VM in the destination project.", true],
+        ["--wait_for_timelines", "Whether to wait for Timesketch to finish processing all timelines.", true],
+        ["--instances", "Name of the instances to analyze.", null],
+        ["--disks", "Comma-separated list of disks to copy from the source GCP project (if `instance` not provided).", null],
+        ["--all_disks", "Copy all disks in the designated instance. Overrides disk_names if specified.", false],
+        ["--stop_instances", "Stop the designated instances after copying disks.", false],
+        ["--cpu_cores", "Number of CPU cores of the analysis VM.", 4, {"format": "regex", "regex": "^\\d+$"}],
+        ["--boot_disk_size", "The size of the analysis VM boot disk (in GB).", 50, {"format": "regex", "regex": "^\\d+$"}],
+        ["--boot_disk_type", "Disk type to use [pd-standard, pd-ssd]", "pd-standard", {"format": "regex", "regex": "^pd-((ssd)|(standard))$"}],
+        ["--image_project", "Name of the project where the analysis VM image is hosted.", "ubuntu-os-cloud"],
+        ["--image_family", "Name of the image to use to create the analysis VM.", "ubuntu-1804-lts"]
+    ]
+}
diff --git a/data/recipes/gcp_turbinia_ts.json b/data/recipes/gcp_turbinia_ts.json
index 3aa096b31..b8ebd081f 100644
--- a/data/recipes/gcp_turbinia_ts.json
+++ b/data/recipes/gcp_turbinia_ts.json
@@ -13,11 +13,14 @@
         "wants": [],
         "name": "TurbiniaGCPProcessor",
         "args": {
-            "turbinia_config_file": null,
+            "request_ids": "@request_ids",
             "disk_names": "@disk_names",
             "project": "@analysis_project_name",
+            "turbinia_auth": "@turbinia_auth",
             "turbinia_recipe": "@turbinia_recipe",
             "turbinia_zone": "@turbinia_zone",
+            "turbinia_api": "@turbinia_api",
+            "incident_id": "@incident_id",
             "sketch_id": "@sketch_id"
         }
     }, {
@@ -34,11 +37,14 @@
     "args": [
         ["analysis_project_name", "Name of GCP project the disk exists in.", null, {"format": "regex", "comma_separated": false, "regex": "^[a-z][-a-z0-9]{4,28}[a-z0-9]$"}],
         ["turbinia_zone", "The GCP zone the disk to process (and Turbinia workers) are in.", null, {"format": "gcp_zone"}],
-        ["disk_names", "Names of GCP persistent disks to process.", null, {"format": "regex", "comma_separated": true, "regex": "^[a-z][-a-z0-9]{0,61}[a-z0-9]?$"}],
+        ["--disk_names", "Comma separated names of GCP persistent disks to process. This parameter can only be used if --request_ids is not provided.", null, {"format": "regex", "comma_separated": true, "regex": "^[a-z][-a-z0-9]{0,61}[a-z0-9]?$"}],
+        ["--request_ids", "Comma separated Turbinia request identifiers to process. This parameter can only be used if --disk_names is not provided.", null, {"format": "regex", "comma_separated": true, "regex": "^[a-f0-9]{32}$"}],
         ["--incident_id", "Incident ID (used for Timesketch description).", null],
         ["--sketch_id", "Timesketch sketch to which the timeline should be added.", null, {"format": "regex", "regex": "^\\d+$"}],
         ["--token_password", "Optional custom password to decrypt Timesketch credential file with.", ""],
         ["--turbinia_recipe", "The Turbinia recipe name to use for evidence processing.", null],
+        ["--turbinia_auth", "Flag to indicate whether Turbinia API server requires authentication.", false],
+        ["--turbinia_api", "Turbinia API server endpoint.", "http://127.0.0.1:8000"],
         ["--wait_for_timelines", "Whether to wait for Timesketch to finish processing all timelines.", true]
     ]
 }
diff --git a/data/recipes/gcp_turbinia_ts_legacy.json b/data/recipes/gcp_turbinia_ts_legacy.json
new file mode 100644
index 000000000..cfeb2d2b5
--- /dev/null
+++ b/data/recipes/gcp_turbinia_ts_legacy.json
@@ -0,0 +1,44 @@
+{
+    "name": "gcp_turbinia_ts_legacy",
+    "short_description": "Processes existing GCP persistent disks with Turbinia project and sends results to Timesketch.",
+    "description": "Process GCP persistent disks with Turbinia and send output to Timesketch.\n\nThis processes disks that are already in the project where Turbinia exists. If you want to copy disks from another project, use the `gcp_turbinia_disk_copy_ts` recipe.",
+    "preflights": [{
+      "wants": [],
+      "name": "GCPTokenCheck",
+      "args": {
+          "project_name": "@analysis_project_name"
+      }
+    }],
+    "modules": [{
+        "wants": [],
+        "name": "TurbiniaGCPProcessorLegacy",
+        "args": {
+            "turbinia_config_file": null,
+            "disk_names": "@disk_names",
+            "project": "@analysis_project_name",
+            "turbinia_recipe": "@turbinia_recipe",
+            "turbinia_zone": "@turbinia_zone",
+            "sketch_id": "@sketch_id"
+        }
+    }, {
+        "wants": ["TurbiniaGCPProcessorLegacy"],
+        "name": "TimesketchExporter",
+        "args": {
+            "incident_id": "@incident_id",
+            "token_password": "@token_password",
+            "sketch_id": "@sketch_id",
+            "analyzers": null,
+            "wait_for_timelines": "@wait_for_timelines"
+        }
+    }],
+    "args": [
+        ["analysis_project_name", "Name of GCP project the disk exists in.", null, {"format": "regex", "comma_separated": false, "regex": "^[a-z][-a-z0-9]{4,28}[a-z0-9]$"}],
+        ["turbinia_zone", "The GCP zone the disk to process (and Turbinia workers) are in.", null, {"format": "gcp_zone"}],
+        ["disk_names", "Names of GCP persistent disks to process.", null, {"format": "regex", "comma_separated": true, "regex": "^[a-z][-a-z0-9]{0,61}[a-z0-9]?$"}],
+        ["--incident_id", "Incident ID (used for Timesketch description).", null],
+        ["--sketch_id", "Timesketch sketch to which the timeline should be added.", null, {"format": "regex", "regex": "^\\d+$"}],
+        ["--token_password", "Optional custom password to decrypt Timesketch credential file with.", ""],
+        ["--turbinia_recipe", "The Turbinia recipe name to use for evidence processing.", null],
+        ["--wait_for_timelines", "Whether to wait for Timesketch to finish processing all timelines.", true]
+    ]
+}
diff --git a/data/recipes/upload_turbinia.json b/data/recipes/upload_turbinia.json
index 62ddb5dd9..2bd4197b8 100644
--- a/data/recipes/upload_turbinia.json
+++ b/data/recipes/upload_turbinia.json
@@ -34,36 +34,24 @@
         "wants": ["SCP-Upload"],
         "name": "TurbiniaArtifactProcessor",
         "args": {
-            "turbinia_config_file": "@turbinia_config",
             "project": null,
+            "turbinia_auth": "@turbinia_auth",
             "turbinia_recipe": "@turbinia_recipe",
             "turbinia_zone": "@turbinia_zone",
+            "turbinia_api": "@turbinia_api",
+            "incident_id": "@incident_id",
             "output_directory": null,
             "sketch_id": null
         }
-    }, {
-          "wants": ["TurbiniaArtifactProcessor"],
-          "runtime_name": "SCP-Download",
-          "name": "SCPExporter",
-          "args": {
-            "paths": null,
-            "destination": "@local_turbinia_results",
-            "hostname": "@hostname",
-            "user": null,
-            "id_file": null,
-            "extra_ssh_options": [],
-            "check_ssh": true,
-            "direction": "download",
-            "multiplexing": true
-          }
-      }],
+    }],
     "args": [
         ["files", "Paths to process.", null],
         ["--turbinia_recipe", "The Turbinia recipe name to use for evidence processing.", null],
         ["--destination_turbinia_dir", "Destination path in Turbinia host to write the files to.", null],
         ["--hostname", "Remote host.", null, {"format": "hostname"}],
         ["--directory", "Directory in which to copy and compress files.", null],
-        ["--turbinia_config", "Turbinia config file to use.", null],
+        ["--turbinia_auth", "Flag to indicate whether Turbinia API server requires authentication.", false],
+        ["--turbinia_api", "Turbinia API server endpoint.", "http://127.0.0.1:8000"],
         ["--local_turbinia_results", "Directory where Turbinia results will be downloaded to.", null],
         ["--turbinia_zone", "The GCP zone the disk to process and Turbinia workers are in.", "us-central1-f", {"format": "gcp_zone"}],
         ["--sketch_id", "Timesketch sketch ID.", null, {"format": "regex", "regex": "^\\d+$"}]
diff --git a/data/recipes/upload_turbinia_legacy.json b/data/recipes/upload_turbinia_legacy.json
new file mode 100644
index 000000000..074643662
--- /dev/null
+++ b/data/recipes/upload_turbinia_legacy.json
@@ -0,0 +1,71 @@
+{
+    "name": "upload_turbinia_legacy",
+    "short_description": "Uploads arbitrary files to Turbinia and downloads results.",
+    "description": "Uploads arbitrary files to Turbinia for processing. The recipe will wait for Turbinia to return with results and will download them back to the filesystem. The Turbinia system needs to be accessible via SSH.",
+    "modules": [{
+      "wants": [],
+      "name": "FilesystemCollector",
+      "args": {
+          "paths": "@files"
+      }
+    }, {
+      "wants": ["FilesystemCollector"],
+      "name": "LocalFilesystemCopy",
+      "args": {
+          "target_directory": "@directory",
+          "compress": true
+      }
+    }, {
+          "wants": ["LocalFilesystemCopy"],
+          "runtime_name": "SCP-Upload",
+          "name": "SCPExporter",
+          "args": {
+            "paths": null,
+            "destination": "@destination_turbinia_dir",
+            "hostname": "@hostname",
+            "user": null,
+            "id_file": null,
+            "extra_ssh_options": [],
+            "check_ssh": true,
+            "direction": "upload",
+            "multiplexing": true
+          }
+    }, {
+        "wants": ["SCP-Upload"],
+        "name": "TurbiniaArtifactProcessorLegacy",
+        "args": {
+            "turbinia_config_file": "@turbinia_config",
+            "project": null,
+            "turbinia_recipe": "@turbinia_recipe",
+            "turbinia_zone": "@turbinia_zone",
+            "output_directory": null,
+            "sketch_id": null
+        }
+    }, {
+          "wants": ["TurbiniaArtifactProcessorLegacy"],
+          "runtime_name": "SCP-Download",
+          "name": "SCPExporter",
+          "args": {
+            "paths": null,
+            "destination": "@local_turbinia_results",
+            "hostname": "@hostname",
+            "user": null,
+            "id_file": null,
+            "extra_ssh_options": [],
+            "check_ssh": true,
+            "direction": "download",
+            "multiplexing": true
+          }
+      }],
+    "args": [
+        ["files", "Paths to process.", null],
+        ["--turbinia_recipe", "The Turbinia recipe name to use for evidence processing.", null],
+        ["--destination_turbinia_dir", "Destination path in Turbinia host to write the files to.", null],
+        ["--hostname", "Remote host.", null, {"format": "hostname"}],
+        ["--directory", "Directory in which to copy and compress files.", null],
+        ["--turbinia_config", "Turbinia config file to use.", null],
+        ["--local_turbinia_results", "Directory where Turbinia results will be downloaded to.", null],
+        ["--turbinia_zone", "The GCP zone the disk to process and Turbinia workers are in.", "us-central1-f", {"format": "gcp_zone"}],
+        ["--sketch_id", "Timesketch sketch ID.", null, {"format": "regex", "regex": "^\\d+$"}]
+    ]
+  }
diff --git a/dftimewolf/cli/dftimewolf_recipes.py b/dftimewolf/cli/dftimewolf_recipes.py
index 05657179b..09e9b141d 100644
--- a/dftimewolf/cli/dftimewolf_recipes.py
+++ b/dftimewolf/cli/dftimewolf_recipes.py
@@ -75,6 +75,8 @@
   'SSHMultiplexer': 'dftimewolf.lib.preflights.ssh_multiplexer',
   'TimesketchEnhancer': 'dftimewolf.lib.enhancers.timesketch',
   'TimesketchExporter': 'dftimewolf.lib.exporters.timesketch',
+  'TurbiniaArtifactProcessorLegacy': 'dftimewolf.lib.processors.turbinia_artifact_legacy',
+  'TurbiniaGCPProcessorLegacy': 'dftimewolf.lib.processors.turbinia_gcp_legacy',
   'TurbiniaArtifactProcessor': 'dftimewolf.lib.processors.turbinia_artifact',
   'TurbiniaGCPProcessor': 'dftimewolf.lib.processors.turbinia_gcp',
   'VTCollector' : 'dftimewolf.lib.collectors.virustotal',
diff --git a/dftimewolf/lib/collectors/gsheets.py b/dftimewolf/lib/collectors/gsheets.py
index 06ff994d6..df80a1a33 100644
--- a/dftimewolf/lib/collectors/gsheets.py
+++ b/dftimewolf/lib/collectors/gsheets.py
@@ -1,7 +1,6 @@
 # -*- coding: utf-8 -*-
 """Pulls entries from Google Sheets."""
 
-
 import os.path
 import re
 import tempfile
@@ -31,10 +30,11 @@ class GoogleSheetsCollector(module.BaseModule):
   _CREDENTIALS_FILENAME = '.dftimewolf_google_sheets_credentials.json'
   _CLIENT_SECRET_FILENAME = '.dftimewolf_google_sheets_client_secret.json'
 
-  def __init__(self,
-               state: DFTimewolfState,
-               name: Optional[str] = None,
-               critical: bool = False) -> None:
+  def __init__(
+      self,
+      state: DFTimewolfState,
+      name: Optional[str] = None,
+      critical: bool = False) -> None:
     """Initializes a Google Sheets collector."""
     super(GoogleSheetsCollector, self).__init__(
         state, name=name, critical=critical)
@@ -48,10 +48,9 @@ def __init__(self,
     self._validate_columns = True
 
   # pylint: disable=arguments-differ
-  def SetUp(self,
-            spreadsheet: str,
-            sheet_names: List[str],
-            validate_columns: bool) -> None:
+  def SetUp(
+      self, spreadsheet: str, sheet_names: List[str],
+      validate_columns: bool) -> None:
     """Sets up a a Google Sheets collector.
 
     Args:
@@ -67,17 +66,17 @@ def SetUp(self,
     if not sheet_names:
       self._all_sheets = True
     self._validate_columns = validate_columns
-    self._sheets_resource = discovery.build('sheets', 'v4',
-      credentials=self._credentials)
+    self._sheets_resource = discovery.build(
+        'sheets', 'v4', credentials=self._credentials)
 
   def Process(self) -> None:
     """Copies entries from Google Sheets."""
 
     try:
       if not self._sheets_resource:
-        self.ModuleError('Google Sheets API resource was not initialized',
-          critical=True)
-        return #return is required otherwise mypy will complain
+        self.ModuleError(
+            'Google Sheets API resource was not initialized', critical=True)
+        return  #return is required otherwise mypy will complain
 
       # Retrieve list of sheets in the spreadsheet
       # Pylint can't see the spreadsheets method.
@@ -115,15 +114,16 @@ def Process(self) -> None:
             f'Downloaded results of sheet "{sheet_title}" to {output_path}')
         output_file.close()
         sheet_csv_file = containers.File(
-            name= self._spreadsheet_id,
+            name=self._spreadsheet_id,
             path=output_path,
-            description= f'{spreadsheet_title}_{sheet_title}')
+            description=f'{spreadsheet_title}_{sheet_title}')
         self.StoreContainer(sheet_csv_file)
 
     except (RefreshError, DefaultCredentialsError) as exception:
-      self.ModuleError('Something is wrong with your gcloud access token or '
-                       'Application Default Credentials. Try running:\n '
-                       '$ gcloud auth application-default login')
+      self.ModuleError(
+          'Something is wrong with your gcloud access token or '
+          'Application Default Credentials. Try running:\n '
+          '$ gcloud auth application-default login')
       self.ModuleError(str(exception), critical=True)
 
   def _GetCredentials(self) -> Credentials:
@@ -146,8 +146,7 @@ def _GetCredentials(self) -> Credentials:
           credentials = Credentials.from_authorized_user_file(
               credentials_path, self.SCOPES)
         except ValueError as exception:
-          self.logger.warning(
-              f'Unable to load credentials: {exception}')
+          self.logger.warning(f'Unable to load credentials: {exception}')
           credentials = None
 
       # If there are no (valid) credentials available, let the user log in.
@@ -188,13 +187,12 @@ def _ValidateSpreadSheetId(self, spreadsheet: str) -> str:
       self.ModuleError(
           f'spreadsheet ID is not in the correct format {spreadsheet}.',
           critical=True)
-      return "" #return is required otherwise mypy will complain
+      return ""  #return is required otherwise mypy will complain
 
     return spreadsheet_match.group(1)
 
-  def _ExtractEntriesFromSheet(self,
-                               spreadsheet_id: str,
-                               sheet_title: str) -> pd.DataFrame | None:
+  def _ExtractEntriesFromSheet(self, spreadsheet_id: str,
+                               sheet_title: str) -> Optional[pd.DataFrame]:
     """Extract entries from the sheet inside the spreadsheet.
 
     Args:
@@ -207,9 +205,9 @@ def _ExtractEntriesFromSheet(self,
     """
 
     if not self._sheets_resource:
-      self.ModuleError('Google Sheets API resource was not initialized',
-        critical=True)
-      return None #return is required otherwise mypy will complain
+      self.ModuleError(
+          'Google Sheets API resource was not initialized', critical=True)
+      return None  #return is required otherwise mypy will complain
 
     # Pylint can't see the spreadsheets method.
     # pylint: disable=no-member
@@ -236,19 +234,18 @@ def _ExtractEntriesFromSheet(self,
         self.logger.warning(
             f'Mandatory column "{column}" was not found in sheet \
               "{sheet_title}".')
-        self.logger.warning('Please make sure all mandatory columns are \
-          present:'
-        )
+        self.logger.warning(
+            'Please make sure all mandatory columns are \
+          present:')
         self.logger.warning(
             '"message": String with an informative message of the event')
         self.logger.warning(
-            '"datetime": ISO8601 format for example: 2015-07-24T19:01:01+00:00'
-        )
+            '"datetime": ISO8601 format for example: 2015-07-24T19:01:01+00:00')
         self.logger.warning(
             '"timestamp_desc": String explaining what type of timestamp it is \
-              for example file created'
-        )
+              for example file created')
         return None
     return df
 
+
 modules_manager.ModulesManager.RegisterModule(GoogleSheetsCollector)
diff --git a/dftimewolf/lib/containers/containers.py b/dftimewolf/lib/containers/containers.py
index 8528fe13f..48d0e9580 100644
--- a/dftimewolf/lib/containers/containers.py
+++ b/dftimewolf/lib/containers/containers.py
@@ -200,10 +200,8 @@ def __init__(self, type_: str, name: str, value: str) -> None:
   def __eq__(self, other: 'TicketAttribute') -> bool:
     """Override '==' operator. Used only in unit tests."""
     return (
-        self.type == other.type
-        and self.name == other.name
-        and self.value == other.value
-    )
+        self.type == other.type and self.name == other.name and
+        self.value == other.value)
 
   def __str__(self) -> str:
     """Override __str()__."""
@@ -458,9 +456,9 @@ def __init__(
       application_name: str,
       path: str,
       filter_expression: str,
-      user_key: Optional[str ]= '',
-      start_time: Optional[str]='',
-      end_time: Optional[str]='') -> None:
+      user_key: Optional[str] = '',
+      start_time: Optional[str] = '',
+      end_time: Optional[str] = '') -> None:
     """Initializes the Workspace logs container.
 
     Args:
@@ -656,10 +654,7 @@ class BigQueryQuery(interface.AttributeContainer):
 
   CONTAINER_TYPE = 'bigquery_query'
 
-  def __init__(self,
-               query: str,
-               description: str,
-               pandas_output: bool) -> None:
+  def __init__(self, query: str, description: str, pandas_output: bool) -> None:
     super(BigQueryQuery, self).__init__()
     self.query = query
     self.description = description
@@ -692,3 +687,39 @@ def __init__(self, key: str, value: str):
   def __str__(self) -> str:
     """Override __str()__."""
     return f'Telemetry<{self.key}:{self.value}>'
+
+
+class TurbiniaRequest(interface.AttributeContainer):
+  """Turbinia request container.
+
+  Attributes:
+    project (str): name of the GCP project containing the disk to process.
+    request_id (str): Turbinia request identifier.
+    evidence_name (str): Name of the evidence being processed.
+  """
+  CONTAINER_TYPE = 'turbiniarequest'
+
+  def __init__(
+      self,
+      project: str,
+      request_id: Optional[str] = None,
+      evidence_name: Optional[str] = None) -> None:
+    """Initializes the Turbinia-request attribute container.
+
+    Args:
+      project (str): name of the GCP project containing the disk to process.
+      request_id: Turbinia request identifier.
+      evidence_name: Name of the evidence being processed.
+    """
+    super().__init__()
+    self.request_id = request_id
+    self.evidence_name = evidence_name
+    self.project = project
+
+  def __str__(self) -> Optional[str]:
+    """Overrides __str()__."""
+    return self.request_id if self.request_id else self.evidence_name
+
+  def __eq__(self, other: TurbiniaRequest) -> bool:
+    """Override __eq__() for this container."""
+    return self.request_id == other.request_id
diff --git a/dftimewolf/lib/exporters/timesketch.py b/dftimewolf/lib/exporters/timesketch.py
index c9fe8cd6c..200d39a54 100644
--- a/dftimewolf/lib/exporters/timesketch.py
+++ b/dftimewolf/lib/exporters/timesketch.py
@@ -3,11 +3,14 @@
 Threaded version of existing Timesketch module."""
 
 import time
-from typing import Optional, List, Type, Union
+import uuid
+from typing import Optional, List, Type, Union, Set
 
 from timesketch_import_client import importer
 from timesketch_api_client import sketch as ts_sketch
 from timesketch_api_client import client as ts_client  # pylint: disable=unused-import,line-too-long  # used for typing
+from timesketch_api_client import error as ts_error
+from timesketch_api_client import analyzer as ts_analyzer
 
 from dftimewolf.lib import module, timesketch_utils
 from dftimewolf.lib.containers import containers, interface
@@ -29,13 +32,13 @@ class TimesketchExporter(module.ThreadAwareModule):
     timesketch_api (TimesketchApiClient): Timesketch API client.
   """
 
-
   sketch: ts_sketch.Sketch
 
-  def __init__(self,
-               state: DFTimewolfState,
-               name: Optional[str]=None,
-               critical: bool=False) -> None:
+  def __init__(
+      self,
+      state: DFTimewolfState,
+      name: Optional[str] = None,
+      critical: bool = False) -> None:
     super(TimesketchExporter, self).__init__(
         state, name=name, critical=critical)
     self.incident_id = None  # type: Union[str, None]
@@ -45,13 +48,16 @@ def __init__(self,
     self.wait_for_timelines = False  # type: bool
     self.host_url = None  # type: Union[str, None]
     self.sketch = None  # type: ts_sketch.Sketch
-
-  def SetUp(self,  # pylint: disable=arguments-differ
-            incident_id: None=None,
-            sketch_id: Optional[int]=0,
-            analyzers: None=None,
-            token_password: str='',
-            wait_for_timelines: bool=False) -> None:
+    self._processed_timelines: Set[int] = set()
+
+  # pylint: disable=arguments-differ
+  def SetUp(
+      self,
+      incident_id: None = None,
+      sketch_id: Optional[int] = 0,
+      analyzers: None = None,
+      token_password: str = '',
+      wait_for_timelines: bool = False) -> None:
     """Setup a connection to a Timesketch server and create a sketch if needed.
 
     Args:
@@ -75,7 +81,8 @@ def SetUp(self,  # pylint: disable=arguments-differ
     if not self.timesketch_api:
       self.ModuleError(
           'Unable to get a Timesketch API client, try deleting the files '
-          '~/.timesketchrc and ~/.timesketch.token', critical=True)
+          '~/.timesketchrc and ~/.timesketch.token',
+          critical=True)
     self.incident_id = incident_id
     self.sketch_id = int(sketch_id) if sketch_id else 0
     self.sketch = None
@@ -105,7 +112,22 @@ def SetUp(self,  # pylint: disable=arguments-differ
     if analyzers:
       self._analyzers = [x.strip() for x in analyzers.split(',')]
 
-  def _CreateSketch(self, incident_id: Optional[str]=None) -> ts_sketch.Sketch:
+    self.sketch = self.state.GetFromCache('timesketch_sketch')
+    if not self.sketch and self.sketch_id:
+      self.logger.info('Using exiting sketch: {0:d}'.format(self.sketch_id))
+      self.sketch = self.timesketch_api.get_sketch(self.sketch_id)
+
+    # Create the sketch if no sketch was stored in the cache.
+    if not self.sketch:
+      self.sketch = self._CreateSketch(incident_id=self.incident_id)
+      self.sketch_id = self.sketch.id
+      self.logger.info('New sketch created: {0:d}'.format(self.sketch_id))
+
+    # register callback in timesketch module
+    self.state.RegisterStreamingCallback(self.Process, containers.File)
+
+  def _CreateSketch(
+      self, incident_id: Optional[str] = None) -> ts_sketch.Sketch:
     """Creates a new Timesketch sketch.
 
     Args:
@@ -120,85 +142,102 @@ def _CreateSketch(self, incident_id: Optional[str]=None) -> ts_sketch.Sketch:
       sketch_name = 'Untitled sketch'
     sketch_description = 'Sketch generated by dfTimewolf'
 
-    sketch = self.timesketch_api.create_sketch(
-        sketch_name, sketch_description)
+    sketch = self.timesketch_api.create_sketch(sketch_name, sketch_description)
     self.sketch_id = sketch.id
     if incident_id:
-      sketch.add_attribute(
-          'incident_id', incident_id, ontology='text')
+      sketch.add_attribute('incident_id', incident_id, ontology='text')
     self.state.AddToCache('timesketch_sketch', sketch)
 
     return sketch
 
   def _WaitForTimelines(self) -> None:
-    """Waits for all timelines in a sketch to be processed."""
-    time.sleep(5)  # Give Timesketch time to populate recently added timelines.
+    """Waits for all timelines in a sketch to be processed.
+
+    Runs analyzers on timelines that are ready.
+    """
     sketch = self.timesketch_api.get_sketch(self.sketch_id)
     timelines = sketch.list_timelines()
-    while True:
-      if all(tl.status in ['fail', 'ready', 'timeout', 'archived']
-             for tl in timelines):
-        break
-      time.sleep(10)
-
-  def Process(self, container: containers.File
-              ) -> None:  # pytype: disable=signature-mismatch
+    self.logger.info(
+        f'Found {len(timelines)} timelines for sketch {self.sketch_id}')
+    while timelines:
+      for timeline in timelines:
+        # if the timeline is is a final state, pop it from the list
+        if timeline.status in ['fail', 'ready', 'timeout', 'archived']:
+          timelines.remove(timeline)
+          # if the timeline is ready, run the analyzers
+          if timeline.status == 'ready' and (not timeline.id
+                                             in self._processed_timelines):
+            self._processed_timelines.add(timeline.id)
+            self._RunAnalyzers(timeline.name)
+        else:
+          self.logger.info(f'Waiting for timeline {timeline.name} to be ready')
+      time.sleep(30)
+
+  def _RunAnalyzers(self, timeline_name: str) -> None:
+    """Runs analyzers on a timeline."""
+    if not self._analyzers:
+      self.logger.info(
+          'No analyzers to run on timeline {0:s}.'.format(timeline_name))
+      return
+
+    timeline = self.sketch.get_timeline(timeline_name=timeline_name)
+    self.logger.info(
+        "Running analyzers {0!s} on timeline {1:s}".format(
+            self._analyzers, timeline_name))
+    try:
+      # By default run_analyzers() ignores analyzers that have already been run
+      results: List[ts_analyzer.AnalyzerResult] = timeline.run_analyzers(
+          analyzer_names=self._analyzers)
+      if not results:
+        self.logger.info(
+            'No new analyzers to run on timeline {0:s}.'.format(timeline_name))
+        return
+      # Get the last result, which is the most recent run of the analyzers.
+      result = results[-1]
+      for analyzer_name, analyzer_status in result.status_dict.items():
+        self.logger.debug(
+            'Analyzer: {0:s} status: {1!s}'.format(
+                analyzer_name, analyzer_status))
+    except ts_error.UnableToRunAnalyzer as exception:
+      self.ModuleError(
+          'Unable to run analyzer: {0!s}'.format(exception), critical=False)
+
+  # pytype: disable=signature-mismatch
+  def Process(self, container: containers.File) -> None:
     """Executes a Timesketch export.
 
     Args:
       container (containers.File): A container holding a File to import."""
 
     recipe_name = self.state.recipe.get('name', 'no_recipe')
-
+    rand = uuid.uuid4().hex[-5:]
     description = container.name
     if description:
       name = description.rpartition('.')[0]
       name = name if name else description
       name = name.replace(' ', '_').replace('-', '_')
-      timeline_name = '{0:s}_{1:s}'.format(
-          recipe_name, name)
+      timeline_name = f'{recipe_name}_{name}'
     else:
-      timeline_name = recipe_name
+      timeline_name = f'{recipe_name}'
 
-    self.logger.info('Uploading {0:s}...'.format(timeline_name))
+    # Give each timeline a unique name
+    timeline_name = f'{timeline_name}_{rand}'
+    self.logger.info('Uploading {0:s} ...'.format(timeline_name))
 
     with importer.ImportStreamer() as streamer:
       streamer.set_sketch(self.sketch)
       streamer.set_timeline_name(timeline_name)
 
       path = container.path
-      streamer.add_file(path)
+      try:
+        streamer.add_file(path)
+      except RuntimeError as exception:
+        self.ModuleError(
+            'Unable to import {0:s}: {1!s}'.format(path, exception),
+            critical=False)
       if streamer.response and container.description:
         streamer.timeline.description = container.description
 
-    if self.wait_for_timelines:
-      self.logger.info('Waiting for timeline {0:s} to finish processing...'\
-          .format(timeline_name))
-      self._WaitForTimelines()
-
-    for analyzer in self._analyzers:
-      self.logger.info("Running analyzer {0:s} on timeline {1:s}".format(
-          analyzer, timeline_name))
-      results = self.sketch.run_analyzer(
-          analyzer_name=analyzer, timeline_name=timeline_name)
-
-      if not results:
-        self.logger.info('Analyzer [{0:s}] not able to run on {1:s}'.format(
-            analyzer, timeline_name))
-        continue
-
-      # Unknown why, but we get a list of 2 identical result objects
-      results = results[0]
-      session_id = results._session_id # pylint: disable=protected-access
-      if not session_id:
-        self.logger.info(
-            'Analyzer [{0:s}] didn\'t provide any session data'.format(
-                analyzer))
-        continue
-      self.logger.info('Analyzer: {0:s} is running, session ID: {1:d}'.format(
-          analyzer, session_id))
-      self.logger.info(results.status_string)
-
   def GetThreadOnContainerType(self) -> Type[interface.AttributeContainer]:
     return containers.File
 
@@ -206,33 +245,21 @@ def GetThreadPoolSize(self) -> int:
     return 5
 
   def PreProcess(self) -> None:
-    """Get the sketch, creating it if it doesn't yet exist."""
-    if not self.timesketch_api:
-      message = 'Could not connect to Timesketch server'
-      self.ModuleError(message, critical=True)
-
-    self.sketch = self.state.GetFromCache('timesketch_sketch')
-    if not self.sketch and self.sketch_id:
-      self.logger.info('Using exiting sketch: {0:d}'.format(self.sketch_id))
-      self.sketch = self.timesketch_api.get_sketch(self.sketch_id)
-
-    # Create the sketch if no sketch was stored in the cache.
-    if not self.sketch:
-      self.sketch = self._CreateSketch(incident_id=self.incident_id)
-      self.sketch_id = self.sketch.id
-      self.logger.info('New sketch created: {0:d}'.format(self.sketch_id))
+    pass
 
   def PostProcess(self) -> None:
     api_root = self.sketch.api.api_root
     host_url = api_root.partition('api/v1')[0]
+
+    if self.wait_for_timelines:
+      self._WaitForTimelines()
+
     sketch_url = '{0:s}sketches/{1:d}/'.format(host_url, self.sketch.id)
     message = 'Your Timesketch URL is: {0:s}'.format(sketch_url)
     self.PublishMessage(message)
 
     report_container = containers.Report(
-        module_name='TimesketchExporter',
-        text=message,
-        text_format='markdown')
+        module_name='TimesketchExporter', text=message, text_format='markdown')
     self.StoreContainer(report_container)
 
 
diff --git a/dftimewolf/lib/module.py b/dftimewolf/lib/module.py
index 98e4835a1..f4ec2a023 100644
--- a/dftimewolf/lib/module.py
+++ b/dftimewolf/lib/module.py
@@ -57,7 +57,6 @@ def __init__(self,
     self.state = state
     self.logger = cast(logging_utils.WolfLogger,
                        logging.getLogger(name=self.name))
-    self.logger.handlers.clear()
     self.logger.propagate = False
     self.SetupLogging()
 
diff --git a/dftimewolf/lib/modules/manager.py b/dftimewolf/lib/modules/manager.py
index ad1054c2b..127b67752 100644
--- a/dftimewolf/lib/modules/manager.py
+++ b/dftimewolf/lib/modules/manager.py
@@ -1,11 +1,12 @@
 # -*- coding: utf-8 -*-
 """Modules manager class."""
 
-from typing import Dict, TYPE_CHECKING, Type, List
+from typing import Dict, TYPE_CHECKING, Type, List, Optional
 
 if TYPE_CHECKING:
   from dftimewolf.lib import module
 
+
 class ModulesManager(object):
   """Modules manager."""
 
@@ -34,7 +35,7 @@ def DeregisterModule(cls, module_class: Type['module.BaseModule']) -> None:
     del cls._module_classes[class_name]
 
   @classmethod
-  def GetModuleByName(cls, name: str) -> Type['module.BaseModule']:
+  def GetModuleByName(cls, name: str) -> Optional[Type['module.BaseModule']]:
     """Retrieves a specific module by its name.
 
     Args:
@@ -44,7 +45,7 @@ def GetModuleByName(cls, name: str) -> Type['module.BaseModule']:
       type: the module class, which is a subclass of BaseModule, or None if
           no corresponding module was found.
     """
-    return cls._module_classes[name]
+    return cls._module_classes.get(name, None)
 
   @classmethod
   def RegisterModule(cls, module_class: Type['module.BaseModule']) -> None:
diff --git a/dftimewolf/lib/processors/turbinia_artifact.py b/dftimewolf/lib/processors/turbinia_artifact.py
index af34c65e4..b1e3b2492 100644
--- a/dftimewolf/lib/processors/turbinia_artifact.py
+++ b/dftimewolf/lib/processors/turbinia_artifact.py
@@ -1,14 +1,10 @@
 # -*- coding: utf-8 -*-
 """Processes a directory of artifacts with Turbinia."""
 
-import os
 import tempfile
 
 from typing import Optional, TYPE_CHECKING, Type
 
-from turbinia import TurbiniaException, evidence
-from turbinia import config as turbinia_config
-
 from dftimewolf.lib import module
 from dftimewolf.lib.containers import containers, interface
 from dftimewolf.lib.modules import manager as modules_manager
@@ -17,8 +13,8 @@
 if TYPE_CHECKING:
   from dftimewolf.lib import state
 
-# pylint: disable=no-member
 
+# pylint: disable=no-member
 class TurbiniaArtifactProcessor(TurbiniaProcessorBase,
                                 module.ThreadAwareModule):
   """Processes Exported GRR Artifacts with Turbinia.
@@ -27,10 +23,11 @@ class TurbiniaArtifactProcessor(TurbiniaProcessorBase,
     directory_path (str): Name of the directory to process.
   """
 
-  def __init__(self,
-               state: "state.DFTimewolfState",
-               name: Optional[str]=None,
-               critical: bool=False) -> None:
+  def __init__(
+      self,
+      state: "state.DFTimewolfState",
+      name: Optional[str] = None,
+      critical: bool = False) -> None:
     """Initializes a Turbinia Artifacts disks processor.
 
     Args:
@@ -40,80 +37,80 @@ def __init__(self,
           the entire recipe to fail if the module encounters an error.
     """
     module.ThreadAwareModule.__init__(self, state, name=name, critical=critical)
-    TurbiniaProcessorBase.__init__(self, self.logger)
+    TurbiniaProcessorBase.__init__(
+        self, state, self.logger, name=name, critical=critical)
     self.output_directory = ''
 
   # pylint: disable=arguments-differ
-  def SetUp(self,
-            turbinia_config_file: Optional[str],
-            project: str,
-            turbinia_recipe: Optional[str],
-            turbinia_zone: str,
-            output_directory: str,
-            sketch_id: int) -> None:
+  def SetUp(
+      self, project: str, turbinia_auth: bool, turbinia_recipe: Optional[str],
+      turbinia_zone: str, turbinia_api: str, output_directory: str,
+      incident_id: str, sketch_id: int) -> None:
     """Sets up the object attributes.
 
     Args:
-      turbinia_config_file (str): Full path to the Turbinia config file to use.
       project (str): name of the GCP project containing the disk to process.
+      turbinia_auth (bool): Turbinia auth flag.
       turbinia_recipe (str): Turbinia recipe name.
       turbinia_zone (str): GCP zone in which the Turbinia server is running.
       output_directory (str): Name of the directory to process.
       sketch_id (int): The Timesketch sketch ID.
     """
-    self.turbinia_config_file = turbinia_config_file
     self.output_directory = output_directory
     if not self.output_directory:
       self.output_directory = tempfile.mkdtemp(prefix='turbinia-results')
       self.PublishMessage(
           f'Turbinia results will be dumped to {self.output_directory}')
-    try:
-      self.TurbiniaSetUp(project, turbinia_recipe, turbinia_zone, sketch_id)
-    except TurbiniaException as exception:
-      self.ModuleError(str(exception), critical=True)
-      return
-
-  def Process(self, container: containers.RemoteFSPath
-              ) -> None:  # pytype: disable=signature-mismatch
+
+    self.TurbiniaSetUp(
+        project, turbinia_auth, turbinia_recipe, turbinia_zone, turbinia_api,
+        incident_id, sketch_id)
+
+  # pytype: disable=signature-mismatch
+  def Process(self, container: containers.RemoteFSPath) -> None:
+    # pytype: enable=signature-mismatch
     """Process files with Turbinia."""
 
-    log_file_path = os.path.join(self._output_path,
-        '{0:s}_{1:s}-turbinia.log'.format(
-            container.hostname, container.path.replace('/', '_')))
-    self.logger.info('Turbinia log file: {0:s}'.format(log_file_path))
     self.logger.info(
         'Processing remote FS path {0:s} from previous collector'.format(
             container.path))
-    evidence_ = evidence.CompressedDirectory(
-        compressed_directory=container.path, source_path=container.path)
-    try:
-      request_id = self.TurbiniaStart(evidence_)
-      self.PublishMessage(f'Turbinia request ID: {request_id}')
-      task_data, _ = self.TurbiniaWait(request_id)
-    except TurbiniaException as exception:
-      self.ModuleError(str(exception), critical=True)
-
-    self.logger.info('Files generated by Turbinia:')
-    for task in task_data:
-      for path in task.get('saved_paths') or []:
-        # Ignore temporary files generated by turbinia
-        if path.startswith(turbinia_config.TMP_DIR):
-          continue
 
-        # We're only interested in plaso files for the time being.
-        if path.endswith('.plaso'):
-          self.logger.info(f'  {task["name"]}: {path}')
-          container = containers.RemoteFSPath(
-              path=path, hostname=container.hostname)
-          self.StoreContainer(container)
+    evidence = {'type': 'CompressedDirectory', 'source_path': container.path}
+    request_id = self.TurbiniaStart(evidence)
+    self.PublishMessage(f'Turbinia request ID: {request_id}')
+
+    for task, path in self.TurbiniaWait(request_id):
+      # Try to set a descriptive name for the container
+      try:
+        task_name = task['name']
+        task_id = task['id']
+        reason = task['reason']
+        descriptive_name = f'{task_name}-{task_id}-{reason}'
+      except KeyError as exception:
+        self.logger.debug(
+          f'Failed to get task key: {str(exception)}, using path for '
+          f'cotnainer name instead.')
+        descriptive_name = container.path
+      # We're only interested in plaso files for the time being.
+      if path.endswith('.plaso'):
+        local_path = self.DownloadFilesFromAPI(task, path)
+        if not local_path:
+          self.logger.warning(
+              f'No interesting output files could be found for task {task_id}')
+          continue
+        self.PublishMessage(f'Found plaso result for task {task["id"]}: {path}')
+        fs_container = containers.File(path=local_path, name=descriptive_name)
+        self.StreamContainer(fs_container)
 
-  def GetThreadOnContainerType(self) -> Type[interface.AttributeContainer]:
+  @staticmethod
+  def GetThreadOnContainerType() -> Type[interface.AttributeContainer]:
     return containers.RemoteFSPath
 
   def GetThreadPoolSize(self) -> int:
     return self.parallel_count
 
-  def KeepThreadedContainersInState(self) -> bool:
+  @staticmethod
+  def KeepThreadedContainersInState() -> bool:
     return False
 
   def PreProcess(self) -> None:
@@ -122,4 +119,5 @@ def PreProcess(self) -> None:
   def PostProcess(self) -> None:
     pass
 
+
 modules_manager.ModulesManager.RegisterModule(TurbiniaArtifactProcessor)
diff --git a/dftimewolf/lib/processors/turbinia_artifact_legacy.py b/dftimewolf/lib/processors/turbinia_artifact_legacy.py
new file mode 100644
index 000000000..2e7f1cd26
--- /dev/null
+++ b/dftimewolf/lib/processors/turbinia_artifact_legacy.py
@@ -0,0 +1,126 @@
+# -*- coding: utf-8 -*-
+"""[DEPRECATED] Processes a directory of artifacts with Turbinia."""
+
+import os
+import tempfile
+
+from typing import Optional, TYPE_CHECKING, Type
+
+# pylint: disable=import-error
+# pytype: disable=import-error
+from turbinia import TurbiniaException, evidence
+from turbinia import config as turbinia_config
+
+from dftimewolf.lib import module
+from dftimewolf.lib.containers import containers, interface
+from dftimewolf.lib.modules import manager as modules_manager
+# pylint: disable=line-too-long
+from dftimewolf.lib.processors.turbinia_base_legacy import TurbiniaProcessorBaseLegacy
+
+if TYPE_CHECKING:
+  from dftimewolf.lib import state
+
+
+# pylint: disable=no-member
+class TurbiniaArtifactProcessorLegacy(TurbiniaProcessorBaseLegacy,
+                                      module.ThreadAwareModule):
+  """Processes Exported GRR Artifacts with Turbinia.
+
+  Attributes:
+    directory_path (str): Name of the directory to process.
+  """
+
+  def __init__(
+      self,
+      state: "state.DFTimewolfState",
+      name: Optional[str] = None,
+      critical: bool = False) -> None:
+    """Initializes a Turbinia Artifacts disks processor.
+
+    Args:
+      state (DFTimewolfState): recipe state.
+      name (Optional[str]): The module's runtime name.
+      critical (Optional[bool]): True if the module is critical, which causes
+          the entire recipe to fail if the module encounters an error.
+    """
+    module.ThreadAwareModule.__init__(self, state, name=name, critical=critical)
+    TurbiniaProcessorBaseLegacy.__init__(self, self.logger)
+    self.output_directory = ''
+
+  # pylint: disable=arguments-differ
+  def SetUp(
+      self, turbinia_config_file: Optional[str], project: str,
+      turbinia_recipe: Optional[str], turbinia_zone: str, output_directory: str,
+      sketch_id: int) -> None:
+    """Sets up the object attributes.
+
+    Args:
+      turbinia_config_file (str): Full path to the Turbinia config file to use.
+      project (str): name of the GCP project containing the disk to process.
+      turbinia_recipe (str): Turbinia recipe name.
+      turbinia_zone (str): GCP zone in which the Turbinia server is running.
+      output_directory (str): Name of the directory to process.
+      sketch_id (int): The Timesketch sketch ID.
+    """
+    self.turbinia_config_file = turbinia_config_file
+    self.output_directory = output_directory
+    if not self.output_directory:
+      self.output_directory = tempfile.mkdtemp(prefix='turbinia-results')
+      self.PublishMessage(
+          f'Turbinia results will be dumped to {self.output_directory}')
+    try:
+      self.TurbiniaSetUp(project, turbinia_recipe, turbinia_zone, sketch_id)
+    except TurbiniaException as exception:
+      self.ModuleError(str(exception), critical=True)
+      return
+
+  def Process(self, container: containers.RemoteFSPath) -> None:  # pytype: disable=signature-mismatch
+    """Process files with Turbinia."""
+
+    log_file_path = os.path.join(
+        self._output_path, '{0:s}_{1:s}-turbinia.log'.format(
+            container.hostname, container.path.replace('/', '_')))
+    self.logger.info('Turbinia log file: {0:s}'.format(log_file_path))
+    self.logger.info(
+        'Processing remote FS path {0:s} from previous collector'.format(
+            container.path))
+    evidence_ = evidence.CompressedDirectory(
+        compressed_directory=container.path, source_path=container.path)
+    try:
+      request_id = self.TurbiniaStart(evidence_)
+      self.PublishMessage(f'Turbinia request identifier: {request_id}')
+      task_data, _ = self.TurbiniaWait(request_id)
+    except TurbiniaException as exception:
+      self.ModuleError(str(exception), critical=True)
+
+    self.logger.info('Files generated by Turbinia:')
+    for task in task_data:
+      for path in task.get('saved_paths') or []:
+        # Ignore temporary files generated by turbinia
+        if path.startswith(turbinia_config.TMP_DIR):
+          continue
+
+        # We're only interested in plaso files for the time being.
+        if path.endswith('.plaso'):
+          self.logger.info(f'  {task["name"]}: {path}')
+          container = containers.RemoteFSPath(
+              path=path, hostname=container.hostname)
+          self.StoreContainer(container)
+
+  def GetThreadOnContainerType(self) -> Type[interface.AttributeContainer]:
+    return containers.RemoteFSPath
+
+  def GetThreadPoolSize(self) -> int:
+    return self.parallel_count
+
+  def KeepThreadedContainersInState(self) -> bool:
+    return False
+
+  def PreProcess(self) -> None:
+    pass
+
+  def PostProcess(self) -> None:
+    pass
+
+
+modules_manager.ModulesManager.RegisterModule(TurbiniaArtifactProcessorLegacy)
diff --git a/dftimewolf/lib/processors/turbinia_base.py b/dftimewolf/lib/processors/turbinia_base.py
index da057d508..5f693127d 100644
--- a/dftimewolf/lib/processors/turbinia_base.py
+++ b/dftimewolf/lib/processors/turbinia_base.py
@@ -1,267 +1,419 @@
-# -*- coding: utf-8 -*-
 """Base class for turbinia interactions."""
 
 import getpass
 import os
-import random
+import tarfile
 import tempfile
 import time
-from typing import Dict, List, Optional, Tuple, Any, Union
+import math
+
+from typing import Dict, List, Optional, Tuple, Any, Union, Iterator
+
+from google_auth_oauthlib import flow
+from google.oauth2.credentials import Credentials
+from google.auth.transport.requests import Request
+from google.auth import exceptions as google_exceptions
+
+import turbinia_api_lib
+from turbinia_client.helpers import formatter as turbinia_formatter
+from turbinia_api_lib.api import (
+    turbinia_requests_api, turbinia_configuration_api)
+from turbinia_api_lib.api import turbinia_request_results_api
 
-from turbinia import TurbiniaException
-from turbinia import client as turbinia_client
-from turbinia import config as turbinia_config
-from turbinia import evidence, output_manager
 from dftimewolf.lib.logging_utils import WolfLogger
+from dftimewolf.lib import module
+# pylint: disable=unused-import
+from dftimewolf.lib import state as state_lib
 
 
 # pylint: disable=abstract-method,no-member
-class TurbiniaProcessorBase(object):
+class TurbiniaProcessorBase(module.BaseModule):
   """Base class for processing with Turbinia.
 
   Attributes:
-    turbinia_config_file (str): Full path to the Turbinia config file to use.
-    client (TurbiniaClient): Turbinia client.
+    client (turbinia_api_lib.ApiClient): Turbinia client.
+    client_config (dict): Turbinia client config.
+    extensions (List[str]): List of file extensions to look for.
+    incident_id (str): The Timesketch incident id.
     instance (str): name of the Turbinia instance
+    logger (WolfLogger): logger.
     project (str): name of the GCP project containing the disk to process.
+    requests_api_instance (turbinia_requests_api.TurbiniaRequestsApi):
+        Turbinia requests API instance.
     sketch_id (int): The Timesketch sketch id
     turbinia_recipe (str): Turbinia recipe name.
     turbinia_region (str): GCP region in which the Turbinia server is running.
     turbinia_zone (str): GCP zone in which the Turbinia server is running.
+    turbinia_api (str): Turbinia API endpoint.
+    turbinia_auth (bool): Turbinia auth flag.
+    parallel_count (int): Number of threads to use.
   """
 
   DEFAULT_YARA_MODULES = 'import "pe"\nimport "math"\nimport "hash"\n\n'
 
-  def __init__(self, logger: WolfLogger) -> None:
+  def __init__(
+      self,
+      state: "state_lib.DFTimewolfState",
+      logger: WolfLogger,
+      name: Optional[str] = None,
+      critical: bool = False,
+  ) -> None:
     """Initializes a Turbinia base processor.
 
     Args:
       state (state.DFTimewolfState): recipe state.
+      logger: A logger instance.
       name (Optional[str]): The module's runtime name.
       critical (Optional[bool]): True if the module is critical, which causes
           the entire recipe to fail if the module encounters an error.
     """
-    self.turbinia_config_file = ''  # type: Any
-    self._output_path = str()
-    self.client = None  # type: turbinia_client.BaseTurbiniaClient
+    super().__init__(state=state, name=name, critical=critical)
+    self.name = name if name else self.__class__.__name__
+    self.critical = critical
+    self.state = state
+    self.output_path = str()
+    self.client: turbinia_api_lib.ApiClient = None
     self.instance = None
     self.project = str()
+    self.incident_id = str()
     self.sketch_id = int()
+    self.turbinia_auth = bool()
     self.turbinia_recipe = str()  # type: Any
     self.turbinia_region = None
     self.turbinia_zone = str()
+    self.turbinia_api = str()
+    self.requests_api_instance: turbinia_requests_api.TurbiniaRequestsApi = None
+    self.client_config = None
+    self.credentials_path = os.path.join(
+        os.path.expanduser('~'), ".dftimewolf_turbinia.token")
+    self.client_secrets_path = os.path.join(
+        os.path.expanduser('~'), ".dftimewolf_turbinia_secrets.json")
     self.parallel_count = 5  # Arbitrary, used by ThreadAwareModule
     self.logger = logger
-
+    self.extensions = [
+        '.plaso', 'BinaryExtractorTask.tar.gz', 'hashes.json',
+        'fraken_stdout.log', 'loki_stdout.log'
+    ]
     os.environ['GRPC_POLL_STRATEGY'] = 'poll'
 
-  def _DeterminePaths(
-      self,
-      task_data: List[Dict[str, Any]]) -> Tuple[List[str], List[str]]:
-    """Builds lists of local and remote paths from data returned by Turbinia.
+  def _isInterestingPath(self, path: str) -> bool:
+    """Checks if a path is interesting for the processor."""
+    for suffix in self.extensions:
+      if path.endswith(suffix):
+        return True
+    return False
+
+  def _FilterTarMembers(self, tgzfile: tarfile.TarFile,
+                        path_to_collect: str) -> List[tarfile.TarInfo]:
+    """Filters a TarFile object for a specific path.
 
-    This finds all .plaso, hashes.json, and BinaryExtractorTask files in the
-    Turbinia output, and determines if they are local or remote (it's possible
-    this will be running against a local instance of Turbinia).
+    Pre-condition: tgzfile must be a valid TarFile object.
 
     Args:
-      task_data (list[dict]): List of dictionaries representing Turbinia task
-          data.
+      tgzfile: A TarFile object of a Turbinia task output file.
+      path_to_collect: A saved path from a Turbinia task.
 
     Returns:
-      tuple[list, list]: A tuple of two lists. The first element contains the
-          local paths, the second element contains the remote (GS) paths.
+      A list of TarInfo objects.
     """
-    local_paths = []
-    gs_paths = []
-    for task in task_data:
-      # saved_paths may be set to None
-      saved_paths = task.get('saved_paths') or []
-      for path in saved_paths:
-
-        if path.endswith('.plaso') or \
-            path.endswith('BinaryExtractorTask.tar.gz') or \
-            path.endswith('hashes.json') or \
-            path.endswith('fraken_stdout.log') or \
-            path.endswith('loki_stdout.log'):
-
-          if path.startswith('gs://'):
-            gs_paths.append(path)
-          else:
-            local_paths.append(path)
-
-    return local_paths, gs_paths
-
-  def _DownloadFilesFromGCS(self,
-                            timeline_label: str,
-                            gs_paths: List[str]) -> List[Tuple[str, str]]:
-    """Downloads files stored in Google Cloud Storage to the local filesystem.
+    members = []
+    names = tgzfile.getnames()
+    path_to_collect = path_to_collect.lstrip('/')
+    if path_to_collect in names:
+      members.append(tgzfile.getmember(path_to_collect))
+    return members
+
+  def _ExtractFiles(self, tgz_path: str, path_to_collect: str) -> str:
+    """Extracts files which appear in a Turbinia task's saved_paths attribute
+
+    Pre-condition(s): The effective UID can read and write to the temporary
+        directory. file_path must be a path to a valid tgz file containing
+        Turbinia task output files.
 
     Args:
-      timeline_label (str): Label to use to construct the path list.
-      gs_paths (List[str]):  gs:// URI to files that need to be downloaded
-          from GS.
+      tgz_path: File path of a Turbinia task output file (tgz).
+      path_to_collect: A saved path from a Turbinia task.
 
-    Raises:
-      TurbiniaException: Upon errors downloading from GCS
+    Returns:
+      A local path to the extracted file.
+    """
+    local_path = ''
+    if not os.path.exists(tgz_path):
+      self.logger.error(f'File not found {tgz_path}')
+      return local_path
+
+    tempdir = tempfile.mkdtemp()
+    with tarfile.open(tgz_path) as file:
+      members = self._FilterTarMembers(file, path_to_collect)
+      file.extractall(path=tempdir, members=members)
+
+    local_path = os.path.join(tempdir, path_to_collect.lstrip('/'))
+    return local_path
+
+  def DownloadFilesFromAPI(self, task_data: Dict[str, List[str]],
+                           path: str) -> Optional[str]:
+    """Downloads task output data from the Turbinia API server.
+
+    Args:
+      task_data: Response from a /api/request/{request_id} API call.
+      path: A saved path from a Turbinia task.
 
     Returns:
-      list:
-        tuple: containing:
-          str: The timeline label for this path.
-          str: A local path where GS files have been copied to.
+      A local path to Turbinia task output files or None if files
+        could not be downloaded.
     """
-    # TODO: Externalize fetching files from GCS buckets to a different module.
-
-    local_paths = []
-    for path in gs_paths:
-      local_path = None
-      output_writer = output_manager.GCSOutputWriter(
-          path, local_output_dir=self._output_path)
-      local_path = output_writer.copy_from(path)
-      self.logger.success('Downloaded {0:s} to {1:s}'.format(path, local_path))
-
-      if local_path:
-        local_paths.append((timeline_label, local_path))
-
-    return local_paths
-
-  def TurbiniaSetUp(self,
-                    project: str,
-                    turbinia_recipe: Union[str, None],
-                    turbinia_zone: str,
-                    sketch_id: int) -> None:
+    result = None
+    api_instance = turbinia_request_results_api.TurbiniaRequestResultsApi(
+        self.client)
+    try:
+      task_id = task_data.get('id')
+      api_response = api_instance.get_task_output(
+          task_id, _preload_content=False)
+      filename = f'{task_id}-'
+
+      # Create a temporary file to write the response to.
+      file = tempfile.NamedTemporaryFile(
+          mode='wb', prefix=f'{filename}', suffix='.tgz', delete=False)
+      local_path = file.name
+      self.logger.info(f'Downloading output for task {task_id} to {local_path}')
+      # Read the response and write to the file.
+      for chunk in api_response.read_chunked():
+        file.write(chunk)
+      file.close()
+
+      # Extract the files from the tgz file.
+      extracted_path = self._ExtractFiles(local_path, path)
+      if os.path.exists(extracted_path):
+        result = extracted_path
+        self.PublishMessage(
+            f'Extracted output file to {result} for task {task_id}')
+    except turbinia_api_lib.ApiException as exception:
+      self.ModuleError(
+          f'Unable to download task data: {exception}', critical=False)
+    except OSError as exception:
+      self.ModuleError(f'Unable to write to file: {exception}', critical=False)
+
+    return result
+
+  def GetCredentials(self, credentials_path: str,
+                     client_secrets_path: str) -> Optional[Any]:
+    """Authenticates the user using Google OAuth services."""
+    scopes = ['openid', 'https://www.googleapis.com/auth/userinfo.email']
+    credentials = None
+
+    # Load credentials file if it exists
+    if os.path.exists(credentials_path):
+      try:
+        credentials = Credentials.from_authorized_user_file(
+            credentials_path, scopes)
+      except ValueError as exception:
+        msg = f'Error loading credentials: {exception!s}'
+        self.ModuleError(msg, critical=True)
+      # Refresh credentials using existing refresh_token
+      if credentials and credentials.refresh_token:
+        self.logger.debug('Found a refresh token. Requesting new id_token...')
+        try:
+          credentials.refresh(Request())
+        except google_exceptions.RefreshError as exception:
+          self.logger.debug(f'Error refreshing credentials: {exception!s}')
+    else:
+      # No credentials file, acquire new credentials from secrets file.
+      self.logger.debug(
+          'Could not find existing credentials. Requesting new tokens.')
+      try:
+        appflow = flow.InstalledAppFlow.from_client_secrets_file(
+            client_secrets_path, scopes)
+      except FileNotFoundError as exception:
+        msg = f'Client secrets file not found: {exception!s}'
+        self.ModuleError(msg, critical=True)
+
+      self.logger.info(
+          'Starting local HTTP server on localhost:8888 for OAUTH flow. '
+          'If running dftimewolf remotely over SSH you will need to tunnel '
+          'port 8888.')
+      appflow.run_local_server(host='localhost', port=8888)
+      credentials = appflow.credentials
+
+      # Save credentials
+      with open(credentials_path, 'w', encoding='utf-8') as token:
+        token.write(credentials.to_json())
+
+    if credentials:
+      return credentials.id_token
+
+    return credentials
+
+  def TurbiniaSetUp(
+      self, project: str, turbinia_auth: bool,
+      turbinia_recipe: Union[str, None], turbinia_zone: str, turbinia_api: str,
+      incident_id: str, sketch_id: int) -> None:
     """Sets up the object attributes.
 
-    Raises:
-      TurbiniaException: For errors in setting up the Turbinia client.
-
     Args:
       project (str): name of the GCP project containing the disk to process.
+      turbinia_auth (bool): Turbinia auth flag.
       turbinia_recipe (str): Turbinia recipe name.
       turbinia_zone (str): GCP zone in which the Turbinia server is running.
-      sketch_id (int): The Timesketch sketch ID.
+      turbinia_api (str): URL of the Turbinia API server.
+      incident_id (str): The incident ID.
+      sketch_id (int): The sketch ID.
     """
     self.project = project
+    self.turbinia_auth = turbinia_auth
+    self.turbinia_api = turbinia_api
     self.turbinia_recipe = turbinia_recipe
     self.turbinia_zone = turbinia_zone
+    self.incident_id = incident_id
     self.sketch_id = sketch_id
-
-    turbinia_config.LoadConfig(config_file=self.turbinia_config_file)
-    if not self.project:
-      self.project = turbinia_config.TURBINIA_PROJECT
-    if not self.turbinia_zone:
-      self.turbinia_zone = turbinia_config.TURBINIA_ZONE
-
-    if not self.project or not self.turbinia_zone:
-      raise TurbiniaException(
-          'project or turbinia_zone are not all specified, bailing out')
-
-    self.turbinia_region = turbinia_config.TURBINIA_REGION
-    self.instance = turbinia_config.INSTANCE_ID
-    if turbinia_config.TURBINIA_PROJECT != self.project:
-      raise TurbiniaException(
-          'Specified project {0!s} does not match Turbinia configured '
-          'project {1!s}. Use gcp_turbinia_disk_copy_ts recipe to copy the '
-          'disk into the same project.'.format(
-              self.project, turbinia_config.TURBINIA_PROJECT))
-    self._output_path = tempfile.mkdtemp()
-    self.client = turbinia_client.get_turbinia_client()
+    self.client_config = turbinia_api_lib.Configuration(host=self.turbinia_api)
+    if not self.client_config:
+      self.ModuleError('Unable to configure Turbinia API server', critical=True)
+      return
+    # Check if Turbinia requires authentication.
+    if self.turbinia_auth:
+      self.client_config.access_token = self.GetCredentials(
+          self.credentials_path, self.client_secrets_path)
+      if not self.client_config.access_token:
+        self.ModuleError(
+            'Unable to authenticate to Turbinia API server', critical=True)
+    self.client = turbinia_api_lib.ApiClient(self.client_config)
+    self.requests_api_instance = turbinia_requests_api.TurbiniaRequestsApi(
+        self.client)
+    # We need to get the output path from the Turbinia server.
+    api_instance = turbinia_configuration_api.TurbiniaConfigurationApi(
+        self.client)
+    try:
+      api_response = api_instance.read_config()
+      self.output_path = api_response.get('OUTPUT_DIR')
+    except turbinia_api_lib.ApiException as exception:
+      self.ModuleError(exception.body, critical=True)
 
   def TurbiniaStart(
-    self,
-    evidence_: evidence.Evidence,
-    threat_intel_indicators: Optional[List[Optional[str]]] = None,
-    yara_rules: Optional[List[str]] = None) -> str:
-    """Creates and sends a Turbinia processing request.
+      self,
+      evidence: Dict[str, Any],
+      threat_intel_indicators: Optional[List[Optional[str]]] = None,
+      yara_rules: Optional[List[str]] = None) -> str:
+    """Sends a Turbinia processing request to the Turbinia API server.
 
     Args:
-      evidence_: The evidence to process.
+      evidence: The evidence to process.
       threat_intel_indicators: list of strings used as regular expressions in
           the Turbinia grepper module.
       yara_rules: List of Yara rule strings to use in the Turbinia Yara module.
+
     Returns:
-      Turbinia request ID.
+      Turbinia request identifier.
     """
-    evidence_.validate()
-    process_client = turbinia_client.get_turbinia_client()  # issues/600
-
-    recipe = None
-    jobs_denylist = None
-    yara_text = None
-
+    request_id = ''
+    yara_text = ''
     jobs_denylist = [
-        'StringsJob', 'BinaryExtractorJob', 'BulkExtractorJob', 'PhotorecJob'
+        'StringsJob', 'BinaryExtractorJob', 'BulkExtractorJob',
+        'PhotorecJob', 'PsortJob'
     ]
+    if not evidence:
+      self.ModuleError('No evidence to process', critical=True)
 
+    evidence_name = evidence.get('type')
     if yara_rules:
       yara_text = self.DEFAULT_YARA_MODULES + '\n'.join(list(yara_rules))
 
+    # Build request and request_options objects to send to the API server.
+    request_options: Dict[str, Any] = {
+        'filter_pattern': threat_intel_indicators,
+        'jobs_allowlist': [],
+        'jobs_denylist': jobs_denylist,
+        'reason': self.incident_id,
+        'requester': getpass.getuser(),
+        'yara_rules': yara_text
+    }
+    if self.sketch_id:
+      request_options['sketch_id'] = int(self.sketch_id)
+
     if self.turbinia_recipe:
-      # Use a pre-configured turbinia recipe
-      recipe = process_client.create_recipe(
-          recipe_name=self.turbinia_recipe,
-          sketch_id=self.sketch_id,
-          yara_rules=yara_text)
-    else:
-      # Use default recipe with custom parameters
-      recipe = process_client.create_recipe(
-          sketch_id=self.sketch_id,
-          filter_patterns=threat_intel_indicators,
-          jobs_denylist=jobs_denylist,
-          yara_rules=yara_text)
-
-    request = process_client.create_request(
-        requester=getpass.getuser(), recipe=recipe)
-    request.evidence.append(evidence_)
-    request_id = request.request_id  # type: str
-    self.logger.success(
-      'Creating Turbinia request {0:s} with Evidence {1!s}'.format(
-          request_id, evidence_.name))
-    process_client.send_request(request)
-    self.logger.info(
-        'Started Turbinia request {0:s}'.format(
-            request_id))
+      request_options['recipe_name'] = self.turbinia_recipe
+      # Remove incompatible options
+      try:
+        request_options.pop('filter_pattern')
+        request_options.pop('jobs_denylist')
+        request_options.pop('jobs_allowlist')
+      except KeyError as exception:
+        self.logger.debug(f'Key: {exception} not found in request options.')
+
+    request = {'evidence': evidence, 'request_options': request_options}
+
+    # Send the request to the API server.
+    try:
+      response = self.requests_api_instance.create_request(request)
+      request_id = response.get('request_id')
+      self.logger.success(
+          'Creating Turbinia request {0!s} with evidence {1!s}'.format(
+              request_id, evidence_name))
+    except turbinia_api_lib.ApiException as exception:
+      self.ModuleError(str(exception), critical=True)
+
+    if not request_id:
+      self.ModuleError('Unable to create Turbinia request', critical=True)
+
     return request_id
 
-  def TurbiniaWait(self, request_id: str) -> Tuple[List[Dict[str, str]], Any]:
-    """Waits for Turbinia Request to finish.
+  def TurbiniaWait(self,
+                   request_id: str) -> Iterator[Tuple[Dict[str, Any], str]]:
+    """This method waits until a Turbinia request finishes processing.
 
-    Args:
-      request_id: Request ID for the Turbinia Job.
+    On each iteration, it checks the status of the request and yields each task
+    data and a path that has not been processed in prior iterations. A path
+    is only considered if it is not already processed, if it is interesting
+    (i.e., not a log file or a temporary file), and if its path starts with the
+    Turbinia server configured output path.
 
-    Returns:
-      The Turbinia task data.
+    The method retries 3 times if there is an API exception.
 
-    Raises:
-      RuntimeError: If the Turbinia request fails for reasons not linked to
-          rate limiting.
+    Args:
+        request_id: Request identifier for the Turbinia Job.
+
+    Yields:
+        A tuple containing the Turbinia task data and the path that has not been
+          processed yet.
     """
-    request_dict = {
-        'instance': self.instance,
-        'project': self.project,
-        'region': self.turbinia_region,
-        'request_id': request_id
-    }
-    task_data = []  # type: List[Dict[str, str]]
-    process_client = turbinia_client.get_turbinia_client()
-    # Workaround for rate limiting in turbinia when checking task status
-    while True:
+
+    interval = 30
+    retries = 0
+    processed_paths = set()
+    status = 'running'
+    if not request_id:
+      self.ModuleError('No request ID provided', critical=True)
+
+    while status == 'running' and retries < 3:
+      time.sleep(interval)
       try:
-        process_client.wait_for_request(**request_dict)
-        task_data = process_client.get_task_data(**request_dict)
-
-        message = process_client.format_task_status(
-            full_report=True, **request_dict)
-        short_message = process_client.format_task_status(**request_dict)
-        self.logger.info(short_message)
-
-        return task_data, message
-      except RuntimeError as exception:
-        if 'Cloud function [gettasks] call failed' not in str(exception) and \
-            'RATE_LIMIT_EXCEEDED' not in str(exception):
-          raise exception
-        delay = 60 + random.randint(0, 30)
-        self.logger.info(
-            f'Rate limit for gettasks hit. Pausing {delay} seconds.')
-        time.sleep(delay)
+        request_data = self.requests_api_instance.get_request_status(request_id)
+        status = request_data.get('status')
+        failed_tasks = request_data.get('failed_tasks')
+        successful_tasks = request_data.get('successful_tasks')
+        task_count = request_data.get('task_count')
+        progress = math.ceil(
+            ((failed_tasks + successful_tasks) / task_count) * 100)
+        self.PublishMessage(
+            f'Turbinia request {request_id} is {status}. Progress: {progress}%')
+
+        for task in request_data.get('tasks', []):
+          current_saved_paths = task.get('saved_paths', [])
+          if not current_saved_paths:
+            continue
+          for path in current_saved_paths:
+            if path not in processed_paths and self._isInterestingPath(
+                path) and path.startswith(self.output_path):
+              processed_paths.add(path)
+              yield task, path
+
+      except turbinia_api_lib.ApiException as exception:
+        retries += 1
+        self.logger.warning(f'Retrying after exception: {exception.body}')
+
+  def TurbiniaFinishReport(self, request_id: str) -> str:
+    """This method generates a report for a Turbinia request."""
+    request_data = self.requests_api_instance.get_request_status(request_id)
+    if request_data:
+      report: str = turbinia_formatter.RequestMarkdownReport(
+          request_data=request_data).generate_markdown()
+    return report
diff --git a/dftimewolf/lib/processors/turbinia_base_legacy.py b/dftimewolf/lib/processors/turbinia_base_legacy.py
new file mode 100644
index 000000000..9415fd408
--- /dev/null
+++ b/dftimewolf/lib/processors/turbinia_base_legacy.py
@@ -0,0 +1,264 @@
+# -*- coding: utf-8 -*-
+"""[DEPRECATED] Base class for turbinia interactions."""
+
+import getpass
+import os
+import random
+import tempfile
+import time
+from typing import Dict, List, Optional, Tuple, Any, Union
+
+# pylint: disable=import-error
+# pytype: disable=import-error
+from turbinia import TurbiniaException
+from turbinia import client as turbinia_client
+from turbinia import config as turbinia_config
+from turbinia import evidence, output_manager
+
+from dftimewolf.lib.logging_utils import WolfLogger
+
+
+# pylint: disable=abstract-method,no-member
+class TurbiniaProcessorBaseLegacy(object):
+  """Base class for processing with Turbinia.
+
+  Attributes:
+    turbinia_config_file (str): Full path to the Turbinia config file to use.
+    client (TurbiniaClient): Turbinia client.
+    instance (str): name of the Turbinia instance
+    project (str): name of the GCP project containing the disk to process.
+    sketch_id (int): The Timesketch sketch id
+    turbinia_recipe (str): Turbinia recipe name.
+    turbinia_region (str): GCP region in which the Turbinia server is running.
+    turbinia_zone (str): GCP zone in which the Turbinia server is running.
+  """
+
+  DEFAULT_YARA_MODULES = 'import "pe"\nimport "math"\nimport "hash"\n\n'
+
+  def __init__(self, logger: WolfLogger) -> None:
+    """Initializes a Turbinia base processor.
+
+    Args:
+      state (state.DFTimewolfState): recipe state.
+      name (Optional[str]): The module's runtime name.
+      critical (Optional[bool]): True if the module is critical, which causes
+          the entire recipe to fail if the module encounters an error.
+    """
+    self.turbinia_config_file = ''  # type: Any
+    self._output_path = str()
+    self.client = None  # type: turbinia_client.BaseTurbiniaClient
+    self.instance = None
+    self.project = str()
+    self.sketch_id = int()
+    self.turbinia_recipe = str()  # type: Any
+    self.turbinia_region = None
+    self.turbinia_zone = str()
+    self.parallel_count = 5  # Arbitrary, used by ThreadAwareModule
+    self.logger = logger
+
+    os.environ['GRPC_POLL_STRATEGY'] = 'poll'
+
+  def _DeterminePaths(
+      self, task_data: List[Dict[str, Any]]) -> Tuple[List[str], List[str]]:
+    """Builds lists of local and remote paths from data returned by Turbinia.
+
+    This finds all .plaso, hashes.json, and BinaryExtractorTask files in the
+    Turbinia output, and determines if they are local or remote (it's possible
+    this will be running against a local instance of Turbinia).
+
+    Args:
+      task_data (list[dict]): List of dictionaries representing Turbinia task
+          data.
+
+    Returns:
+      tuple[list, list]: A tuple of two lists. The first element contains the
+          local paths, the second element contains the remote (GS) paths.
+    """
+    local_paths = []
+    gs_paths = []
+    for task in task_data:
+      # saved_paths may be set to None
+      saved_paths = task.get('saved_paths') or []
+      for path in saved_paths:
+
+        if path.endswith('.plaso') or \
+            path.endswith('BinaryExtractorTask.tar.gz') or \
+            path.endswith('hashes.json') or \
+            path.endswith('fraken_stdout.log') or \
+            path.endswith('loki_stdout.log'):
+
+          if path.startswith('gs://'):
+            gs_paths.append(path)
+          else:
+            local_paths.append(path)
+
+    return local_paths, gs_paths
+
+  def _DownloadFilesFromGCS(self, timeline_label: str,
+                            gs_paths: List[str]) -> List[Tuple[str, str]]:
+    """Downloads files stored in Google Cloud Storage to the local filesystem.
+
+    Args:
+      timeline_label (str): Label to use to construct the path list.
+      gs_paths (List[str]):  gs:// URI to files that need to be downloaded
+          from GS.
+
+    Raises:
+      TurbiniaException: Upon errors downloading from GCS
+
+    Returns:
+      list:
+        tuple: containing:
+          str: The timeline label for this path.
+          str: A local path where GS files have been copied to.
+    """
+    # TODO: Externalize fetching files from GCS buckets to a different module.
+
+    local_paths = []
+    for path in gs_paths:
+      local_path = None
+      output_writer = output_manager.GCSOutputWriter(
+          path, local_output_dir=self._output_path)
+      local_path = output_writer.copy_from(path)
+      self.logger.success('Downloaded {0:s} to {1:s}'.format(path, local_path))
+
+      if local_path:
+        local_paths.append((timeline_label, local_path))
+
+    return local_paths
+
+  def TurbiniaSetUp(
+      self, project: str, turbinia_recipe: Union[str, None], turbinia_zone: str,
+      sketch_id: int) -> None:
+    """Sets up the object attributes.
+
+    Raises:
+      TurbiniaException: For errors in setting up the Turbinia client.
+
+    Args:
+      project (str): name of the GCP project containing the disk to process.
+      turbinia_recipe (str): Turbinia recipe name.
+      turbinia_zone (str): GCP zone in which the Turbinia server is running.
+      sketch_id (int): The Timesketch sketch ID.
+    """
+    self.project = project
+    self.turbinia_recipe = turbinia_recipe
+    self.turbinia_zone = turbinia_zone
+    self.sketch_id = sketch_id
+
+    turbinia_config.LoadConfig(config_file=self.turbinia_config_file)
+    if not self.project:
+      self.project = turbinia_config.TURBINIA_PROJECT
+    if not self.turbinia_zone:
+      self.turbinia_zone = turbinia_config.TURBINIA_ZONE
+
+    if not self.project or not self.turbinia_zone:
+      raise TurbiniaException(
+          'project or turbinia_zone are not all specified, bailing out')
+
+    self.turbinia_region = turbinia_config.TURBINIA_REGION
+    self.instance = turbinia_config.INSTANCE_ID
+    if turbinia_config.TURBINIA_PROJECT != self.project:
+      raise TurbiniaException(
+          'Specified project {0!s} does not match Turbinia configured '
+          'project {1!s}. Use gcp_turbinia_disk_copy_ts recipe to copy the '
+          'disk into the same project.'.format(
+              self.project, turbinia_config.TURBINIA_PROJECT))
+    self._output_path = tempfile.mkdtemp()
+    self.client = turbinia_client.get_turbinia_client()
+
+  def TurbiniaStart(
+      self,
+      evidence_: evidence.Evidence,
+      threat_intel_indicators: Optional[List[Optional[str]]] = None,
+      yara_rules: Optional[List[str]] = None) -> str:
+    """Creates and sends a Turbinia processing request.
+
+    Args:
+      evidence_: The evidence to process.
+      threat_intel_indicators: list of strings used as regular expressions in
+          the Turbinia grepper module.
+      yara_rules: List of Yara rule strings to use in the Turbinia Yara module.
+    Returns:
+      Turbinia request identifier.
+    """
+    evidence_.validate()
+    process_client = turbinia_client.get_turbinia_client()  # issues/600
+
+    recipe = None
+    jobs_denylist = None
+    yara_text = None
+
+    jobs_denylist = [
+        'StringsJob', 'BinaryExtractorJob', 'BulkExtractorJob', 'PhotorecJob'
+    ]
+
+    if yara_rules:
+      yara_text = self.DEFAULT_YARA_MODULES + '\n'.join(list(yara_rules))
+
+    if self.turbinia_recipe:
+      # Use a pre-configured turbinia recipe
+      recipe = process_client.create_recipe(
+          recipe_name=self.turbinia_recipe,
+          sketch_id=self.sketch_id,
+          yara_rules=yara_text)
+    else:
+      # Use default recipe with custom parameters
+      recipe = process_client.create_recipe(
+          sketch_id=self.sketch_id,
+          filter_patterns=threat_intel_indicators,
+          jobs_denylist=jobs_denylist,
+          yara_rules=yara_text)
+
+    request = process_client.create_request(
+        requester=getpass.getuser(), recipe=recipe)
+    request.evidence.append(evidence_)
+    request_id = request.request_id  # type: str
+    self.logger.success(
+        'Creating Turbinia request {0:s} with Evidence {1!s}'.format(
+            request_id, evidence_.name))
+    process_client.send_request(request)
+    self.logger.info('Started Turbinia request {0:s}'.format(request_id))
+    return request_id
+
+  def TurbiniaWait(self, request_id: str) -> Tuple[List[Dict[str, str]], Any]:
+    """Waits for Turbinia Request to finish.
+
+    Args:
+      request_id: Request ID for the Turbinia Job.
+
+    Returns:
+      The Turbinia task data.
+
+    Raises:
+      RuntimeError: If the Turbinia request fails for reasons not linked to
+          rate limiting.
+    """
+    request_dict = {
+        'instance': self.instance,
+        'project': self.project,
+        'region': self.turbinia_region,
+        'request_id': request_id
+    }
+    task_data = []  # type: List[Dict[str, str]]
+    process_client = turbinia_client.get_turbinia_client()
+    # Workaround for rate limiting in turbinia when checking task status
+    while True:
+      try:
+        process_client.wait_for_request(**request_dict)
+        task_data = process_client.get_task_data(**request_dict)
+
+        message = process_client.format_task_status(
+            full_report=True, **request_dict)
+        short_message = process_client.format_task_status(**request_dict)
+        self.logger.info(short_message)
+
+        return task_data, message
+      except RuntimeError as exception:
+        if 'Cloud function [gettasks] call failed' not in str(exception) and \
+            'RATE_LIMIT_EXCEEDED' not in str(exception):
+          raise exception
+        delay = 60 + random.randint(0, 30)
+        self.logger.info(
+            f'Rate limit for gettasks hit. Pausing {delay} seconds.')
+        time.sleep(delay)
diff --git a/dftimewolf/lib/processors/turbinia_gcp.py b/dftimewolf/lib/processors/turbinia_gcp.py
index a63f1d584..214f0c9c5 100644
--- a/dftimewolf/lib/processors/turbinia_gcp.py
+++ b/dftimewolf/lib/processors/turbinia_gcp.py
@@ -1,13 +1,8 @@
-# -*- coding: utf-8 -*-
 """Processes GCP cloud disks using Turbinia."""
 
-import os
-from typing import Dict, List, Optional, TYPE_CHECKING, Any, Type, Union
+from typing import Any, Dict, Optional, TYPE_CHECKING, Type, Union, Set
 
 import magic
-from turbinia import TurbiniaException
-from turbinia import config as turbinia_config  #pylint: disable=unused-import
-from turbinia import evidence
 
 from dftimewolf.lib import module
 from dftimewolf.lib.containers import containers, interface
@@ -22,13 +17,14 @@ class TurbiniaGCPProcessor(TurbiniaProcessorBase, module.ThreadAwareModule):
   """Processes Google Cloud (GCP) disks with Turbinia.
 
   Attributes:
-    disk_name (str): name of the disk to process.
+    request_ids Set[str]: Turbinia requests for jobs being processed.
   """
 
-  def __init__(self,
-               state: "state.DFTimewolfState",
-               name: Optional[str]=None,
-               critical: bool=False) -> None:
+  def __init__(
+      self,
+      state: "state.DFTimewolfState",
+      name: Optional[str] = None,
+      critical: bool = False) -> None:
     """Initializes a Turbinia Google Cloud (GCP) disks processor.
 
     Args:
@@ -38,153 +34,205 @@ def __init__(self,
           the entire recipe to fail if the module encounters an error.
     """
     module.ThreadAwareModule.__init__(self, state, name=name, critical=critical)
-    TurbiniaProcessorBase.__init__(self, self.logger)
+    TurbiniaProcessorBase.__init__(
+        self, state, self.logger, name=name, critical=critical)
+    self.request_ids: Set[str] = set()
+
+  def _BuildContainer(
+      self, path: str, container_name: str
+  ) -> Optional[Union[containers.File, containers.ThreatIntelligence]]:
+    """Builds a container from a path."""
+    container: Optional[Union[containers.File,
+                              containers.ThreatIntelligence]] = None
+    if path.endswith('BinaryExtractorTask.tar.gz'):
+      container = containers.ThreatIntelligence(
+          name=container_name, indicator=None, path=path)
+    elif path.endswith('hashes.json'):
+      container = containers.ThreatIntelligence(
+          name=container_name, indicator=None, path=path)
+    elif path.endswith('.plaso'):
+      container = containers.File(name=container_name, path=path)
+    elif magic.from_file(path, mime=True).startswith('text'):
+      container = containers.File(name=container_name, path=path)
+    else:
+      self.PublishMessage(
+          f'Skipping result of type {magic.from_file(path)} at: {path}')
+
+    return container
+
+  def _CreateTurbiniaRequest(
+      self, request_container: containers.TurbiniaRequest) -> str:
+    """Creates a new Turbinia request.
+
+    Args:
+      request_container (containers.TurbiniaRequest): Turbinia request.
+
+    Returns:
+      str: Turbinia request identifier.
+    """
+    evidence = {
+        'type': 'GoogleCloudDisk',
+        'disk_name': request_container.evidence_name,
+        'project': self.project,
+        'zone': self.turbinia_zone
+    }
+    threat_intel_indicators = None
+    threatintel = self.GetContainers(containers.ThreatIntelligence)
+    if threatintel:
+      self.logger.info(
+          f'Sending {len(threatintel)} threatintel to Turbinia GrepWorkers...')
+      threat_intel_indicators = [item.indicator for item in threatintel]
+
+    yara_rules = None
+    yara_containers = self.GetContainers(containers.YaraRule)
+    if yara_containers:
+      self.logger.info(
+          f'Sending {len(yara_containers)} Yara rules to Turbinia '
+          'Plaso worker...')
+      yara_rules = [rule.rule_text for rule in yara_containers]
+
+    request_id = self.TurbiniaStart(
+        evidence, threat_intel_indicators, yara_rules)
+    if not request_id:
+      self.ModuleError('Turbinia request failed', critical=True)
+
+    return request_id
 
   # pylint: disable=arguments-differ
-  def SetUp(self,
-            turbinia_config_file: Union[str, None],
-            project: str,
-            turbinia_recipe: Union[str, None],
-            turbinia_zone: str,
-            sketch_id: int,
-            disk_names: str = '') -> None:
+  def SetUp(
+      self,
+      project: str,
+      turbinia_auth: bool,
+      turbinia_recipe: Union[str, None],
+      turbinia_zone: str,
+      turbinia_api: str,
+      incident_id: str,
+      sketch_id: int,
+      request_ids: str = '',
+      disk_names: str = '') -> None:
     """Sets up the object attributes.
 
     Args:
-      turbinia_config_file (str): Full path to the Turbinia config file to use.
       disk_names (str): names of the disks to process.
       project (str): name of the GCP project containing the disk to process.
+      turbinia_auth (bool): Turbinia auth flag.
+      turbinia_api (str): Turbinia API endpoint.
       turbinia_recipe (str): Turbinia recipe name.
       turbinia_zone (str): GCP zone in which the Turbinia server is running.
-      sketch_id (int): The Timesketch sketch ID.
+      incident_id (str): The incident ID.
+      sketch_id (int): The sketch ID.
+      request_ids (str): Turbinia requests for jobs being processed.
+      disk_names (str): Names of the disks to process.
     """
-    self.turbinia_config_file = turbinia_config_file
+
+    if (disk_names and request_ids):
+      self.ModuleError(
+          'One of disk_names or request_ids can be specified, but not both.',
+          critical=True)
+      return
+
+    if request_ids:
+      self.request_ids = {
+          request_ids.strip()
+          for request_ids in request_ids.split(',')
+          if request_ids.strip()
+      }
+      for request_id in self.request_ids:
+        self.StoreContainer(
+            containers.TurbiniaRequest(project=project, request_id=request_id))
 
     if disk_names:
       for disk in disk_names.strip().split(','):
         if not disk:
           continue
-        self.StoreContainer(containers.GCEDisk(
-            name=disk,
-            project=project))
-
-    try:
-      self.TurbiniaSetUp(project, turbinia_recipe, turbinia_zone, sketch_id)
-    except TurbiniaException as exception:
-      self.ModuleError(str(exception), critical=True)
-      return
+        self.StoreContainer(
+            containers.TurbiniaRequest(project=project, evidence_name=disk))
+
+    self.TurbiniaSetUp(
+        project, turbinia_auth, turbinia_recipe, turbinia_zone, turbinia_api,
+        incident_id, sketch_id)
 
   def PreProcess(self) -> None:
-    """Ensure ForensicsVM containers from previous modules are processed.
+    """Ensures containers from previous modules are processed.
 
-    Before the addition of containers.GCEDiskEvidence, containers.ForensicsVM
-    was used to track disks needing processing by Turbinia via this module. Here
-    we grab those containers and track the disks for processing by this module,
-    for any modules that aren't using the new container yet.
+    GCEDisk containers from preivous modules will be deduplicated, and
+    TurbiniaRequest containers will be created for each GCEDisk container.
+    This is necessary because TurbiniaRequest containers are used to track
+    Turbinia jobs (e.g. to support resuming a recipe after a failure).
     """
     vm_containers = self.GetContainers(containers.ForensicsVM)
     for container in vm_containers:
       if container.evidence_disk and container.evidence_disk.name:
         self.StoreContainer(
             containers.GCEDisk(
-                name=container.evidence_disk.name,
-                project=self.project))
+                name=container.evidence_disk.name, project=self.project))
+
     self.state.DedupeContainers(containers.GCEDisk)
 
-  # pylint: disable=arguments-renamed
-  def Process(self, disk_container: containers.GCEDisk) -> None:
-    """Process a GCE Disk with Turbinia."""
-    if disk_container.project != self.project:
-      self.logger.info(f'Found disk "{disk_container.name}" but skipping as it '
-          f'is in a different project "{disk_container.project}".')
+    disk_containers = self.GetContainers(containers.GCEDisk)
+    turb_containers = self.GetContainers(containers.TurbiniaRequest)
+    if not disk_containers and not turb_containers:
+      self.ModuleError(
+          'No disk names or request IDs specified, and there are no valid '
+          'containers to process from previous modules. '
+          'Please specify disk names or request IDs.',
+          critical=True)
       return
+    for disk_container in disk_containers:
+      self.StoreContainer(
+          containers.TurbiniaRequest(
+              project=disk_container.project,
+              evidence_name=disk_container.name))
 
-    log_file_path = os.path.join(
-        self._output_path, f'{disk_container.name}-turbinia.log')
-
-    self.logger.info(f'Turbinia log file: {log_file_path}')
-    self.logger.info(
-        f'Using disk {disk_container.name} from previous collector')
-
-    evidence_ = evidence.GoogleCloudDisk(
-        disk_name=disk_container.name,
-        project=self.project,
-        zone=self.turbinia_zone)
+  # pylint: disable=arguments-renamed
+  def Process(self, request_container: containers.TurbiniaRequest) -> None:
+    """Process a GCE Disk with Turbinia."""
+    request_id = ''
+    task: Dict[str, Any] = {}
+    report = ''
 
-    threat_intel_indicators = None
-    threatintel = self.GetContainers(containers.ThreatIntelligence)
-    if threatintel:
+    if request_container.project != self.project:
       self.logger.info(
-          f'Sending {len(threatintel)} threatintel to Turbinia GrepWorkers...')
-      threat_intel_indicators = [item.indicator for item in threatintel]
-
-    yara_rules = None
-    yara_containers = self.GetContainers(containers.YaraRule)
-    if yara_containers:
-      self.logger.info(f'Sending {len(yara_containers)} Yara rules to Turbinia '
-          'Plaso worker...')
-      yara_rules = [rule.rule_text for rule in yara_containers]
-
-    try:
-      request_id = self.TurbiniaStart(
-          evidence_, threat_intel_indicators, yara_rules)
-      self.PublishMessage(f'Turbinia request ID: {request_id}')
-      task_data, report = self.TurbiniaWait(request_id)
-    except TurbiniaException as exception:
-      self.ModuleError(str(exception), critical=True)
-
-    self.StoreContainer(containers.Report(
-        module_name='TurbiniaProcessor', text=report, text_format='markdown'))
-
-    local_paths, gs_paths = self._DeterminePaths(task_data)
+          f'Found disk "{request_container.evidence_name}" but skipping as it '
+          f'is in a different project "{request_container.project}".')
+      return
 
-    if not local_paths and not gs_paths:
-      self.ModuleError(
-          'No interesting files found in Turbinia output.', critical=True)
-
-    timeline_label = f'{self.project}-{disk_container.name}'
-    # Any local files that exist we can add immediately to the output
-    all_local_paths = [
-        (timeline_label, p) for p in local_paths if os.path.exists(p)]
-
-    try:
-      downloaded_gs_paths = self._DownloadFilesFromGCS(timeline_label, gs_paths)
-    except TurbiniaException as exception:
-      # Don't add a critical error for now, until we start raising errors
-      # instead of returning manually each
-      self.ModuleError(str(exception), critical=False)
-
-    all_local_paths.extend(downloaded_gs_paths)
-    self.logger.info(f'Collected {len(all_local_paths)} results')
-
-    if not all_local_paths:
-      self.ModuleError('No interesting files could be found.', critical=True)
-
-    container: Union[containers.File, containers.ThreatIntelligence]
-    for description, path in all_local_paths:
-      if path.endswith('BinaryExtractorTask.tar.gz'):
-        self.PublishMessage(f'Found BinaryExtractorTask result: {path}')
-        container = containers.ThreatIntelligence(
-            name='BinaryExtractorResults', indicator=None, path=path)
-      elif path.endswith('hashes.json'):
-        self.PublishMessage(f'Found hashes.json: {path}')
-        container = containers.ThreatIntelligence(
-            name='ImageExportHashes', indicator=None, path=path)
-      elif path.endswith('.plaso'):
-        self.PublishMessage(f'Found plaso result: {path}')
-        container = containers.File(name=description, path=path)
-      elif magic.from_file(path, mime=True).startswith('text'):
-        self.PublishMessage(f'Found result: {path}')
-        container = containers.File(name=description, path=path)
-      else:
-        self.PublishMessage(
-            f'Skipping result of type {magic.from_file(path)} at: {path}')
+    if request_container.request_id:
+      # We have a request ID, so we can skip creating a new Turbinia request.
+      request_id = request_container.request_id
+    else:
+      # We don't have a request ID, so we need to create a new Turbinia request.
+      request_id = self._CreateTurbiniaRequest(request_container)
+
+    self.PublishMessage(f'Turbinia request identifier: {request_id}')
+
+    for task, path in self.TurbiniaWait(request_id):
+      task_id = task.get('id')
+      task_name = task.get('name')
+      container_name = f'{self.project}-{task_name}-{task_id}'
+      self.PublishMessage(f'New output file {path} found for task {task_id}')
+      local_path = self.DownloadFilesFromAPI(task, path)
+      if not local_path:
+        self.logger.warning(
+            f'No interesting output files could be found for task {task_id}')
         continue
-      self.StoreContainer(container)
-  # pylint: enable=arguments-renamed
-
-  def GetThreadOnContainerType(self) -> Type[interface.AttributeContainer]:
-    return containers.GCEDisk
+      container = self._BuildContainer(local_path, container_name)
+      if container:
+        self.PublishMessage(f'Streaming container {container.name}')
+        self.StreamContainer(container)
+
+    # Generate a Turbinia report and store it in the state.
+    report = self.TurbiniaFinishReport(request_id)
+    self.StoreContainer(
+        containers.Report(
+            module_name='TurbiniaProcessor',
+            text=report,
+            text_format='markdown'))
+    self.PublishMessage(report)
+
+  @staticmethod
+  def GetThreadOnContainerType() -> Type[interface.AttributeContainer]:
+    return containers.TurbiniaRequest
 
   def GetThreadPoolSize(self) -> int:
     return self.parallel_count
diff --git a/dftimewolf/lib/processors/turbinia_gcp_legacy.py b/dftimewolf/lib/processors/turbinia_gcp_legacy.py
new file mode 100644
index 000000000..8e8f07628
--- /dev/null
+++ b/dftimewolf/lib/processors/turbinia_gcp_legacy.py
@@ -0,0 +1,201 @@
+"""[DEPRECATED] Processes GCP cloud disks using Turbinia."""
+
+import os
+from typing import Optional, TYPE_CHECKING, Type, Union
+
+import magic
+
+# pylint: disable=import-error
+# pytype: disable=import-error
+from turbinia import TurbiniaException
+from turbinia import config as turbinia_config  #pylint: disable=unused-import
+from turbinia import evidence
+
+
+from dftimewolf.lib import module
+from dftimewolf.lib.containers import containers, interface
+from dftimewolf.lib.modules import manager as modules_manager
+from dftimewolf.lib.processors.turbinia_base_legacy import (
+    TurbiniaProcessorBaseLegacy)
+
+if TYPE_CHECKING:
+  from dftimewolf.lib import state
+
+
+class TurbiniaGCPProcessorLegacy(
+    TurbiniaProcessorBaseLegacy, module.ThreadAwareModule):
+  """Processes Google Cloud (GCP) disks with Turbinia.
+
+  Attributes:
+    disk_name (str): name of the disk to process.
+  """
+
+  def __init__(self,
+               state: "state.DFTimewolfState",
+               name: Optional[str]=None,
+               critical: bool=False) -> None:
+    """Initializes a Turbinia Google Cloud (GCP) disks processor.
+
+    Args:
+      state (state.DFTimewolfState): recipe state.
+      name (Optional[str]): The module's runtime name.
+      critical (Optional[bool]): True if the module is critical, which causes
+          the entire recipe to fail if the module encounters an error.
+    """
+    module.ThreadAwareModule.__init__(self, state, name=name, critical=critical)
+    TurbiniaProcessorBaseLegacy.__init__(self, self.logger)
+
+  # pylint: disable=arguments-differ
+  def SetUp(self,
+            turbinia_config_file: Union[str, None],
+            project: str,
+            turbinia_recipe: Union[str, None],
+            turbinia_zone: str,
+            sketch_id: int,
+            disk_names: str = '') -> None:
+    """Sets up the object attributes.
+
+    Args:
+      turbinia_config_file (str): Full path to the Turbinia config file to use.
+      disk_names (str): names of the disks to process.
+      project (str): name of the GCP project containing the disk to process.
+      turbinia_recipe (str): Turbinia recipe name.
+      turbinia_zone (str): GCP zone in which the Turbinia server is running.
+      sketch_id (int): The Timesketch sketch ID.
+    """
+    self.turbinia_config_file = turbinia_config_file
+
+    if disk_names:
+      for disk in disk_names.strip().split(','):
+        if not disk:
+          continue
+        self.StoreContainer(containers.GCEDisk(
+            name=disk,
+            project=project))
+
+    try:
+      self.TurbiniaSetUp(project, turbinia_recipe, turbinia_zone, sketch_id)
+    except TurbiniaException as exception:
+      self.ModuleError(str(exception), critical=True)
+      return
+
+  def PreProcess(self) -> None:
+    """Ensure ForensicsVM containers from previous modules are processed.
+
+    Before the addition of containers.GCEDiskEvidence, containers.ForensicsVM
+    was used to track disks needing processing by Turbinia via this module. Here
+    we grab those containers and track the disks for processing by this module,
+    for any modules that aren't using the new container yet.
+    """
+    vm_containers = self.GetContainers(containers.ForensicsVM)
+    for container in vm_containers:
+      if container.evidence_disk and container.evidence_disk.name:
+        self.StoreContainer(
+            containers.GCEDisk(
+                name=container.evidence_disk.name,
+                project=self.project))
+    self.state.DedupeContainers(containers.GCEDisk)
+
+  # pylint: disable=arguments-renamed
+  def Process(self, disk_container: containers.GCEDisk) -> None:
+    """Process a GCE Disk with Turbinia."""
+    if disk_container.project != self.project:
+      self.logger.info(f'Found disk "{disk_container.name}" but skipping as it '
+          f'is in a different project "{disk_container.project}".')
+      return
+
+    log_file_path = os.path.join(
+        self._output_path, f'{disk_container.name}-turbinia.log')
+
+    self.logger.info(f'Turbinia log file: {log_file_path}')
+    self.logger.info(
+        f'Using disk {disk_container.name} from previous collector')
+
+    evidence_ = evidence.GoogleCloudDisk(
+        disk_name=disk_container.name,
+        project=self.project,
+        zone=self.turbinia_zone)
+
+    threat_intel_indicators = None
+    threatintel = self.GetContainers(containers.ThreatIntelligence)
+    if threatintel:
+      self.logger.info(
+          f'Sending {len(threatintel)} threatintel to Turbinia GrepWorkers...')
+      threat_intel_indicators = [item.indicator for item in threatintel]
+
+    yara_rules = None
+    yara_containers = self.GetContainers(containers.YaraRule)
+    if yara_containers:
+      self.logger.info(f'Sending {len(yara_containers)} Yara rules to Turbinia '
+          'Plaso worker...')
+      yara_rules = [rule.rule_text for rule in yara_containers]
+
+    try:
+      request_id = self.TurbiniaStart(
+          evidence_, threat_intel_indicators, yara_rules)
+      self.PublishMessage(f'Turbinia request identifier: {request_id}')
+      task_data, report = self.TurbiniaWait(request_id)
+    except TurbiniaException as exception:
+      self.ModuleError(str(exception), critical=True)
+
+    self.StoreContainer(containers.Report(
+        module_name='TurbiniaProcessor', text=report, text_format='markdown'))
+
+    local_paths, gs_paths = self._DeterminePaths(task_data)
+
+    if not local_paths and not gs_paths:
+      self.ModuleError(
+          'No interesting files found in Turbinia output.', critical=True)
+
+    timeline_label = f'{self.project}-{disk_container.name}'
+    # Any local files that exist we can add immediately to the output
+    all_local_paths = [
+        (timeline_label, p) for p in local_paths if os.path.exists(p)]
+
+    try:
+      downloaded_gs_paths = self._DownloadFilesFromGCS(timeline_label, gs_paths)
+    except TurbiniaException as exception:
+      # Don't add a critical error for now, until we start raising errors
+      # instead of returning manually each
+      self.ModuleError(str(exception), critical=False)
+
+    all_local_paths.extend(downloaded_gs_paths)
+    self.logger.info(f'Collected {len(all_local_paths)} results')
+
+    if not all_local_paths:
+      self.ModuleError('No interesting files could be found.', critical=True)
+
+    container: Union[containers.File, containers.ThreatIntelligence]
+    for description, path in all_local_paths:
+      if path.endswith('BinaryExtractorTask.tar.gz'):
+        self.PublishMessage(f'Found BinaryExtractorTask result: {path}')
+        container = containers.ThreatIntelligence(
+            name='BinaryExtractorResults', indicator=None, path=path)
+      elif path.endswith('hashes.json'):
+        self.PublishMessage(f'Found hashes.json: {path}')
+        container = containers.ThreatIntelligence(
+            name='ImageExportHashes', indicator=None, path=path)
+      elif path.endswith('.plaso'):
+        self.PublishMessage(f'Found plaso result: {path}')
+        container = containers.File(name=description, path=path)
+      elif magic.from_file(path, mime=True).startswith('text'):
+        self.PublishMessage(f'Found result: {path}')
+        container = containers.File(name=description, path=path)
+      else:
+        self.PublishMessage(
+            f'Skipping result of type {magic.from_file(path)} at: {path}')
+        continue
+      self.StoreContainer(container)
+  # pylint: enable=arguments-renamed
+
+  def GetThreadOnContainerType(self) -> Type[interface.AttributeContainer]:
+    return containers.GCEDisk
+
+  def GetThreadPoolSize(self) -> int:
+    return self.parallel_count
+
+  def PostProcess(self) -> None:
+    pass
+
+
+modules_manager.ModulesManager.RegisterModule(TurbiniaGCPProcessorLegacy)
diff --git a/dftimewolf/lib/state.py b/dftimewolf/lib/state.py
index ed1c56350..aa0778cda 100644
--- a/dftimewolf/lib/state.py
+++ b/dftimewolf/lib/state.py
@@ -23,6 +23,7 @@
 
 if TYPE_CHECKING:
   from dftimewolf.lib import module as dftw_module
+
 T = TypeVar("T", bound="interface.AttributeContainer")  # pylint: disable=invalid-name,line-too-long
 
 logger = logging.getLogger('dftimewolf.state')
@@ -31,6 +32,7 @@
 
 TELEMETRY = telemetry
 
+
 class DFTimewolfState(object):
   """The main State class.
 
@@ -49,8 +51,7 @@ class DFTimewolfState(object):
     telemetry_store: store for statistics generated by modules.
   """
 
-  def __init__(self,
-               config: Type[Config]) -> None:
+  def __init__(self, config: Type[Config]) -> None:
     """Initializes a state."""
     super(DFTimewolfState, self).__init__()
     self.command_line_options = {}  # type: Dict[str, Any]
@@ -60,8 +61,8 @@ def __init__(self,
     self._threading_event_per_module = {}  # type: Dict[str, threading.Event]
     self.config = config
     self.errors = []  # type: List[DFTimewolfError]
-    self.global_errors = [] # type: List[DFTimewolfError]
-    self.recipe = {} # type: Dict[str, Any]
+    self.global_errors = []  # type: List[DFTimewolfError]
+    self.recipe = {}  # type: Dict[str, Any]
     self.store = {}  # type: Dict[str, List[interface.AttributeContainer]]
     self.streaming_callbacks = {}  # type: Dict[Type[interface.AttributeContainer], List[Callable[[Any], Any]]]  # pylint: disable=line-too-long
     self._abort_execution = False
@@ -77,7 +78,7 @@ def _InvokeModulesInThreads(self, callback: Callable[[Any], Any]) -> None:
     """
     threads = []
     for module_definition in self.recipe['modules']:
-      thread_args = (module_definition, )
+      thread_args = (module_definition,)
       thread = threading.Thread(target=callback, args=thread_args)
       threads.append(thread)
       thread.start()
@@ -101,12 +102,18 @@ def ImportRecipeModules(self, module_locations: Dict[str, str]) -> None:
     """
     for module in self.recipe['modules'] + self.recipe.get('preflights', []):
       name = module['name']
+      if name.endswith('Legacy'):
+        msg = f'Skipping legacy module {name}, rename module to use.'
+        logger.warning(msg)
+        continue
       if name not in module_locations:
-        msg = (f'In {self.recipe["name"]}: module {name} cannot be found. '
-               'It may not have been declared.')
+        msg = (
+            f'In {self.recipe["name"]}: module {name} cannot be found. '
+            'It may not have been declared.')
         raise errors.RecipeParseError(msg)
-      logger.debug('Loading module {0:s} from {1:s}'.format(
-          name, module_locations[name]))
+      logger.debug(
+          'Loading module {0:s} from {1:s}'.format(
+              name, module_locations[name]))
 
       location = module_locations[name]
       try:
@@ -115,9 +122,8 @@ def ImportRecipeModules(self, module_locations: Dict[str, str]) -> None:
         msg = f'Cannot find Python module for {name} ({location}): {exception}'
         raise errors.RecipeParseError(msg)
 
-  def LoadRecipe(self,
-                 recipe: Dict[str, Any],
-                 module_locations: Dict[str, str]) -> None:
+  def LoadRecipe(
+      self, recipe: Dict[str, Any], module_locations: Dict[str, str]) -> None:
     """Populates the internal module pool with modules declared in a recipe.
 
     Args:
@@ -134,14 +140,14 @@ def LoadRecipe(self,
     for module_definition in module_definitions + preflight_definitions:
       # Combine CLI args with args from the recipe description
       module_name = module_definition['name']
-      module_class = modules_manager.ModulesManager.GetModuleByName(module_name)
-
       runtime_name = module_definition.get('runtime_name')
       if not runtime_name:
         runtime_name = module_name
-      # pytype: disable=wrong-arg-types
-      self._module_pool[runtime_name] = module_class(self, name=runtime_name)
-      # pytype: enable=wrong-arg-types
+      module_class = modules_manager.ModulesManager.GetModuleByName(module_name)
+      if module_class:
+        # pytype: disable=wrong-arg-types
+        self._module_pool[runtime_name] = module_class(self, name=runtime_name)
+        # pytype: enable=wrong-arg-types
 
   def FormatExecutionPlan(self) -> str:
     """Formats execution plan.
@@ -195,7 +201,7 @@ def AddToCache(self, name: str, value: Any) -> None:
     with self._state_lock:
       self._cache[name] = value
 
-  def GetFromCache(self, name: str, default_value: Any=None) -> Any:
+  def GetFromCache(self, name: str, default_value: Any = None) -> Any:
     """Thread-safe method to get data from the state's cache.
 
     Args:
@@ -212,9 +218,10 @@ def GetFromCache(self, name: str, default_value: Any=None) -> Any:
     with self._state_lock:
       return self._cache.get(name, default_value)
 
-  def StoreContainer(self,
-                     container: "interface.AttributeContainer",
-                     source_module: str = "") -> None:
+  def StoreContainer(
+      self,
+      container: "interface.AttributeContainer",
+      source_module: str = "") -> None:
     """Thread-safe method to store data in the state's store.
 
     Args:
@@ -222,28 +229,26 @@ def StoreContainer(self,
       source_module: the originating module.
     """
     with self._state_lock:
-      container.SetMetadata(
-          interface.METADATA_KEY_SOURCE_MODULE, source_module)
+      container.SetMetadata(interface.METADATA_KEY_SOURCE_MODULE, source_module)
       self.store.setdefault(container.CONTAINER_TYPE, []).append(container)
 
   def LogTelemetry(
-    self, telemetry_entry: telemetry.TelemetryCollection) -> None:
+      self, telemetry_entry: telemetry.TelemetryCollection) -> None:
     """Method to store telemetry in the state's telemetry store.
 
     Args:
       telemetry_entry: The telemetry object to store.
     """
     for key, value in telemetry_entry.telemetry.items():
-      if self.telemetry:
-        self.telemetry.LogTelemetry(
-          key, value, telemetry_entry.module_name, telemetry_entry.recipe
-        )
+      TELEMETRY.LogTelemetry(
+          key, value, telemetry_entry.module_name, telemetry_entry.recipe)
 
-  def GetContainers(self,
-                    container_class: Type[T],
-                    pop: bool=False,
-                    metadata_filter_key: Optional[str]=None,
-                    metadata_filter_value: Optional[Any]=None) -> Sequence[T]:
+  def GetContainers(
+      self,
+      container_class: Type[T],
+      pop: bool = False,
+      metadata_filter_key: Optional[str] = None,
+      metadata_filter_value: Optional[Any] = None) -> Sequence[T]:
     """Thread-safe method to retrieve data from the state's store.
 
     Args:
@@ -326,16 +331,18 @@ def _SetupModuleThread(self, module_definition: Dict[str, str]) -> None:
       # We're catching any exception that is not a DFTimewolfError, so we want
       # to generate an error for further reporting.
       error = errors.DFTimewolfError(
-          message=msg, name='dftimewolf', stacktrace=traceback.format_exc(),
-          critical=True, unexpected=True)
+          message=msg,
+          name='dftimewolf',
+          stacktrace=traceback.format_exc(),
+          critical=True,
+          unexpected=True)
       self.AddError(error)
 
     self._threading_event_per_module[runtime_name] = threading.Event()
     self.CleanUp()
 
-  def _RunModuleSetUp(self,
-                     module: BaseModule,
-                     **new_args: Dict[str, object]) -> None:
+  def _RunModuleSetUp(
+      self, module: BaseModule, **new_args: Dict[str, object]) -> None:
     """Runs SetUp of a single module.
 
     Designed to be wrapped by an output handling subclass.
@@ -355,8 +362,7 @@ def _RunModuleProcess(self, module: BaseModule) -> None:
     module.Process()
 
   def _RunModuleProcessThreaded(
-      self, module: ThreadAwareModule
-    ) -> List[Future]: # type: ignore
+      self, module: ThreadAwareModule) -> List[Future]:  # type: ignore
     """Runs Process of a single ThreadAwareModule module.
 
     Designed to be wrapped by an output handling subclass.
@@ -370,8 +376,8 @@ def _RunModuleProcessThreaded(
     """
     cont_count = len(self.GetContainers(module.GetThreadOnContainerType()))
     logger.info(
-       f'Running {cont_count} threads, max {module.GetThreadPoolSize()} '
-       f'simultaneous for module {module.name}')
+        f'Running {cont_count} threads, max {module.GetThreadPoolSize()} '
+        f'simultaneous for module {module.name}')
 
     futures = []
 
@@ -383,8 +389,7 @@ def _RunModuleProcessThreaded(
             f'Launching {module.name}.Process thread with {str(c)} from '
             f'{c.metadata.get(interface.METADATA_KEY_SOURCE_MODULE, "Unknown")}'
         )
-        futures.append(
-            executor.submit(module.Process, c))
+        futures.append(executor.submit(module.Process, c))
 
     return futures
 
@@ -418,7 +423,8 @@ def _HandleFuturesFromThreadedModule(
       runtime_name: runtime name of the module."""
     for fut in futures:
       if fut.exception():
-        raise fut.exception() # type: ignore
+        raise fut.exception()  # type: ignore
+
   # pylint: disable=unused-argument
 
   def SetupModules(self) -> None:
@@ -479,8 +485,11 @@ def _RunModuleThread(self, module_definition: Dict[str, str]) -> None:
       # We're catching any exception that is not a DFTimewolfError, so we want
       # to generate an error for further reporting.
       error = errors.DFTimewolfError(
-          message=msg, name='dftimewolf', stacktrace=traceback.format_exc(),
-          critical=True, unexpected=True)
+          message=msg,
+          name='dftimewolf',
+          stacktrace=traceback.format_exc(),
+          critical=True,
+          unexpected=True)
       self.AddError(error)
 
     logger.info('Module {0:s} finished execution'.format(runtime_name))
@@ -515,7 +524,7 @@ def CleanUpPreflights(self) -> None:
       finally:
         self.CheckErrors(is_global=True)
 
-  def InstantiateModule(self, module_name: str) -> "BaseModule":
+  def InstantiateModule(self, module_name: str) -> Optional["BaseModule"]:
     """Instantiates an arbitrary dfTimewolf module.
 
     Args:
@@ -523,22 +532,22 @@ def InstantiateModule(self, module_name: str) -> "BaseModule":
 
     Returns:
       BaseModule: An instance of a dftimewolf Module, which is a subclass of
-          BaseModule.
+          BaseModule, or None if the module could not be found.
     """
-    module_class: Type["BaseModule"]
+    module_class: Optional[Type["BaseModule"]]
     module_class = modules_manager.ModulesManager.GetModuleByName(module_name)
     # pytype: disable=wrong-arg-types
-    return module_class(self)
+    if module_class:
+      return module_class(self)
     # pytype: enable=wrong-arg-types
+    return None
 
   def RunModules(self) -> None:
     """Performs the actual processing for each module in the module pool."""
     self._InvokeModulesInThreads(self._RunModuleThread)
 
   def RegisterStreamingCallback(
-      self,
-      target: Callable[[T], Any],
-      container_type: Type[T]) -> None:
+      self, target: Callable[[T], Any], container_type: Type[T]) -> None:
     """Registers a callback for a type of container.
 
     The function to be registered should a single parameter of type
@@ -553,9 +562,10 @@ def RegisterStreamingCallback(
       self.streaming_callbacks[container_type] = []
     self.streaming_callbacks[container_type].append(target)
 
-  def StreamContainer(self,
-                      container: "interface.AttributeContainer",
-                      source_module: str = "") -> None:
+  def StreamContainer(
+      self,
+      container: "interface.AttributeContainer",
+      source_module: str = "") -> None:
     """Streams a container to the callbacks that are registered to handle it.
 
     Args:
@@ -587,7 +597,7 @@ def CleanUp(self) -> None:
     self.global_errors.extend(self.errors)
     self.errors = []
 
-  def CheckErrors(self, is_global: bool=False) -> None:
+  def CheckErrors(self, is_global: bool = False) -> None:
     """Checks for errors and exits if any of them are critical.
 
     Args:
@@ -604,8 +614,9 @@ def CheckErrors(self, is_global: bool=False) -> None:
       logger.error('dfTimewolf encountered one or more errors:')
 
     for index, error in enumerate(error_objects):
-      logger.error('{0:d}: error from {1:s}: {2:s}'.format(
-          index+1, error.name, error.message))
+      logger.error(
+          '{0:d}: error from {1:s}: {2:s}'.format(
+              index + 1, error.name, error.message))
       if error.stacktrace:
         for line in error.stacktrace.split('\n'):
           logger.error(line)
@@ -620,10 +631,8 @@ def CheckErrors(self, is_global: bool=False) -> None:
     if critical_errors:
       raise errors.CriticalError('Critical error found. Aborting.')
 
-  def PublishMessage(self,
-                     source: str,
-                     message: str,
-                     is_error: bool = False) -> None:
+  def PublishMessage(
+      self, source: str, message: str, is_error: bool = False) -> None:
     """Receives a message for publishing.
 
     The base class does nothing with this (as the method in module also logs the
@@ -635,20 +644,16 @@ def PublishMessage(self,
       is_error: True if the message is an error message, False otherwise.
     """
 
-  def ProgressUpdate(self,
-                     module_name: str,
-                     steps_taken: int,
-                     steps_expected: int) -> None:
+  def ProgressUpdate(
+      self, module_name: str, steps_taken: int, steps_expected: int) -> None:
     """Currently unsupported when no UI is in use."""
     if not self._progress_warning_shown:
       self._progress_warning_shown = True
       logger.debug('ProgressUpdate called in unsupported display mode.')
 
-  def ThreadProgressUpdate(self,
-                           module_name: str,
-                           thread_id: str,
-                           steps_taken: int,
-                           steps_expected: int) -> None:
+  def ThreadProgressUpdate(
+      self, module_name: str, thread_id: str, steps_taken: int,
+      steps_expected: int) -> None:
     """Currently unsupported when no UI is in use."""
     if not self._progress_warning_shown:
       self._progress_warning_shown = True
@@ -659,17 +664,15 @@ class DFTimewolfStateWithCDM(DFTimewolfState):
   """The main state class, extended to wrap methods with updates to a
   CursesDisplayManager object."""
 
-  def __init__(self,
-               config: Type[Config],
-               cursesdm: cdm.CursesDisplayManager) -> None:
+  def __init__(
+      self, config: Type[Config], cursesdm: cdm.CursesDisplayManager) -> None:
     """Initializes a state."""
     super(DFTimewolfStateWithCDM, self).__init__(config)
     self.cursesdm = cursesdm
     self.stdout_log = False
 
-  def LoadRecipe(self,
-                 recipe: Dict[str, Any],
-                 module_locations: Dict[str, str]) -> None:
+  def LoadRecipe(
+      self, recipe: Dict[str, Any], module_locations: Dict[str, str]) -> None:
     """Populates the internal module pool with modules declared in a recipe.
 
     Args:
@@ -685,18 +688,17 @@ def LoadRecipe(self,
 
     self.cursesdm.SetRecipe(self.recipe['name'])
     for module_definition in preflight_definitions:
-      self.cursesdm.EnqueuePreflight(module_definition['name'],
-                                     module_definition.get('wants', []),
-                                     module_definition.get('runtime_name'))
+      self.cursesdm.EnqueuePreflight(
+          module_definition['name'], module_definition.get('wants', []),
+          module_definition.get('runtime_name'))
     for module_definition in module_definitions:
-      self.cursesdm.EnqueueModule(module_definition['name'],
-                                  module_definition.get('wants', []),
-                                  module_definition.get('runtime_name'))
+      self.cursesdm.EnqueueModule(
+          module_definition['name'], module_definition.get('wants', []),
+          module_definition.get('runtime_name'))
     self.cursesdm.Draw()
 
-  def _RunModuleSetUp(self,
-                     module: BaseModule,
-                     **new_args: Dict[str, object]) -> None:
+  def _RunModuleSetUp(
+      self, module: BaseModule, **new_args: Dict[str, object]) -> None:
     """Runs SetUp of a single module.
 
     Args:
@@ -716,8 +718,7 @@ def _RunModuleProcess(self, module: BaseModule) -> None:
     self.cursesdm.UpdateModuleStatus(module.name, cdm.Status.COMPLETED)
 
   def _RunModuleProcessThreaded(
-      self, module: ThreadAwareModule
-    ) -> List[Future]: # type: ignore
+      self, module: ThreadAwareModule) -> List[Future]:  # type: ignore
     """Runs Process of a single ThreadAwareModule module.
 
     Args:
@@ -729,8 +730,8 @@ def _RunModuleProcessThreaded(
     """
     cont_count = len(self.GetContainers(module.GetThreadOnContainerType()))
     logger.info(
-       f'Running {cont_count} threads, max {module.GetThreadPoolSize()} '
-       f'simultaneous for module {module.name}')
+        f'Running {cont_count} threads, max {module.GetThreadPoolSize()} '
+        f'simultaneous for module {module.name}')
 
     self.cursesdm.SetThreadedModuleContainerCount(module.name, cont_count)
     self.cursesdm.UpdateModuleStatus(module.name, cdm.Status.PROCESSING)
@@ -742,8 +743,7 @@ def _RunModuleProcessThreaded(
       pop = not module.KeepThreadedContainersInState()
       for c in self.GetContainers(module.GetThreadOnContainerType(), pop):
         futures.append(
-            executor.submit(
-                self._WrapThreads, module.Process, c, module.name))
+            executor.submit(self._WrapThreads, module.Process, c, module.name))
 
     return futures
 
@@ -779,10 +779,9 @@ def _HandleFuturesFromThreadedModule(
         self.cursesdm.SetError(runtime_name, str(fut.exception()))
         raise fut.exception()  # type: ignore
 
-  def _WrapThreads(self,
-                  process: Callable[[AttributeContainer], None],
-                  container: AttributeContainer,
-                  module_name: str) -> None:
+  def _WrapThreads(
+      self, process: Callable[[AttributeContainer], None],
+      container: AttributeContainer, module_name: str) -> None:
     """Wraps a ThreadPoolExecutor call to module.process with the
     CursesDisplayManager status update methods.
 
@@ -811,10 +810,8 @@ def AddError(self, error: DFTimewolfError) -> None:
     name = error.name if error.name else 'no_module_name'
     self.cursesdm.SetError(name, error.message)
 
-  def PublishMessage(self,
-                     source: str,
-                     message: str,
-                     is_error: bool = False) -> None:
+  def PublishMessage(
+      self, source: str, message: str, is_error: bool = False) -> None:
     """Receives a message for publishing to the list of messages.
 
     Args:
@@ -823,10 +820,8 @@ def PublishMessage(self,
       is_error: True if the message is an error message, False otherwise."""
     self.cursesdm.EnqueueMessage(source, message, is_error)
 
-  def ProgressUpdate(self,
-                     module_name: str,
-                     steps_taken: int,
-                     steps_expected: int) -> None:
+  def ProgressUpdate(
+      self, module_name: str, steps_taken: int, steps_expected: int) -> None:
     """Set the current completion status of a module.
 
     Args:
@@ -834,14 +829,11 @@ def ProgressUpdate(self,
       steps_taken: The number of steps taken so far.
       steps_expected: The number of total steps expected for completion.
     """
-    self.cursesdm.SetModuleProgress(
-        module_name, steps_taken, steps_expected)
-
-  def ThreadProgressUpdate(self,
-                           module_name: str,
-                           thread_id: str,
-                           steps_taken: int,
-                           steps_expected: int) -> None:
+    self.cursesdm.SetModuleProgress(module_name, steps_taken, steps_expected)
+
+  def ThreadProgressUpdate(
+      self, module_name: str, thread_id: str, steps_taken: int,
+      steps_expected: int) -> None:
     """Set the current completion status of a module thread.
 
     Args:
diff --git a/poetry.lock b/poetry.lock
index 02e42f311..8682f65a6 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1,4 +1,4 @@
-# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand.
+# This file is automatically @generated by Poetry and should not be changed by hand.
 
 [[package]]
 name = "adal"
@@ -129,6 +129,7 @@ speedups = ["Brotli", "aiodns", "cchardet"]
 name = "aiosignal"
 version = "1.3.1"
 description = "aiosignal: a list of registered asynchronous callbacks"
+category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -141,32 +142,33 @@ frozenlist = ">=1.1.0"
 
 [[package]]
 name = "altair"
-version = "5.0.1"
-description = "Vega-Altair: A declarative statistical visualization library for Python."
+version = "4.2.2"
+description = "Altair: A declarative statistical visualization library for Python."
+category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
-    {file = "altair-5.0.1-py3-none-any.whl", hash = "sha256:9f3552ed5497d4dfc14cf48a76141d8c29ee56eae2873481b4b28134268c9bbe"},
-    {file = "altair-5.0.1.tar.gz", hash = "sha256:087d7033cb2d6c228493a053e12613058a5d47faf6a36aea3ff60305fd8b4cb0"},
+    {file = "altair-4.2.2-py3-none-any.whl", hash = "sha256:8b45ebeaf8557f2d760c5c77b79f02ae12aee7c46c27c06014febab6f849bc87"},
+    {file = "altair-4.2.2.tar.gz", hash = "sha256:39399a267c49b30d102c10411e67ab26374156a84b1aeb9fcd15140429ba49c5"},
 ]
 
 [package.dependencies]
+entrypoints = "*"
 jinja2 = "*"
 jsonschema = ">=3.0"
 numpy = "*"
 pandas = ">=0.18"
 toolz = "*"
-typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""}
 
 [package.extras]
-dev = ["black (<24)", "hatch", "ipython", "m2r", "mypy", "pandas-stubs", "pytest", "pytest-cov", "ruff", "types-jsonschema", "types-setuptools", "vega-datasets", "vl-convert-python"]
-doc = ["docutils", "geopandas", "jinja2", "myst-parser", "numpydoc", "pillow", "pydata-sphinx-theme", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinxext-altair"]
+dev = ["black", "docutils", "flake8", "ipython", "m2r", "mistune (<2.0.0)", "pytest", "recommonmark", "sphinx", "vega-datasets"]
 
 [[package]]
 name = "amqp"
 version = "5.1.1"
 description = "Low-level AMQP client for Python (fork of amqplib)."
-optional = false
+category = "main"
+optional = true
 python-versions = ">=3.6"
 files = [
     {file = "amqp-5.1.1-py3-none-any.whl", hash = "sha256:6f0956d2c23d8fa6e7691934d8c3930eadb44972cbbd1a7ae3a520f735d43359"},
@@ -180,6 +182,7 @@ vine = ">=5.0.0"
 name = "astroid"
 version = "2.7.3"
 description = "An abstract syntax tree for Python with inference support."
+category = "dev"
 optional = false
 python-versions = "~=3.6"
 files = [
@@ -196,6 +199,7 @@ wrapt = ">=1.11,<1.13"
 name = "async-timeout"
 version = "4.0.2"
 description = "Timeout context manager for asyncio programs"
+category = "main"
 optional = false
 python-versions = ">=3.6"
 files = [
@@ -205,26 +209,28 @@ files = [
 
 [[package]]
 name = "attrs"
-version = "23.1.0"
+version = "22.2.0"
 description = "Classes Without Boilerplate"
+category = "main"
 optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.6"
 files = [
-    {file = "attrs-23.1.0-py3-none-any.whl", hash = "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04"},
-    {file = "attrs-23.1.0.tar.gz", hash = "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015"},
+    {file = "attrs-22.2.0-py3-none-any.whl", hash = "sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836"},
+    {file = "attrs-22.2.0.tar.gz", hash = "sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99"},
 ]
 
 [package.extras]
-cov = ["attrs[tests]", "coverage[toml] (>=5.3)"]
-dev = ["attrs[docs,tests]", "pre-commit"]
-docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"]
-tests = ["attrs[tests-no-zope]", "zope-interface"]
-tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"]
+cov = ["attrs[tests]", "coverage-enable-subprocess", "coverage[toml] (>=5.3)"]
+dev = ["attrs[docs,tests]"]
+docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope.interface"]
+tests = ["attrs[tests-no-zope]", "zope.interface"]
+tests-no-zope = ["cloudpickle", "cloudpickle", "hypothesis", "hypothesis", "mypy (>=0.971,<0.990)", "mypy (>=0.971,<0.990)", "pympler", "pympler", "pytest (>=4.3.0)", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-mypy-plugins", "pytest-xdist[psutil]", "pytest-xdist[psutil]"]
 
 [[package]]
 name = "azure-common"
 version = "1.1.28"
 description = "Microsoft Azure Client Library for Python (Common)"
+category = "main"
 optional = false
 python-versions = "*"
 files = [
@@ -234,13 +240,14 @@ files = [
 
 [[package]]
 name = "azure-core"
-version = "1.27.1"
+version = "1.26.4"
 description = "Microsoft Azure Core Library for Python"
+category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
-    {file = "azure-core-1.27.1.zip", hash = "sha256:5975c20808fa388243f01a8b79021bfbe114f503a27c543f002c5fc8bbdd73dd"},
-    {file = "azure_core-1.27.1-py3-none-any.whl", hash = "sha256:1b4b19f455eb7b4332c6f92adc2c669353ded07c2722eb436165f0c253737792"},
+    {file = "azure-core-1.26.4.zip", hash = "sha256:075fe06b74c3007950dd93d49440c2f3430fd9b4a5a2756ec8c79454afc989c6"},
+    {file = "azure_core-1.26.4-py3-none-any.whl", hash = "sha256:d9664b4bc2675d72fba461a285ac43ae33abb2967014a955bf136d9703a2ab3c"},
 ]
 
 [package.dependencies]
@@ -253,19 +260,20 @@ aio = ["aiohttp (>=3.0)"]
 
 [[package]]
 name = "azure-identity"
-version = "1.13.0"
+version = "1.12.0"
 description = "Microsoft Azure Identity Library for Python"
+category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
-    {file = "azure-identity-1.13.0.zip", hash = "sha256:c931c27301ffa86b07b4dcf574e29da73e3deba9ab5d1fe4f445bb6a3117e260"},
-    {file = "azure_identity-1.13.0-py3-none-any.whl", hash = "sha256:bd700cebb80cd9862098587c29d8677e819beca33c62568ced6d5a8e5e332b82"},
+    {file = "azure-identity-1.12.0.zip", hash = "sha256:7f9b1ae7d97ea7af3f38dd09305e19ab81a1e16ab66ea186b6579d85c1ca2347"},
+    {file = "azure_identity-1.12.0-py3-none-any.whl", hash = "sha256:2a58ce4a209a013e37eaccfd5937570ab99e9118b3e1acf875eed3a85d541b92"},
 ]
 
 [package.dependencies]
 azure-core = ">=1.11.0,<2.0.0"
 cryptography = ">=2.5"
-msal = ">=1.20.0,<2.0.0"
+msal = ">=1.12.0,<2.0.0"
 msal-extensions = ">=0.3.0,<2.0.0"
 six = ">=1.12.0"
 
@@ -273,6 +281,7 @@ six = ">=1.12.0"
 name = "azure-mgmt-compute"
 version = "23.1.0"
 description = "Microsoft Azure Compute Management Client Library for Python"
+category = "main"
 optional = false
 python-versions = "*"
 files = [
@@ -289,6 +298,7 @@ msrest = ">=0.6.21"
 name = "azure-mgmt-core"
 version = "1.4.0"
 description = "Microsoft Azure Management Core Library for Python"
+category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -301,29 +311,31 @@ azure-core = ">=1.26.2,<2.0.0"
 
 [[package]]
 name = "azure-mgmt-monitor"
-version = "6.0.1"
+version = "6.0.0"
 description = "Microsoft Azure Monitor Client Library for Python"
+category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
-    {file = "azure-mgmt-monitor-6.0.1.zip", hash = "sha256:8fe2eb9cbb1fbd1365a252987fbd99517c60e656f7dd1ecf7f1bde052cb11c32"},
-    {file = "azure_mgmt_monitor-6.0.1-py3-none-any.whl", hash = "sha256:fb54cf4b2a75ca7e8ae7a066baef1d82bb0fc6bc221b2b745d841271adb9625b"},
+    {file = "azure-mgmt-monitor-6.0.0.zip", hash = "sha256:521b3ec33dec07998eb752c6242ab384c4f9616c30327a66562e16bd71c17d96"},
+    {file = "azure_mgmt_monitor-6.0.0-py3-none-any.whl", hash = "sha256:1c8b5ae8e0243ca091cfd2612540d7ae97c975c2ce4f8c71fd6aa5221fe7ba91"},
 ]
 
 [package.dependencies]
 azure-common = ">=1.1,<2.0"
 azure-mgmt-core = ">=1.3.2,<2.0.0"
-isodate = ">=0.6.1,<1.0.0"
+msrest = ">=0.7.1"
 
 [[package]]
 name = "azure-mgmt-network"
-version = "23.1.0"
+version = "23.0.0"
 description = "Microsoft Azure Network Management Client Library for Python"
+category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
-    {file = "azure-mgmt-network-23.1.0.zip", hash = "sha256:37172699113831d7ce1bb9da35c635007c73cffc549a85a58f55542b54b12bf3"},
-    {file = "azure_mgmt_network-23.1.0-py3-none-any.whl", hash = "sha256:83afb63fdf7a526a4b2ee32e0a6038e9d05d4182bcaac1e0190a59152165648d"},
+    {file = "azure-mgmt-network-23.0.0.zip", hash = "sha256:d9a878b41a2efe28ba6bba339da3ae2d4b7bd038994f5b45a819360c01842927"},
+    {file = "azure_mgmt_network-23.0.0-py3-none-any.whl", hash = "sha256:8943944e1f5ed8ce9a6c687d7b15827a49f3ca4bafded6672e10485fcaecf007"},
 ]
 
 [package.dependencies]
@@ -333,13 +345,14 @@ isodate = ">=0.6.1,<1.0.0"
 
 [[package]]
 name = "azure-mgmt-resource"
-version = "23.0.1"
+version = "23.0.0"
 description = "Microsoft Azure Resource Management Client Library for Python"
+category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
-    {file = "azure-mgmt-resource-23.0.1.zip", hash = "sha256:c2ba6cfd99df95f55f36eadc4245e3dc713257302a1fd0277756d94bd8cb28e0"},
-    {file = "azure_mgmt_resource-23.0.1-py3-none-any.whl", hash = "sha256:f185eec72bbc39f42bcb83ae6f1bad744f0e3f20a12d9b2b3e70d16c74ad9cc0"},
+    {file = "azure-mgmt-resource-23.0.0.zip", hash = "sha256:e6137abc3244efdecc730feed05b17542ccdaf8735a66b9141ad0a378d872922"},
+    {file = "azure_mgmt_resource-23.0.0-py3-none-any.whl", hash = "sha256:b556803a3b8699dbcd1f4c33c8229830225c8c61ba058323d5bae7dc7785d65e"},
 ]
 
 [package.dependencies]
@@ -351,6 +364,7 @@ isodate = ">=0.6.1,<1.0.0"
 name = "azure-mgmt-storage"
 version = "21.0.0"
 description = "Microsoft Azure Storage Management Client Library for Python"
+category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -365,13 +379,14 @@ msrest = ">=0.7.1"
 
 [[package]]
 name = "azure-storage-blob"
-version = "12.16.0"
+version = "12.15.0"
 description = "Microsoft Azure Blob Storage Client Library for Python"
+category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
-    {file = "azure-storage-blob-12.16.0.zip", hash = "sha256:43b45f19a518a5c6895632f263b3825ebc23574f25cc84b66e1630a6160e466f"},
-    {file = "azure_storage_blob-12.16.0-py3-none-any.whl", hash = "sha256:91bb192b2a97939c4259c72373bac0f41e30810bbc853d5184f0f45904eacafd"},
+    {file = "azure-storage-blob-12.15.0.zip", hash = "sha256:f8b8d582492740ab16744455408342fb8e4c8897b64a8a3fc31743844722c2f2"},
+    {file = "azure_storage_blob-12.15.0-py3-none-any.whl", hash = "sha256:08d8807c577c63a436740627927c1a03a97c963efc29af5c818aed906590e1cf"},
 ]
 
 [package.dependencies]
@@ -387,6 +402,7 @@ aio = ["azure-core[aio] (>=1.26.0,<2.0.0)"]
 name = "beautifulsoup4"
 version = "4.12.2"
 description = "Screen-scraping library"
+category = "main"
 optional = false
 python-versions = ">=3.6.0"
 files = [
@@ -403,28 +419,30 @@ lxml = ["lxml"]
 
 [[package]]
 name = "billiard"
-version = "4.1.0"
+version = "3.6.4.0"
 description = "Python multiprocessing fork with improvements and bugfixes"
-optional = false
-python-versions = ">=3.7"
+category = "main"
+optional = true
+python-versions = "*"
 files = [
-    {file = "billiard-4.1.0-py3-none-any.whl", hash = "sha256:0f50d6be051c6b2b75bfbc8bfd85af195c5739c281d3f5b86a5640c65563614a"},
-    {file = "billiard-4.1.0.tar.gz", hash = "sha256:1ad2eeae8e28053d729ba3373d34d9d6e210f6e4d8bf0a9c64f92bd053f1edf5"},
+    {file = "billiard-3.6.4.0-py3-none-any.whl", hash = "sha256:87103ea78fa6ab4d5c751c4909bcff74617d985de7fa8b672cf8618afd5a875b"},
+    {file = "billiard-3.6.4.0.tar.gz", hash = "sha256:299de5a8da28a783d51b197d496bef4f1595dd023a93a4f59dde1886ae905547"},
 ]
 
 [[package]]
 name = "boto3"
-version = "1.26.153"
+version = "1.26.110"
 description = "The AWS SDK for Python"
+category = "main"
 optional = false
 python-versions = ">= 3.7"
 files = [
-    {file = "boto3-1.26.153-py3-none-any.whl", hash = "sha256:ec3a4aef45d16d9362191aa245a31059df009cd73668d0c3b15126cfeb5d3fd7"},
-    {file = "boto3-1.26.153.tar.gz", hash = "sha256:92de7eec15adda76abff0580b1e8ca70646470fba4c807934062456d0c5c9171"},
+    {file = "boto3-1.26.110-py3-none-any.whl", hash = "sha256:8972a5e0a04ea6f477c41e390765a46ec7bcffb62f99d4a0774ce70fb87bea59"},
+    {file = "boto3-1.26.110.tar.gz", hash = "sha256:97d942d958cac28687187b89ee88ac760e0fa3007094cb1d6b16e241144306f3"},
 ]
 
 [package.dependencies]
-botocore = ">=1.29.153,<1.30.0"
+botocore = ">=1.29.110,<1.30.0"
 jmespath = ">=0.7.1,<2.0.0"
 s3transfer = ">=0.6.0,<0.7.0"
 
@@ -433,13 +451,14 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"]
 
 [[package]]
 name = "botocore"
-version = "1.29.153"
+version = "1.29.110"
 description = "Low-level, data-driven core of boto 3."
+category = "main"
 optional = false
 python-versions = ">= 3.7"
 files = [
-    {file = "botocore-1.29.153-py3-none-any.whl", hash = "sha256:d6d3294fde297ae76fadb4bafad93ef958f145bb9a502bf7b1e57ff6f8dc4039"},
-    {file = "botocore-1.29.153.tar.gz", hash = "sha256:d59b8e87138581a339b9f84a9e90bc4c1a152cf8ca2adbaad7792a4c4125bffa"},
+    {file = "botocore-1.29.110-py3-none-any.whl", hash = "sha256:39879fcc3d263513f9ba92cc5060b5a4dbe54f758a917be29c7a71132e34f399"},
+    {file = "botocore-1.29.110.tar.gz", hash = "sha256:9d5054159782b19f27bff3e5a65bc494dc323255e889ea3abec002711a1fb0c0"},
 ]
 
 [package.dependencies]
@@ -454,6 +473,7 @@ crt = ["awscrt (==0.16.9)"]
 name = "cachetools"
 version = "4.2.4"
 description = "Extensible memoizing collections and decorators"
+category = "main"
 optional = false
 python-versions = "~=3.5"
 files = [
@@ -463,74 +483,76 @@ files = [
 
 [[package]]
 name = "celery"
-version = "5.3.0"
+version = "5.2.7"
 description = "Distributed Task Queue."
-optional = false
+category = "main"
+optional = true
 python-versions = ">=3.7"
 files = [
-    {file = "celery-5.3.0-py3-none-any.whl", hash = "sha256:95d29f9a93f41c4b122fddf1fe3ef13f872029dca4ad1f9af4f1a414442ceecf"},
-    {file = "celery-5.3.0.tar.gz", hash = "sha256:1eaba5ee14d8c8c0bed8f6063e5e10dabdbcf23503a861cf0e10b7221d99cb0d"},
+    {file = "celery-5.2.7-py3-none-any.whl", hash = "sha256:138420c020cd58d6707e6257b6beda91fd39af7afde5d36c6334d175302c0e14"},
+    {file = "celery-5.2.7.tar.gz", hash = "sha256:fafbd82934d30f8a004f81e8f7a062e31413a23d444be8ee3326553915958c6d"},
 ]
 
 [package.dependencies]
-billiard = ">=4.1.0,<5.0"
-click = ">=8.1.2,<9.0"
-click-didyoumean = ">=0.3.0"
+billiard = ">=3.6.4.0,<4.0"
+click = ">=8.0.3,<9.0"
+click-didyoumean = ">=0.0.3"
 click-plugins = ">=1.1.1"
 click-repl = ">=0.2.0"
-kombu = ">=5.3.0,<6.0"
-python-dateutil = ">=2.8.2"
-tzdata = ">=2022.7"
+kombu = ">=5.2.3,<6.0"
+pytz = ">=2021.3"
 vine = ">=5.0.0,<6.0"
 
 [package.extras]
-arangodb = ["pyArango (>=2.0.1)"]
-auth = ["cryptography (==41.0.1)"]
-azureblockblob = ["azure-storage-blob (>=12.15.0)"]
+arangodb = ["pyArango (>=1.3.2)"]
+auth = ["cryptography"]
+azureblockblob = ["azure-storage-blob (==12.9.0)"]
 brotli = ["brotli (>=1.0.0)", "brotlipy (>=0.7.0)"]
-cassandra = ["cassandra-driver (>=3.25.0,<4)"]
-consul = ["python-consul2 (==0.1.5)"]
-cosmosdbsql = ["pydocumentdb (==2.3.5)"]
+cassandra = ["cassandra-driver (<3.21.0)"]
+consul = ["python-consul2"]
+cosmosdbsql = ["pydocumentdb (==2.3.2)"]
 couchbase = ["couchbase (>=3.0.0)"]
-couchdb = ["pycouchdb (==1.14.2)"]
-django = ["Django (>=2.2.28)"]
-dynamodb = ["boto3 (>=1.26.143)"]
-elasticsearch = ["elasticsearch (<8.0)"]
+couchdb = ["pycouchdb"]
+django = ["Django (>=1.11)"]
+dynamodb = ["boto3 (>=1.9.178)"]
+elasticsearch = ["elasticsearch"]
 eventlet = ["eventlet (>=0.32.0)"]
 gevent = ["gevent (>=1.5.0)"]
-librabbitmq = ["librabbitmq (>=2.0.0)"]
-memcache = ["pylibmc (==1.6.3)"]
-mongodb = ["pymongo[srv] (>=4.0.2)"]
-msgpack = ["msgpack (==1.0.5)"]
-pymemcache = ["python-memcached (==1.59)"]
-pyro = ["pyro4 (==4.82)"]
-pytest = ["pytest-celery (==0.0.0)"]
-redis = ["redis (>=4.5.2)"]
-s3 = ["boto3 (>=1.26.143)"]
+librabbitmq = ["librabbitmq (>=1.5.0)"]
+memcache = ["pylibmc"]
+mongodb = ["pymongo[srv] (>=3.11.1)"]
+msgpack = ["msgpack"]
+pymemcache = ["python-memcached"]
+pyro = ["pyro4"]
+pytest = ["pytest-celery"]
+redis = ["redis (>=3.4.1,!=4.0.0,!=4.0.1)"]
+s3 = ["boto3 (>=1.9.125)"]
 slmq = ["softlayer-messaging (>=1.0.3)"]
-solar = ["ephem (==4.1.4)"]
-sqlalchemy = ["sqlalchemy (>=1.4.48,<2.1)"]
-sqs = ["kombu[sqs] (>=5.3.0)"]
+solar = ["ephem"]
+sqlalchemy = ["sqlalchemy"]
+sqs = ["kombu[sqs]"]
 tblib = ["tblib (>=1.3.0)", "tblib (>=1.5.0)"]
 yaml = ["PyYAML (>=3.10)"]
 zookeeper = ["kazoo (>=1.3.1)"]
-zstd = ["zstandard (==0.21.0)"]
+zstd = ["zstandard"]
 
 [[package]]
 name = "certifi"
-version = "2023.5.7"
+version = "2022.12.7"
 description = "Python package for providing Mozilla's CA Bundle."
+category = "main"
 optional = false
 python-versions = ">=3.6"
 files = [
-    {file = "certifi-2023.5.7-py3-none-any.whl", hash = "sha256:c6c2e98f5c7869efca1f8916fed228dd91539f9f1b444c314c06eef02980c716"},
-    {file = "certifi-2023.5.7.tar.gz", hash = "sha256:0f0d56dc5a6ad56fd4ba36484d6cc34451e1c6548c61daad8c320169f91eddc7"},
+    {file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"},
+    {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"},
 ]
 
 [[package]]
 name = "cffi"
 version = "1.15.1"
 description = "Foreign Function Interface for Python calling C code."
+category = "main"
 optional = false
 python-versions = "*"
 files = [
@@ -607,6 +629,7 @@ pycparser = "*"
 name = "chardet"
 version = "4.0.0"
 description = "Universal encoding detector for Python 2 and 3"
+category = "main"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
 files = [
@@ -618,6 +641,7 @@ files = [
 name = "charset-normalizer"
 version = "3.1.0"
 description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
+category = "main"
 optional = false
 python-versions = ">=3.7.0"
 files = [
@@ -702,6 +726,7 @@ files = [
 name = "click"
 version = "8.1.3"
 description = "Composable command line interface toolkit"
+category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -716,7 +741,8 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""}
 name = "click-didyoumean"
 version = "0.3.0"
 description = "Enables git-like *did-you-mean* feature in click"
-optional = false
+category = "main"
+optional = true
 python-versions = ">=3.6.2,<4.0.0"
 files = [
     {file = "click-didyoumean-0.3.0.tar.gz", hash = "sha256:f184f0d851d96b6d29297354ed981b7dd71df7ff500d82fa6d11f0856bee8035"},
@@ -730,7 +756,8 @@ click = ">=7"
 name = "click-plugins"
 version = "1.1.1"
 description = "An extension module for click to enable registering CLI commands via setuptools entry-points."
-optional = false
+category = "main"
+optional = true
 python-versions = "*"
 files = [
     {file = "click-plugins-1.1.1.tar.gz", hash = "sha256:46ab999744a9d831159c3411bb0c79346d94a444df9a3a3742e9ed63645f264b"},
@@ -747,7 +774,8 @@ dev = ["coveralls", "pytest (>=3.6)", "pytest-cov", "wheel"]
 name = "click-repl"
 version = "0.2.0"
 description = "REPL plugin for Click"
-optional = false
+category = "main"
+optional = true
 python-versions = "*"
 files = [
     {file = "click-repl-0.2.0.tar.gz", hash = "sha256:cd12f68d745bf6151210790540b4cb064c7b13e571bc64b6957d98d120dacfd8"},
@@ -763,6 +791,7 @@ six = "*"
 name = "cmd2"
 version = "2.4.3"
 description = "cmd2 - quickly build feature-rich and user-friendly interactive command line applications in Python"
+category = "main"
 optional = false
 python-versions = ">=3.6"
 files = [
@@ -785,6 +814,7 @@ validate = ["flake8", "mypy", "types-pkg-resources"]
 name = "colorama"
 version = "0.4.6"
 description = "Cross-platform colored terminal text."
+category = "main"
 optional = false
 python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
 files = [
@@ -796,7 +826,8 @@ files = [
 name = "colorlog"
 version = "6.7.0"
 description = "Add colours to the output of Python's logging module."
-optional = false
+category = "main"
+optional = true
 python-versions = ">=3.6"
 files = [
     {file = "colorlog-6.7.0-py2.py3-none-any.whl", hash = "sha256:0d33ca236784a1ba3ff9c532d4964126d8a2c44f1f0cb1d2b0728196f512f662"},
@@ -813,6 +844,7 @@ development = ["black", "flake8", "mypy", "pytest", "types-colorama"]
 name = "coverage"
 version = "6.5.0"
 description = "Code coverage measurement for Python"
+category = "dev"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -875,6 +907,7 @@ toml = ["tomli"]
 name = "cryptography"
 version = "3.3.2"
 description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
+category = "main"
 optional = false
 python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*"
 files = [
@@ -909,6 +942,7 @@ test = ["hypothesis (>=1.11.4,!=3.79.2)", "iso8601", "pretend", "pytest (>=3.6.0
 name = "docker"
 version = "5.0.3"
 description = "A Python library for the Docker Engine API."
+category = "main"
 optional = false
 python-versions = ">=3.6"
 files = [
@@ -929,6 +963,7 @@ tls = ["cryptography (>=3.4.7)", "idna (>=2.0.0)", "pyOpenSSL (>=17.5.0)"]
 name = "ecdsa"
 version = "0.18.0"
 description = "ECDSA cryptographic signature library (pure python)"
+category = "main"
 optional = false
 python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
 files = [
@@ -943,25 +978,39 @@ six = ">=1.9.0"
 gmpy = ["gmpy"]
 gmpy2 = ["gmpy2"]
 
+[[package]]
+name = "entrypoints"
+version = "0.4"
+description = "Discover and load entry points from installed packages."
+category = "main"
+optional = false
+python-versions = ">=3.6"
+files = [
+    {file = "entrypoints-0.4-py3-none-any.whl", hash = "sha256:f174b5ff827504fd3cd97cc3f8649f3693f51538c7e4bdf3ef002c8429d42f9f"},
+    {file = "entrypoints-0.4.tar.gz", hash = "sha256:b706eddaa9218a19ebcd67b56818f05bb27589b1ca9e8d797b74affad4ccacd4"},
+]
+
 [[package]]
 name = "filelock"
-version = "3.12.2"
+version = "3.11.0"
 description = "A platform independent file lock."
+category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
-    {file = "filelock-3.12.2-py3-none-any.whl", hash = "sha256:cbb791cdea2a72f23da6ac5b5269ab0a0d161e9ef0100e653b69049a7706d1ec"},
-    {file = "filelock-3.12.2.tar.gz", hash = "sha256:002740518d8aa59a26b0c76e10fb8c6e15eae825d34b6fdf670333fd7b938d81"},
+    {file = "filelock-3.11.0-py3-none-any.whl", hash = "sha256:f08a52314748335c6460fc8fe40cd5638b85001225db78c2aa01c8c0db83b318"},
+    {file = "filelock-3.11.0.tar.gz", hash = "sha256:3618c0da67adcc0506b015fd11ef7faf1b493f0b40d87728e19986b536890c37"},
 ]
 
 [package.extras]
-docs = ["furo (>=2023.5.20)", "sphinx (>=7.0.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"]
-testing = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "diff-cover (>=7.5)", "pytest (>=7.3.1)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)", "pytest-timeout (>=2.1)"]
+docs = ["furo (>=2023.3.27)", "sphinx (>=6.1.3)", "sphinx-autodoc-typehints (>=1.22,!=1.23.4)"]
+testing = ["covdefaults (>=2.3)", "coverage (>=7.2.2)", "diff-cover (>=7.5)", "pytest (>=7.2.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)", "pytest-timeout (>=2.1)"]
 
 [[package]]
 name = "frozenlist"
 version = "1.3.3"
 description = "A list-like structure which implements collections.abc.MutableSequence"
+category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -1045,7 +1094,8 @@ files = [
 name = "future"
 version = "0.18.3"
 description = "Clean single-source support for Python 3 and 2"
-optional = false
+category = "main"
+optional = true
 python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
 files = [
     {file = "future-0.18.3.tar.gz", hash = "sha256:34a17436ed1e96697a86f9de3d15a3b0be01d8bc8de9c1dffd59fb8234ed5307"},
@@ -1055,7 +1105,8 @@ files = [
 name = "gax-google-logging-v2"
 version = "0.8.3"
 description = "GAX library for the Google Logging API"
-optional = false
+category = "main"
+optional = true
 python-versions = "*"
 files = [
     {file = "gax-google-logging-v2-0.8.3.tar.gz", hash = "sha256:c36cbb93e070b3b535e9fffff6879efe447e525dff75b62eb4564def515e1a18"},
@@ -1071,7 +1122,8 @@ oauth2client = ">=1.4.11"
 name = "gax-google-pubsub-v1"
 version = "0.8.3"
 description = "DEPRECATED"
-optional = false
+category = "main"
+optional = true
 python-versions = "*"
 files = [
     {file = "gax-google-pubsub-v1-0.8.3.tar.gz", hash = "sha256:943df4aa05cf0302fa1616197d05e29adb62be2c0f55f80d8345439d72471526"},
@@ -1087,7 +1139,8 @@ oauth2client = ">=1.4.11"
 name = "gcloud"
 version = "0.18.3"
 description = "API Client library for Google Cloud"
-optional = false
+category = "main"
+optional = true
 python-versions = "*"
 files = [
     {file = "gcloud-0.18.3.tar.gz", hash = "sha256:0af2dec59fce20561752f86e42d981c6a255e306a6c5e5d1fa3d358a8857e4fb"},
@@ -1113,6 +1166,7 @@ grpc = ["gax-google-logging-v2 (>=0.8.0,<0.9dev)", "gax-google-pubsub-v1 (>=0.8.
 name = "ghp-import"
 version = "2.1.0"
 description = "Copy your docs directly to the gh-pages branch."
+category = "dev"
 optional = false
 python-versions = "*"
 files = [
@@ -1130,6 +1184,7 @@ dev = ["flake8", "markdown", "twine", "wheel"]
 name = "google-api-core"
 version = "1.32.0"
 description = "Google API client core library"
+category = "main"
 optional = false
 python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*"
 files = [
@@ -1155,26 +1210,28 @@ grpcio-gcp = ["grpcio-gcp (>=0.2.2)"]
 
 [[package]]
 name = "google-api-python-client"
-version = "2.89.0"
+version = "2.85.0"
 description = "Google API Client Library for Python"
+category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
-    {file = "google-api-python-client-2.89.0.tar.gz", hash = "sha256:0b0c9503df2da92692ffceee88423ca593cbf0b939d879e2c46fbdc1a39cf091"},
-    {file = "google_api_python_client-2.89.0-py2.py3-none-any.whl", hash = "sha256:272ff339928ac35b1e117d30e5db444fb701803bb748bb29e7bb520be29dea36"},
+    {file = "google-api-python-client-2.85.0.tar.gz", hash = "sha256:07b21ef21a542dd69cd7c09817a6079b2769cc2a791981402e8f0fcdb2d47f90"},
+    {file = "google_api_python_client-2.85.0-py2.py3-none-any.whl", hash = "sha256:baf3c6f9b1679d89fcb88c29941a8b04b9a815d721880786baecc6a7f5bd376f"},
 ]
 
 [package.dependencies]
-google-api-core = ">=1.31.5,<2.0.dev0 || >2.3.0,<3.0.0.dev0"
-google-auth = ">=1.19.0,<3.0.0.dev0"
+google-api-core = ">=1.31.5,<2.0.0 || >2.3.0,<3.0.0dev"
+google-auth = ">=1.19.0,<3.0.0dev"
 google-auth-httplib2 = ">=0.1.0"
-httplib2 = ">=0.15.0,<1.dev0"
+httplib2 = ">=0.15.0,<1dev"
 uritemplate = ">=3.0.1,<5"
 
 [[package]]
 name = "google-auth"
 version = "1.35.0"
 description = "Google Authentication Library"
+category = "main"
 optional = false
 python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*"
 files = [
@@ -1198,6 +1255,7 @@ reauth = ["pyu2f (>=0.1.5)"]
 name = "google-auth-httplib2"
 version = "0.1.0"
 description = "Google Authentication Library: httplib2 transport"
+category = "main"
 optional = false
 python-versions = "*"
 files = [
@@ -1214,6 +1272,7 @@ six = "*"
 name = "google-auth-oauthlib"
 version = "0.5.3"
 description = "Google Authentication Library"
+category = "main"
 optional = false
 python-versions = ">=3.6"
 files = [
@@ -1232,6 +1291,7 @@ tool = ["click (>=6.0.0)"]
 name = "google-cloud-bigquery"
 version = "3.1.0"
 description = "Google BigQuery API client library"
+category = "main"
 optional = false
 python-versions = ">=3.6, <3.11"
 files = [
@@ -1240,7 +1300,7 @@ files = [
 ]
 
 [package.dependencies]
-google-api-core = {version = ">=1.31.5,<2.0.dev0 || >2.3.0,<3.0.0dev", extras = ["grpc"]}
+google-api-core = {version = ">=1.31.5,<2.0.0 || >2.3.0,<3.0.0dev", extras = ["grpc"]}
 google-cloud-bigquery-storage = ">=2.0.0,<3.0.0dev"
 google-cloud-core = ">=1.4.1,<3.0.0dev"
 google-resumable-media = ">=0.6.0,<3.0dev"
@@ -1264,6 +1324,7 @@ tqdm = ["tqdm (>=4.7.4,<5.0.0dev)"]
 name = "google-cloud-bigquery-storage"
 version = "2.13.1"
 description = "BigQuery Storage API API client library"
+category = "main"
 optional = false
 python-versions = ">=3.6"
 files = [
@@ -1272,7 +1333,7 @@ files = [
 ]
 
 [package.dependencies]
-google-api-core = {version = ">=1.31.5,<2.0.dev0 || >2.3.0,<3.0.0dev", extras = ["grpc"]}
+google-api-core = {version = ">=1.31.5,<2.0.0 || >2.3.0,<3.0.0dev", extras = ["grpc"]}
 proto-plus = ">=1.18.0"
 
 [package.extras]
@@ -1285,6 +1346,7 @@ tests = ["freezegun"]
 name = "google-cloud-core"
 version = "1.7.3"
 description = "Google Cloud API client core library"
+category = "main"
 optional = false
 python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*"
 files = [
@@ -1304,7 +1366,8 @@ grpc = ["grpcio (>=1.8.2,<2.0dev)"]
 name = "google-cloud-datastore"
 version = "2.0.0"
 description = "Google Cloud Datastore API client library"
-optional = false
+category = "main"
+optional = true
 python-versions = ">=3.6"
 files = [
     {file = "google-cloud-datastore-2.0.0.tar.gz", hash = "sha256:dd83b82650b179cbbf010dd7bbb2fac871e1f5baea019f1bd080669b97c3bd8f"},
@@ -1321,6 +1384,7 @@ proto-plus = ">=1.4.0"
 name = "google-cloud-error-reporting"
 version = "1.5.2"
 description = "Error Reporting API client library"
+category = "main"
 optional = false
 python-versions = ">=3.6"
 files = [
@@ -1329,7 +1393,7 @@ files = [
 ]
 
 [package.dependencies]
-google-api-core = {version = ">=1.31.5,<2.0.dev0 || >2.3.0,<3.0.0dev", extras = ["grpc"]}
+google-api-core = {version = ">=1.31.5,<2.0.0 || >2.3.0,<3.0.0dev", extras = ["grpc"]}
 google-cloud-logging = ">=1.14.0,<4.0.0dev"
 proto-plus = ">=1.15.0"
 
@@ -1337,6 +1401,7 @@ proto-plus = ">=1.15.0"
 name = "google-cloud-logging"
 version = "2.3.1"
 description = "Stackdriver Logging API client library"
+category = "main"
 optional = false
 python-versions = ">=3.6"
 files = [
@@ -1353,7 +1418,8 @@ proto-plus = ">=1.11.0"
 name = "google-cloud-pubsub"
 version = "1.7.0"
 description = "Google Cloud Pub/Sub API client library"
-optional = false
+category = "main"
+optional = true
 python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*"
 files = [
     {file = "google-cloud-pubsub-1.7.0.tar.gz", hash = "sha256:c8d098ebd208d00c8f3bb55eefecd8553e7391d59700426a97d35125f0dcb248"},
@@ -1361,13 +1427,14 @@ files = [
 ]
 
 [package.dependencies]
-google-api-core = {version = ">=1.14.0,<1.17.dev0 || >=1.20.dev0", extras = ["grpc"]}
+google-api-core = {version = ">=1.14.0,<1.17.0 || >=1.20.0", extras = ["grpc"]}
 grpc-google-iam-v1 = ">=0.12.3,<0.13dev"
 
 [[package]]
 name = "google-cloud-spanner"
 version = "3.11.1"
 description = "Cloud Spanner API client library"
+category = "dev"
 optional = false
 python-versions = ">=3.6"
 files = [
@@ -1391,6 +1458,7 @@ tracing = ["opentelemetry-api (>=1.1.0)", "opentelemetry-instrumentation (>=0.20
 name = "google-cloud-storage"
 version = "2.2.1"
 description = "Google Cloud Storage API client library"
+category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -1399,7 +1467,7 @@ files = [
 ]
 
 [package.dependencies]
-google-api-core = ">=1.31.5,<2.0.dev0 || >2.3.0,<3.0.0dev"
+google-api-core = ">=1.31.5,<2.0.0 || >2.3.0,<3.0.0dev"
 google-auth = ">=1.25.0,<3.0dev"
 google-cloud-core = ">=1.6.0,<3.0dev"
 google-resumable-media = ">=2.3.2"
@@ -1410,6 +1478,7 @@ requests = ">=2.18.0,<3.0.0dev"
 name = "google-crc32c"
 version = "1.5.0"
 description = "A python wrapper of the C library 'Google CRC32C'"
+category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -1490,7 +1559,8 @@ testing = ["pytest"]
 name = "google-gax"
 version = "0.12.5"
 description = "Google API Extensions"
-optional = false
+category = "main"
+optional = true
 python-versions = "*"
 files = [
     {file = "google-gax-0.12.5.tar.gz", hash = "sha256:63312a04cb87ca50e857245f05c582b2171807a30e61cef85006b983bf659cb9"},
@@ -1505,13 +1575,14 @@ protobuf = ">=3.0.0b3"
 
 [[package]]
 name = "google-resumable-media"
-version = "2.5.0"
+version = "2.4.1"
 description = "Utilities for Google Media Downloads and Resumable Uploads"
+category = "main"
 optional = false
 python-versions = ">= 3.7"
 files = [
-    {file = "google-resumable-media-2.5.0.tar.gz", hash = "sha256:218931e8e2b2a73a58eb354a288e03a0fd5fb1c4583261ac6e4c078666468c93"},
-    {file = "google_resumable_media-2.5.0-py2.py3-none-any.whl", hash = "sha256:da1bd943e2e114a56d85d6848497ebf9be6a14d3db23e9fc57581e7c3e8170ec"},
+    {file = "google-resumable-media-2.4.1.tar.gz", hash = "sha256:15b8a2e75df42dc6502d1306db0bce2647ba6013f9cd03b6e17368c0886ee90a"},
+    {file = "google_resumable_media-2.4.1-py2.py3-none-any.whl", hash = "sha256:831e86fd78d302c1a034730a0c6e5369dd11d37bad73fa69ca8998460d5bae8d"},
 ]
 
 [package.dependencies]
@@ -1525,6 +1596,7 @@ requests = ["requests (>=2.18.0,<3.0.0dev)"]
 name = "googleapis-common-protos"
 version = "1.56.0"
 description = "Common protobufs used in Google APIs"
+category = "main"
 optional = false
 python-versions = ">=3.6"
 files = [
@@ -1543,6 +1615,7 @@ grpc = ["grpcio (>=1.0.0)"]
 name = "graphviz"
 version = "0.20.1"
 description = "Simple Python interface for Graphviz"
+category = "dev"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -1559,6 +1632,7 @@ test = ["coverage", "mock (>=4)", "pytest (>=7)", "pytest-cov", "pytest-mock (>=
 name = "grpc-google-iam-v1"
 version = "0.12.4"
 description = "IAM API client library"
+category = "main"
 optional = false
 python-versions = ">=3.6"
 files = [
@@ -1574,7 +1648,8 @@ grpcio = ">=1.0.0,<2.0.0dev"
 name = "grpc-google-logging-v2"
 version = "0.8.1"
 description = "GRPC library for the google-logging-v2 service"
-optional = false
+category = "main"
+optional = true
 python-versions = "*"
 files = [
     {file = "grpc-google-logging-v2-0.8.1.tar.gz", hash = "sha256:4b6b4e603860b134b2cb8732bb4d1f2cec963d553497dcf70b3f5ecc99d7fb4f"},
@@ -1589,7 +1664,8 @@ oauth2client = ">=1.4.11"
 name = "grpc-google-pubsub-v1"
 version = "0.8.1"
 description = "GRPC library for the google-pubsub-v1 service"
-optional = false
+category = "main"
+optional = true
 python-versions = "*"
 files = [
     {file = "grpc-google-pubsub-v1-0.8.1.tar.gz", hash = "sha256:ab5a3a239a9678012cdc00a9b9a8fe8c75fca9a0035da41da3078145e9d967b9"},
@@ -1602,65 +1678,67 @@ oauth2client = ">=1.4.11"
 
 [[package]]
 name = "grpcio"
-version = "1.54.2"
+version = "1.53.0"
 description = "HTTP/2-based RPC framework"
+category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
-    {file = "grpcio-1.54.2-cp310-cp310-linux_armv7l.whl", hash = "sha256:40e1cbf69d6741b40f750f3cccc64326f927ac6145a9914d33879e586002350c"},
-    {file = "grpcio-1.54.2-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:2288d76e4d4aa7ef3fe7a73c1c470b66ea68e7969930e746a8cd8eca6ef2a2ea"},
-    {file = "grpcio-1.54.2-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:c0e3155fc5335ec7b3b70f15230234e529ca3607b20a562b6c75fb1b1218874c"},
-    {file = "grpcio-1.54.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bf88004fe086c786dc56ef8dd6cb49c026833fdd6f42cb853008bce3f907148"},
-    {file = "grpcio-1.54.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2be88c081e33f20630ac3343d8ad9f1125f32987968e9c8c75c051c9800896e8"},
-    {file = "grpcio-1.54.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:33d40954199bddbb6a78f8f6f2b2082660f381cd2583ec860a6c2fa7c8400c08"},
-    {file = "grpcio-1.54.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b52d00d1793d290c81ad6a27058f5224a7d5f527867e5b580742e1bd211afeee"},
-    {file = "grpcio-1.54.2-cp310-cp310-win32.whl", hash = "sha256:881d058c5ccbea7cc2c92085a11947b572498a27ef37d3eef4887f499054dca8"},
-    {file = "grpcio-1.54.2-cp310-cp310-win_amd64.whl", hash = "sha256:0212e2f7fdf7592e4b9d365087da30cb4d71e16a6f213120c89b4f8fb35a3ab3"},
-    {file = "grpcio-1.54.2-cp311-cp311-linux_armv7l.whl", hash = "sha256:1e623e0cf99a0ac114f091b3083a1848dbc64b0b99e181473b5a4a68d4f6f821"},
-    {file = "grpcio-1.54.2-cp311-cp311-macosx_10_10_universal2.whl", hash = "sha256:66233ccd2a9371158d96e05d082043d47dadb18cbb294dc5accfdafc2e6b02a7"},
-    {file = "grpcio-1.54.2-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:4cb283f630624ebb16c834e5ac3d7880831b07cbe76cb08ab7a271eeaeb8943e"},
-    {file = "grpcio-1.54.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a1e601ee31ef30a9e2c601d0867e236ac54c922d32ed9f727b70dd5d82600d5"},
-    {file = "grpcio-1.54.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8da84bbc61a4e92af54dc96344f328e5822d574f767e9b08e1602bb5ddc254a"},
-    {file = "grpcio-1.54.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:5008964885e8d23313c8e5ea0d44433be9bfd7e24482574e8cc43c02c02fc796"},
-    {file = "grpcio-1.54.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a2f5a1f1080ccdc7cbaf1171b2cf384d852496fe81ddedeb882d42b85727f610"},
-    {file = "grpcio-1.54.2-cp311-cp311-win32.whl", hash = "sha256:b74ae837368cfffeb3f6b498688a123e6b960951be4dec0e869de77e7fa0439e"},
-    {file = "grpcio-1.54.2-cp311-cp311-win_amd64.whl", hash = "sha256:8cdbcbd687e576d48f7886157c95052825ca9948c0ed2afdc0134305067be88b"},
-    {file = "grpcio-1.54.2-cp37-cp37m-linux_armv7l.whl", hash = "sha256:782f4f8662a2157c4190d0f99eaaebc602899e84fb1e562a944e5025929e351c"},
-    {file = "grpcio-1.54.2-cp37-cp37m-macosx_10_10_universal2.whl", hash = "sha256:714242ad0afa63a2e6dabd522ae22e1d76e07060b5af2ddda5474ba4f14c2c94"},
-    {file = "grpcio-1.54.2-cp37-cp37m-manylinux_2_17_aarch64.whl", hash = "sha256:f900ed4ad7a0f1f05d35f955e0943944d5a75f607a836958c6b8ab2a81730ef2"},
-    {file = "grpcio-1.54.2-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96a41817d2c763b1d0b32675abeb9179aa2371c72aefdf74b2d2b99a1b92417b"},
-    {file = "grpcio-1.54.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70fcac7b94f4c904152809a050164650ac81c08e62c27aa9f156ac518029ebbe"},
-    {file = "grpcio-1.54.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:fd6c6c29717724acf9fc1847c4515d57e4dc12762452457b9cb37461f30a81bb"},
-    {file = "grpcio-1.54.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c2392f5b5d84b71d853918687d806c1aa4308109e5ca158a16e16a6be71041eb"},
-    {file = "grpcio-1.54.2-cp37-cp37m-win_amd64.whl", hash = "sha256:51630c92591d6d3fe488a7c706bd30a61594d144bac7dee20c8e1ce78294f474"},
-    {file = "grpcio-1.54.2-cp38-cp38-linux_armv7l.whl", hash = "sha256:b04202453941a63b36876a7172b45366dc0cde10d5fd7855c0f4a4e673c0357a"},
-    {file = "grpcio-1.54.2-cp38-cp38-macosx_10_10_universal2.whl", hash = "sha256:89dde0ac72a858a44a2feb8e43dc68c0c66f7857a23f806e81e1b7cc7044c9cf"},
-    {file = "grpcio-1.54.2-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:09d4bfd84686cd36fd11fd45a0732c7628308d094b14d28ea74a81db0bce2ed3"},
-    {file = "grpcio-1.54.2-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7fc2b4edb938c8faa4b3c3ea90ca0dd89b7565a049e8e4e11b77e60e4ed2cc05"},
-    {file = "grpcio-1.54.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61f7203e2767800edee7a1e1040aaaf124a35ce0c7fe0883965c6b762defe598"},
-    {file = "grpcio-1.54.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:e416c8baf925b5a1aff31f7f5aecc0060b25d50cce3a5a7255dc5cf2f1d4e5eb"},
-    {file = "grpcio-1.54.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:dc80c9c6b608bf98066a038e0172013a49cfa9a08d53335aefefda2c64fc68f4"},
-    {file = "grpcio-1.54.2-cp38-cp38-win32.whl", hash = "sha256:8d6192c37a30a115f4663592861f50e130caed33efc4eec24d92ec881c92d771"},
-    {file = "grpcio-1.54.2-cp38-cp38-win_amd64.whl", hash = "sha256:46a057329938b08e5f0e12ea3d7aed3ecb20a0c34c4a324ef34e00cecdb88a12"},
-    {file = "grpcio-1.54.2-cp39-cp39-linux_armv7l.whl", hash = "sha256:2296356b5c9605b73ed6a52660b538787094dae13786ba53080595d52df13a98"},
-    {file = "grpcio-1.54.2-cp39-cp39-macosx_10_10_universal2.whl", hash = "sha256:c72956972e4b508dd39fdc7646637a791a9665b478e768ffa5f4fe42123d5de1"},
-    {file = "grpcio-1.54.2-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:9bdbb7624d65dc0ed2ed8e954e79ab1724526f09b1efa88dcd9a1815bf28be5f"},
-    {file = "grpcio-1.54.2-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c44e1a765b31e175c391f22e8fc73b2a2ece0e5e6ff042743d8109b5d2eff9f"},
-    {file = "grpcio-1.54.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5cc928cfe6c360c1df636cf7991ab96f059666ac7b40b75a769410cc6217df9c"},
-    {file = "grpcio-1.54.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a08920fa1a97d4b8ee5db2f31195de4a9def1a91bc003544eb3c9e6b8977960a"},
-    {file = "grpcio-1.54.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4864f99aac207e3e45c5e26c6cbb0ad82917869abc2f156283be86c05286485c"},
-    {file = "grpcio-1.54.2-cp39-cp39-win32.whl", hash = "sha256:b38b3de8cff5bc70f8f9c615f51b48eff7313fc9aca354f09f81b73036e7ddfa"},
-    {file = "grpcio-1.54.2-cp39-cp39-win_amd64.whl", hash = "sha256:be48496b0e00460717225e7680de57c38be1d8629dc09dadcd1b3389d70d942b"},
-    {file = "grpcio-1.54.2.tar.gz", hash = "sha256:50a9f075eeda5097aa9a182bb3877fe1272875e45370368ac0ee16ab9e22d019"},
-]
-
-[package.extras]
-protobuf = ["grpcio-tools (>=1.54.2)"]
+    {file = "grpcio-1.53.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:752d2949b40e12e6ad3ed8cc552a65b54d226504f6b1fb67cab2ccee502cc06f"},
+    {file = "grpcio-1.53.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:8a48fd3a7222be226bb86b7b413ad248f17f3101a524018cdc4562eeae1eb2a3"},
+    {file = "grpcio-1.53.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:f3e837d29f0e1b9d6e7b29d569e2e9b0da61889e41879832ea15569c251c303a"},
+    {file = "grpcio-1.53.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aef7d30242409c3aa5839b501e877e453a2c8d3759ca8230dd5a21cda029f046"},
+    {file = "grpcio-1.53.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e6f90698b5d1c5dd7b3236cd1fa959d7b80e17923f918d5be020b65f1c78b173"},
+    {file = "grpcio-1.53.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a96c3c7f564b263c5d7c0e49a337166c8611e89c4c919f66dba7b9a84abad137"},
+    {file = "grpcio-1.53.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ee81349411648d1abc94095c68cd25e3c2812e4e0367f9a9355be1e804a5135c"},
+    {file = "grpcio-1.53.0-cp310-cp310-win32.whl", hash = "sha256:fdc6191587de410a184550d4143e2b24a14df495c86ca15e59508710681690ac"},
+    {file = "grpcio-1.53.0-cp310-cp310-win_amd64.whl", hash = "sha256:658ffe1e39171be00490db5bd3b966f79634ac4215a1eb9a85c6cd6783bf7f6e"},
+    {file = "grpcio-1.53.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:1b172e6d497191940c4b8d75b53de82dc252e15b61de2951d577ec5b43316b29"},
+    {file = "grpcio-1.53.0-cp311-cp311-macosx_10_10_universal2.whl", hash = "sha256:82434ba3a5935e47908bc861ce1ebc43c2edfc1001d235d6e31e5d3ed55815f7"},
+    {file = "grpcio-1.53.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:1c734a2d4843e4e14ececf5600c3c4750990ec319e1299db7e4f0d02c25c1467"},
+    {file = "grpcio-1.53.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b6a2ead3de3b2d53119d473aa2f224030257ef33af1e4ddabd4afee1dea5f04c"},
+    {file = "grpcio-1.53.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a34d6e905f071f9b945cabbcc776e2055de1fdb59cd13683d9aa0a8f265b5bf9"},
+    {file = "grpcio-1.53.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eaf8e3b97caaf9415227a3c6ca5aa8d800fecadd526538d2bf8f11af783f1550"},
+    {file = "grpcio-1.53.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:da95778d37be8e4e9afca771a83424f892296f5dfb2a100eda2571a1d8bbc0dc"},
+    {file = "grpcio-1.53.0-cp311-cp311-win32.whl", hash = "sha256:e4f513d63df6336fd84b74b701f17d1bb3b64e9d78a6ed5b5e8a198bbbe8bbfa"},
+    {file = "grpcio-1.53.0-cp311-cp311-win_amd64.whl", hash = "sha256:ddb2511fbbb440ed9e5c9a4b9b870f2ed649b7715859fd6f2ebc585ee85c0364"},
+    {file = "grpcio-1.53.0-cp37-cp37m-linux_armv7l.whl", hash = "sha256:2a912397eb8d23c177d6d64e3c8bc46b8a1c7680b090d9f13a640b104aaec77c"},
+    {file = "grpcio-1.53.0-cp37-cp37m-macosx_10_10_universal2.whl", hash = "sha256:55930c56b8f5b347d6c8c609cc341949a97e176c90f5cbb01d148d778f3bbd23"},
+    {file = "grpcio-1.53.0-cp37-cp37m-manylinux_2_17_aarch64.whl", hash = "sha256:6601d812105583948ab9c6e403a7e2dba6e387cc678c010e74f2d6d589d1d1b3"},
+    {file = "grpcio-1.53.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c705e0c21acb0e8478a00e7e773ad0ecdb34bd0e4adc282d3d2f51ba3961aac7"},
+    {file = "grpcio-1.53.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba074af9ca268ad7b05d3fc2b920b5fb3c083da94ab63637aaf67f4f71ecb755"},
+    {file = "grpcio-1.53.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:14817de09317dd7d3fbc8272864288320739973ef0f4b56bf2c0032349da8cdf"},
+    {file = "grpcio-1.53.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c7ad9fbedb93f331c2e9054e202e95cf825b885811f1bcbbdfdc301e451442db"},
+    {file = "grpcio-1.53.0-cp37-cp37m-win_amd64.whl", hash = "sha256:dad5b302a4c21c604d88a5d441973f320134e6ff6a84ecef9c1139e5ffd466f6"},
+    {file = "grpcio-1.53.0-cp38-cp38-linux_armv7l.whl", hash = "sha256:fa8eaac75d3107e3f5465f2c9e3bbd13db21790c6e45b7de1756eba16b050aca"},
+    {file = "grpcio-1.53.0-cp38-cp38-macosx_10_10_universal2.whl", hash = "sha256:104a2210edd3776c38448b4f76c2f16e527adafbde171fc72a8a32976c20abc7"},
+    {file = "grpcio-1.53.0-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:dbc1ba968639c1d23476f75c356e549e7bbf2d8d6688717dcab5290e88e8482b"},
+    {file = "grpcio-1.53.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:95952d3fe795b06af29bb8ec7bbf3342cdd867fc17b77cc25e6733d23fa6c519"},
+    {file = "grpcio-1.53.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f144a790f14c51b8a8e591eb5af40507ffee45ea6b818c2482f0457fec2e1a2e"},
+    {file = "grpcio-1.53.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:0698c094688a2dd4c7c2f2c0e3e142cac439a64d1cef6904c97f6cde38ba422f"},
+    {file = "grpcio-1.53.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6b6d60b0958be711bab047e9f4df5dbbc40367955f8651232bfdcdd21450b9ab"},
+    {file = "grpcio-1.53.0-cp38-cp38-win32.whl", hash = "sha256:1948539ce78805d4e6256ab0e048ec793956d54787dc9d6777df71c1d19c7f81"},
+    {file = "grpcio-1.53.0-cp38-cp38-win_amd64.whl", hash = "sha256:df9ba1183b3f649210788cf80c239041dddcb375d6142d8bccafcfdf549522cd"},
+    {file = "grpcio-1.53.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:19caa5b7282a89b799e63776ff602bb39604f7ca98db6df27e2de06756ae86c3"},
+    {file = "grpcio-1.53.0-cp39-cp39-macosx_10_10_universal2.whl", hash = "sha256:b5bd026ac928c96cc23149e6ef79183125542062eb6d1ccec34c0a37e02255e7"},
+    {file = "grpcio-1.53.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:7dc8584ca6c015ad82e186e82f4c0fe977394588f66b8ecfc4ec873285314619"},
+    {file = "grpcio-1.53.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2eddaae8af625e45b5c8500dcca1043264d751a6872cde2eda5022df8a336959"},
+    {file = "grpcio-1.53.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c5fb6f3d7824696c1c9f2ad36ddb080ba5a86f2d929ef712d511b4d9972d3d27"},
+    {file = "grpcio-1.53.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:8270d1dc2c98ab57e6dbf36fa187db8df4c036f04a398e5d5e25b4e01a766d70"},
+    {file = "grpcio-1.53.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:976a7f24eb213e8429cab78d5e120500dfcdeb01041f1f5a77b17b9101902615"},
+    {file = "grpcio-1.53.0-cp39-cp39-win32.whl", hash = "sha256:9c84a481451e7174f3a764a44150f93b041ab51045aa33d7b5b68b6979114e48"},
+    {file = "grpcio-1.53.0-cp39-cp39-win_amd64.whl", hash = "sha256:6beb84f83360ff29a3654f43f251ec11b809dcb5524b698d711550243debd289"},
+    {file = "grpcio-1.53.0.tar.gz", hash = "sha256:a4952899b4931a6ba12951f9a141ef3e74ff8a6ec9aa2dc602afa40f63595e33"},
+]
+
+[package.extras]
+protobuf = ["grpcio-tools (>=1.53.0)"]
 
 [[package]]
 name = "grr-api-client"
 version = "3.4.6.post0"
 description = "GRR API client library"
+category = "main"
 optional = false
 python-versions = "*"
 files = [
@@ -1677,6 +1755,7 @@ Werkzeug = ">=0.16.0,<=1.0.1"
 name = "grr-response-proto"
 version = "3.4.6.post0"
 description = "GRR API client library"
+category = "main"
 optional = false
 python-versions = "*"
 files = [
@@ -1690,6 +1769,7 @@ protobuf = "3.12.2"
 name = "httplib2"
 version = "0.22.0"
 description = "A comprehensive HTTP client library."
+category = "main"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
 files = [
@@ -1704,6 +1784,7 @@ pyparsing = {version = ">=2.4.2,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.0.2 || >3.0
 name = "idna"
 version = "2.10"
 description = "Internationalized Domain Names in Applications (IDNA)"
+category = "main"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
 files = [
@@ -1715,6 +1796,7 @@ files = [
 name = "importlab"
 version = "0.8"
 description = "A library to calculate python dependency graphs."
+category = "dev"
 optional = false
 python-versions = ">=3.6.0"
 files = [
@@ -1725,10 +1807,31 @@ files = [
 [package.dependencies]
 networkx = ">=2"
 
+[[package]]
+name = "importlib-metadata"
+version = "6.3.0"
+description = "Read metadata from Python packages"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+files = [
+    {file = "importlib_metadata-6.3.0-py3-none-any.whl", hash = "sha256:8f8bd2af397cf33bd344d35cfe7f489219b7d14fc79a3f854b75b8417e9226b0"},
+    {file = "importlib_metadata-6.3.0.tar.gz", hash = "sha256:23c2bcae4762dfb0bbe072d358faec24957901d75b6c4ab11172c0c982532402"},
+]
+
+[package.dependencies]
+zipp = ">=0.5"
+
+[package.extras]
+docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
+perf = ["ipython"]
+testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"]
+
 [[package]]
 name = "isodate"
 version = "0.6.1"
 description = "An ISO 8601 date/time/duration parser and formatter"
+category = "main"
 optional = false
 python-versions = "*"
 files = [
@@ -1743,6 +1846,7 @@ six = "*"
 name = "isort"
 version = "5.12.0"
 description = "A Python utility / library to sort Python imports."
+category = "dev"
 optional = false
 python-versions = ">=3.8.0"
 files = [
@@ -1760,6 +1864,7 @@ requirements-deprecated-finder = ["pip-api", "pipreqs"]
 name = "jinja2"
 version = "3.1.2"
 description = "A very fast and expressive template engine."
+category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -1777,6 +1882,7 @@ i18n = ["Babel (>=2.7)"]
 name = "jmespath"
 version = "1.0.1"
 description = "JSON Matching Expressions"
+category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -1788,6 +1894,7 @@ files = [
 name = "jsonschema"
 version = "4.17.3"
 description = "An implementation of JSON Schema validation for Python"
+category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -1805,40 +1912,41 @@ format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-
 
 [[package]]
 name = "kombu"
-version = "5.3.0"
+version = "5.2.4"
 description = "Messaging library for Python."
-optional = false
-python-versions = ">=3.8"
+category = "main"
+optional = true
+python-versions = ">=3.7"
 files = [
-    {file = "kombu-5.3.0-py3-none-any.whl", hash = "sha256:fa9be55281bb351ba9da582b2a74e3dd5015b8d075b287e4d16f0b2f25fefcc2"},
-    {file = "kombu-5.3.0.tar.gz", hash = "sha256:d084ec1f96f7a7c37ba9e816823bdbc08f0fc7ddb3a5be555805e692102297d8"},
+    {file = "kombu-5.2.4-py3-none-any.whl", hash = "sha256:8b213b24293d3417bcf0d2f5537b7f756079e3ea232a8386dcc89a59fd2361a4"},
+    {file = "kombu-5.2.4.tar.gz", hash = "sha256:37cee3ee725f94ea8bb173eaab7c1760203ea53bbebae226328600f9d2799610"},
 ]
 
 [package.dependencies]
-amqp = ">=5.1.1,<6.0.0"
+amqp = ">=5.0.9,<6.0.0"
 vine = "*"
 
 [package.extras]
-azureservicebus = ["azure-servicebus (>=7.10.0)"]
-azurestoragequeues = ["azure-identity (>=1.12.0)", "azure-storage-queue (>=12.6.0)"]
-confluentkafka = ["confluent-kafka (==2.1.1)"]
-consul = ["python-consul2"]
+azureservicebus = ["azure-servicebus (>=7.0.0)"]
+azurestoragequeues = ["azure-storage-queue"]
+consul = ["python-consul (>=0.6.0)"]
 librabbitmq = ["librabbitmq (>=2.0.0)"]
-mongodb = ["pymongo (>=4.1.1)"]
+mongodb = ["pymongo (>=3.3.0,<3.12.1)"]
 msgpack = ["msgpack"]
 pyro = ["pyro4"]
 qpid = ["qpid-python (>=0.26)", "qpid-tools (>=0.26)"]
-redis = ["redis (>=4.5.2)"]
+redis = ["redis (>=3.4.1,!=4.0.0,!=4.0.1)"]
 slmq = ["softlayer-messaging (>=1.0.3)"]
-sqlalchemy = ["sqlalchemy (>=1.4.48,<2.1)"]
-sqs = ["boto3 (>=1.26.143)", "pycurl (==7.43.0.5)", "urllib3 (>=1.26.16)"]
+sqlalchemy = ["sqlalchemy"]
+sqs = ["boto3 (>=1.9.12)", "pycurl (>=7.44.1,<7.45.0)", "urllib3 (>=1.26.7)"]
 yaml = ["PyYAML (>=3.10)"]
-zookeeper = ["kazoo (>=2.8.0)"]
+zookeeper = ["kazoo (>=1.3.1)"]
 
 [[package]]
 name = "kubernetes"
 version = "26.1.0"
 description = "Kubernetes python client"
+category = "main"
 optional = false
 python-versions = ">=3.6"
 files = [
@@ -1856,7 +1964,7 @@ requests-oauthlib = "*"
 setuptools = ">=21.0.0"
 six = ">=1.9.0"
 urllib3 = ">=1.24.2"
-websocket-client = ">=0.32.0,<0.40.0 || >0.40.0,<0.41.dev0 || >=0.43.dev0"
+websocket-client = ">=0.32.0,<0.40.0 || >0.40.0,<0.41.0 || >=0.43.0"
 
 [package.extras]
 adal = ["adal (>=1.0.2)"]
@@ -1865,6 +1973,7 @@ adal = ["adal (>=1.0.2)"]
 name = "lazy-object-proxy"
 version = "1.9.0"
 description = "A fast and thorough lazy object proxy."
+category = "dev"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -1910,6 +2019,7 @@ files = [
 name = "libcloudforensics"
 version = "20220718"
 description = "libcloudforensics is a set of tools to help acquire forensic evidence from cloud platforms."
+category = "main"
 optional = false
 python-versions = "*"
 files = [
@@ -1944,41 +2054,42 @@ sshpubkeys = "*"
 
 [[package]]
 name = "libcst"
-version = "1.0.1"
+version = "0.4.9"
 description = "A concrete syntax tree with AST-like properties for Python 3.5, 3.6, 3.7, 3.8, 3.9, and 3.10 programs."
+category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
-    {file = "libcst-1.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:80423311f09fc5fc3270ede44d30d9d8d3c2d3dd50dbf703a581ca7346949fa6"},
-    {file = "libcst-1.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9d6dec2a3c443792e6af7c36fadc256e4ea586214c76b52f0d18118811dbe351"},
-    {file = "libcst-1.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4840a3de701778f0a19582bb3085c61591329153f801dc25da84689a3733960b"},
-    {file = "libcst-1.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0138068baf09561268c7f079373bda45f0e2b606d2d19df1307ca8a5134fc465"},
-    {file = "libcst-1.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9a4931feceab171e6fce73de94e13880424367247dad6ff2b49cabfec733e144"},
-    {file = "libcst-1.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:47dba43855e9c7b06d8b256ee81f0ebec6a4f43605456519577e09dfe4b4288c"},
-    {file = "libcst-1.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8c50541c3fd6b1d5a3765c4bb5ee8ecbba9d0e798e48f79fd5adf3b6752de4d0"},
-    {file = "libcst-1.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5599166d5fec40e18601fb8868519dde99f77b6e4ad6074958018f9545da7abd"},
-    {file = "libcst-1.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:600c4d3a9a2f75d5a055fed713a5a4d812709947909610aa6527abe08a31896f"},
-    {file = "libcst-1.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6b5aea04c35e13109edad3cf83bc6dcd74309b150a781d2189eecb288b73a87"},
-    {file = "libcst-1.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ddd4e0eeec499d1c824ab545e62e957dbbd69a16bc4273208817638eb7d6b3c6"},
-    {file = "libcst-1.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:414350df5e334ddf0db1732d63da44e81b734d45abe1c597b5e5c0dd46aa4156"},
-    {file = "libcst-1.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:1adcfa7cafb6a0d39a1a0bec541355608038b45815e0c5019c95f91921d42884"},
-    {file = "libcst-1.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d31ce2790eab59c1bd8e33fe72d09cfc78635c145bdc3f08296b360abb5f443"},
-    {file = "libcst-1.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2cb687e1514625e91024e50a5d2e485c0ad3be24f199874ebf32b5de0346150"},
-    {file = "libcst-1.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6caa33430c0c7a0fcad921b0deeec61ddb96796b6f88dca94966f6db62065f4f"},
-    {file = "libcst-1.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:b97f652b15c50e91df411a9c8d5e6f75882b30743a49b387dcedd3f68ed94d75"},
-    {file = "libcst-1.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:967c66fabd52102954207bf1541312b467afc210fdf7033f32da992fb6c2372c"},
-    {file = "libcst-1.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b666a605f4205c8357696f3b6571a38f6a8537cdcbb8f357587d35168298af34"},
-    {file = "libcst-1.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae49dcbfadefb82e830d41d9f0a1db0af3b771224768f431f1b7b3a9803ed7e3"},
-    {file = "libcst-1.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c90c74a8a314f0774f045122323fb60bacba79cbf5f71883c0848ecd67179541"},
-    {file = "libcst-1.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0533de4e35396c61aeb3a6266ac30369a855910c2385aaa902ff4aabd60d409"},
-    {file = "libcst-1.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:5e3293e77657ba62533553bb9f0c5fb173780e164c65db1ea2a3e0d03944a284"},
-    {file = "libcst-1.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:119ba709f1dcb785a4458cf36cedb51d6f9cb2eec0acd7bb171f730eac7cb6ce"},
-    {file = "libcst-1.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4b4e336f6d68456017671cdda8ddebf9caebce8052cc21a3f494b03d7bd28386"},
-    {file = "libcst-1.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8420926791b0b6206cb831a7ec73d26ae820e65bdf07ce9813c7754c7722c07a"},
-    {file = "libcst-1.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d237e9164a43caa7d6765ee560412264484e7620c546a2ee10a8d01bd56884e0"},
-    {file = "libcst-1.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:440887e5f82efb299f2e98d4bfa5663851a878cfc0efed652ab8c50205191436"},
-    {file = "libcst-1.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:ae7f4e71d714f256b5f2ff98b5a9effba0f9dff4d779d8f35d7eb157bef78f59"},
-    {file = "libcst-1.0.1.tar.gz", hash = "sha256:37187337f979ba426d8bfefc08008c3c1b09b9e9f9387050804ed2da88107570"},
+    {file = "libcst-0.4.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4f9e42085c403e22201e5c41e707ef73e4ea910ad9fc67983ceee2368097f54e"},
+    {file = "libcst-0.4.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1266530bf840cc40633a04feb578bb4cac1aa3aea058cc3729e24eab09a8e996"},
+    {file = "libcst-0.4.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9679177391ccb9b0cdde3185c22bf366cb672457c4b7f4031fcb3b5e739fbd6"},
+    {file = "libcst-0.4.9-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d67bc87e0d8db9434f2ea063734938a320f541f4c6da1074001e372f840f385d"},
+    {file = "libcst-0.4.9-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e316da5a126f2a9e1d7680f95f907b575f082a35e2f8bd5620c59b2aaaebfe0a"},
+    {file = "libcst-0.4.9-cp310-cp310-win_amd64.whl", hash = "sha256:7415569ab998a85b0fc9af3a204611ea7fadb2d719a12532c448f8fc98f5aca4"},
+    {file = "libcst-0.4.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:15ded11ff7f4572f91635e02b519ae959f782689fdb4445bbebb7a3cc5c71d75"},
+    {file = "libcst-0.4.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5b266867b712a120fad93983de432ddb2ccb062eb5fd2bea748c9a94cb200c36"},
+    {file = "libcst-0.4.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:045b3b0b06413cdae6e9751b5f417f789ffa410f2cb2815e3e0e0ea6bef10ec0"},
+    {file = "libcst-0.4.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e799add8fba4976628b9c1a6768d73178bf898f0ed1bd1322930c2d3db9063ba"},
+    {file = "libcst-0.4.9-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10479371d04ee8dc978c889c1774bbf6a83df88fa055fcb0159a606f6679c565"},
+    {file = "libcst-0.4.9-cp311-cp311-win_amd64.whl", hash = "sha256:7a98286cbbfa90a42d376900c875161ad02a5a2a6b7c94c0f7afd9075e329ce4"},
+    {file = "libcst-0.4.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:400166fc4efb9aa06ce44498d443aa78519082695b1894202dd73cd507d2d712"},
+    {file = "libcst-0.4.9-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46123863fba35cc84f7b54dd68826419cabfd9504d8a101c7fe3313ea03776f9"},
+    {file = "libcst-0.4.9-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27be8db54c0e5fe440021a771a38b81a7dbc23cd630eb8b0e9828b7717f9b702"},
+    {file = "libcst-0.4.9-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:132bec627b064bd567e7e4cd6c89524d02842151eb0d8f5f3f7ffd2579ec1b09"},
+    {file = "libcst-0.4.9-cp37-cp37m-win_amd64.whl", hash = "sha256:596860090aeed3ee6ad1e59c35c6c4110a57e4e896abf51b91cae003ec720a11"},
+    {file = "libcst-0.4.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f4487608258109f774300466d4ca97353df29ae6ac23d1502e13e5509423c9d5"},
+    {file = "libcst-0.4.9-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:aa53993e9a2853efb3ed3605da39f2e7125df6430f613eb67ef886c1ce4f94b5"},
+    {file = "libcst-0.4.9-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6ce794483d4c605ef0f5b199a49fb6996f9586ca938b7bfef213bd13858d7ab"},
+    {file = "libcst-0.4.9-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:786e562b54bbcd17a060d1244deeef466b7ee07fe544074c252c4a169e38f1ee"},
+    {file = "libcst-0.4.9-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:794250d2359edd518fb698e5d21c38a5bdfc5e4a75d0407b4c19818271ce6742"},
+    {file = "libcst-0.4.9-cp38-cp38-win_amd64.whl", hash = "sha256:76491f67431318c3145442e97dddcead7075b074c59eac51be7cc9e3fffec6ee"},
+    {file = "libcst-0.4.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3cf48d7aec6dc54b02aec0b1bb413c5bb3b02d852fd6facf1f05c7213e61a176"},
+    {file = "libcst-0.4.9-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9b3348c6b7711a5235b133bd8e11d22e903c388db42485b8ceb5f2aa0fae9b9f"},
+    {file = "libcst-0.4.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7e33b66762efaa014c38819efae5d8f726dd823e32d5d691035484411d2a2a69"},
+    {file = "libcst-0.4.9-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1350d375d3fb9b20a6cf10c09b2964baca9be753a033dde7c1aced49d8e58387"},
+    {file = "libcst-0.4.9-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3822056dc13326082362db35b3f649e0f4a97e36ddb4e487441da8e0fb9db7b3"},
+    {file = "libcst-0.4.9-cp39-cp39-win_amd64.whl", hash = "sha256:183636141b839aa35b639e100883813744523bc7c12528906621121731b28443"},
+    {file = "libcst-0.4.9.tar.gz", hash = "sha256:01786c403348f76f274dbaf3888ae237ffb73e6ed6973e65eba5c1fc389861dd"},
 ]
 
 [package.dependencies]
@@ -1987,12 +2098,13 @@ typing-extensions = ">=3.7.4.2"
 typing-inspect = ">=0.4.0"
 
 [package.extras]
-dev = ["Sphinx (>=5.1.1)", "black (==23.3.0)", "build (>=0.10.0)", "coverage (>=4.5.4)", "fixit (==0.1.1)", "flake8 (>=3.7.8,<5)", "hypothesis (>=4.36.0)", "hypothesmith (>=0.0.4)", "jinja2 (==3.1.2)", "jupyter (>=1.0.0)", "maturin (>=0.8.3,<0.16)", "nbsphinx (>=0.4.2)", "prompt-toolkit (>=2.0.9)", "pyre-check (==0.9.10)", "setuptools-rust (>=1.5.2)", "setuptools-scm (>=6.0.1)", "slotscheck (>=0.7.1)", "sphinx-rtd-theme (>=0.4.3)", "ufmt (==2.1.0)", "usort (==1.0.7)"]
+dev = ["Sphinx (>=5.1.1)", "black (==22.10.0)", "coverage (>=4.5.4)", "fixit (==0.1.1)", "flake8 (>=3.7.8,<5)", "hypothesis (>=4.36.0)", "hypothesmith (>=0.0.4)", "jinja2 (==3.1.2)", "jupyter (>=1.0.0)", "maturin (>=0.8.3,<0.14)", "nbsphinx (>=0.4.2)", "prompt-toolkit (>=2.0.9)", "pyre-check (==0.9.9)", "setuptools-rust (>=1.5.2)", "setuptools-scm (>=6.0.1)", "slotscheck (>=0.7.1)", "sphinx-rtd-theme (>=0.4.3)", "ufmt (==2.0.1)", "usort (==1.0.5)"]
 
 [[package]]
 name = "markdown"
 version = "3.3.7"
 description = "Python implementation of Markdown."
+category = "dev"
 optional = false
 python-versions = ">=3.6"
 files = [
@@ -2000,72 +2112,77 @@ files = [
     {file = "Markdown-3.3.7.tar.gz", hash = "sha256:cbb516f16218e643d8e0a95b309f77eb118cb138d39a4f27851e6a63581db874"},
 ]
 
+[package.dependencies]
+importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""}
+
 [package.extras]
 testing = ["coverage", "pyyaml"]
 
 [[package]]
 name = "markupsafe"
-version = "2.1.3"
+version = "2.1.2"
 description = "Safely add untrusted strings to HTML/XML markup."
+category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
-    {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"},
-    {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"},
-    {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"},
-    {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"},
-    {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"},
-    {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"},
-    {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"},
-    {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"},
-    {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"},
-    {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"},
-    {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"},
-    {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"},
-    {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"},
-    {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"},
-    {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"},
-    {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"},
-    {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"},
-    {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"},
-    {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"},
-    {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"},
-    {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"},
-    {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"},
-    {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"},
-    {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"},
-    {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"},
-    {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"},
-    {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"},
-    {file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"},
-    {file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"},
-    {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"},
-    {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"},
-    {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"},
-    {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"},
-    {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"},
-    {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"},
-    {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"},
-    {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"},
-    {file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"},
-    {file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"},
-    {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"},
-    {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"},
-    {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"},
-    {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"},
-    {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"},
-    {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"},
-    {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"},
-    {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"},
-    {file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"},
-    {file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"},
-    {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"},
+    {file = "MarkupSafe-2.1.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:665a36ae6f8f20a4676b53224e33d456a6f5a72657d9c83c2aa00765072f31f7"},
+    {file = "MarkupSafe-2.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:340bea174e9761308703ae988e982005aedf427de816d1afe98147668cc03036"},
+    {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22152d00bf4a9c7c83960521fc558f55a1adbc0631fbb00a9471e097b19d72e1"},
+    {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28057e985dace2f478e042eaa15606c7efccb700797660629da387eb289b9323"},
+    {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca244fa73f50a800cf8c3ebf7fd93149ec37f5cb9596aa8873ae2c1d23498601"},
+    {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d9d971ec1e79906046aa3ca266de79eac42f1dbf3612a05dc9368125952bd1a1"},
+    {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7e007132af78ea9df29495dbf7b5824cb71648d7133cf7848a2a5dd00d36f9ff"},
+    {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7313ce6a199651c4ed9d7e4cfb4aa56fe923b1adf9af3b420ee14e6d9a73df65"},
+    {file = "MarkupSafe-2.1.2-cp310-cp310-win32.whl", hash = "sha256:c4a549890a45f57f1ebf99c067a4ad0cb423a05544accaf2b065246827ed9603"},
+    {file = "MarkupSafe-2.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:835fb5e38fd89328e9c81067fd642b3593c33e1e17e2fdbf77f5676abb14a156"},
+    {file = "MarkupSafe-2.1.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2ec4f2d48ae59bbb9d1f9d7efb9236ab81429a764dedca114f5fdabbc3788013"},
+    {file = "MarkupSafe-2.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:608e7073dfa9e38a85d38474c082d4281f4ce276ac0010224eaba11e929dd53a"},
+    {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65608c35bfb8a76763f37036547f7adfd09270fbdbf96608be2bead319728fcd"},
+    {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2bfb563d0211ce16b63c7cb9395d2c682a23187f54c3d79bfec33e6705473c6"},
+    {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da25303d91526aac3672ee6d49a2f3db2d9502a4a60b55519feb1a4c7714e07d"},
+    {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9cad97ab29dfc3f0249b483412c85c8ef4766d96cdf9dcf5a1e3caa3f3661cf1"},
+    {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:085fd3201e7b12809f9e6e9bc1e5c96a368c8523fad5afb02afe3c051ae4afcc"},
+    {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1bea30e9bf331f3fef67e0a3877b2288593c98a21ccb2cf29b74c581a4eb3af0"},
+    {file = "MarkupSafe-2.1.2-cp311-cp311-win32.whl", hash = "sha256:7df70907e00c970c60b9ef2938d894a9381f38e6b9db73c5be35e59d92e06625"},
+    {file = "MarkupSafe-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:e55e40ff0cc8cc5c07996915ad367fa47da6b3fc091fdadca7f5403239c5fec3"},
+    {file = "MarkupSafe-2.1.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a6e40afa7f45939ca356f348c8e23048e02cb109ced1eb8420961b2f40fb373a"},
+    {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf877ab4ed6e302ec1d04952ca358b381a882fbd9d1b07cccbfd61783561f98a"},
+    {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63ba06c9941e46fa389d389644e2d8225e0e3e5ebcc4ff1ea8506dce646f8c8a"},
+    {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f1cd098434e83e656abf198f103a8207a8187c0fc110306691a2e94a78d0abb2"},
+    {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:55f44b440d491028addb3b88f72207d71eeebfb7b5dbf0643f7c023ae1fba619"},
+    {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a6f2fcca746e8d5910e18782f976489939d54a91f9411c32051b4aab2bd7c513"},
+    {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0b462104ba25f1ac006fdab8b6a01ebbfbce9ed37fd37fd4acd70c67c973e460"},
+    {file = "MarkupSafe-2.1.2-cp37-cp37m-win32.whl", hash = "sha256:7668b52e102d0ed87cb082380a7e2e1e78737ddecdde129acadb0eccc5423859"},
+    {file = "MarkupSafe-2.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6d6607f98fcf17e534162f0709aaad3ab7a96032723d8ac8750ffe17ae5a0666"},
+    {file = "MarkupSafe-2.1.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a806db027852538d2ad7555b203300173dd1b77ba116de92da9afbc3a3be3eed"},
+    {file = "MarkupSafe-2.1.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a4abaec6ca3ad8660690236d11bfe28dfd707778e2442b45addd2f086d6ef094"},
+    {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f03a532d7dee1bed20bc4884194a16160a2de9ffc6354b3878ec9682bb623c54"},
+    {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4cf06cdc1dda95223e9d2d3c58d3b178aa5dacb35ee7e3bbac10e4e1faacb419"},
+    {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22731d79ed2eb25059ae3df1dfc9cb1546691cc41f4e3130fe6bfbc3ecbbecfa"},
+    {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f8ffb705ffcf5ddd0e80b65ddf7bed7ee4f5a441ea7d3419e861a12eaf41af58"},
+    {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8db032bf0ce9022a8e41a22598eefc802314e81b879ae093f36ce9ddf39ab1ba"},
+    {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2298c859cfc5463f1b64bd55cb3e602528db6fa0f3cfd568d3605c50678f8f03"},
+    {file = "MarkupSafe-2.1.2-cp38-cp38-win32.whl", hash = "sha256:50c42830a633fa0cf9e7d27664637532791bfc31c731a87b202d2d8ac40c3ea2"},
+    {file = "MarkupSafe-2.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:bb06feb762bade6bf3c8b844462274db0c76acc95c52abe8dbed28ae3d44a147"},
+    {file = "MarkupSafe-2.1.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:99625a92da8229df6d44335e6fcc558a5037dd0a760e11d84be2260e6f37002f"},
+    {file = "MarkupSafe-2.1.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8bca7e26c1dd751236cfb0c6c72d4ad61d986e9a41bbf76cb445f69488b2a2bd"},
+    {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f"},
+    {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40dfd3fefbef579ee058f139733ac336312663c6706d1163b82b3003fb1925c4"},
+    {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:090376d812fb6ac5f171e5938e82e7f2d7adc2b629101cec0db8b267815c85e2"},
+    {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2e7821bffe00aa6bd07a23913b7f4e01328c3d5cc0b40b36c0bd81d362faeb65"},
+    {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c0a33bc9f02c2b17c3ea382f91b4db0e6cde90b63b296422a939886a7a80de1c"},
+    {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b8526c6d437855442cdd3d87eede9c425c4445ea011ca38d937db299382e6fa3"},
+    {file = "MarkupSafe-2.1.2-cp39-cp39-win32.whl", hash = "sha256:137678c63c977754abe9086a3ec011e8fd985ab90631145dfb9294ad09c102a7"},
+    {file = "MarkupSafe-2.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:0576fe974b40a400449768941d5d0858cc624e3249dfd1e0c33674e5c7ca7aed"},
+    {file = "MarkupSafe-2.1.2.tar.gz", hash = "sha256:abcabc8c2b26036d62d4c746381a6f7cf60aafcc653198ad678306986b09450d"},
 ]
 
 [[package]]
 name = "mccabe"
 version = "0.6.1"
 description = "McCabe checker, plugin for flake8"
+category = "dev"
 optional = false
 python-versions = "*"
 files = [
@@ -2077,6 +2194,7 @@ files = [
 name = "mergedeep"
 version = "1.3.4"
 description = "A deep merge function for 🐍."
+category = "dev"
 optional = false
 python-versions = ">=3.6"
 files = [
@@ -2086,19 +2204,21 @@ files = [
 
 [[package]]
 name = "mkdocs"
-version = "1.4.3"
+version = "1.4.2"
 description = "Project documentation with Markdown."
+category = "dev"
 optional = false
 python-versions = ">=3.7"
 files = [
-    {file = "mkdocs-1.4.3-py3-none-any.whl", hash = "sha256:6ee46d309bda331aac915cd24aab882c179a933bd9e77b80ce7d2eaaa3f689dd"},
-    {file = "mkdocs-1.4.3.tar.gz", hash = "sha256:5955093bbd4dd2e9403c5afaf57324ad8b04f16886512a3ee6ef828956481c57"},
+    {file = "mkdocs-1.4.2-py3-none-any.whl", hash = "sha256:c8856a832c1e56702577023cd64cc5f84948280c1c0fcc6af4cd39006ea6aa8c"},
+    {file = "mkdocs-1.4.2.tar.gz", hash = "sha256:8947af423a6d0facf41ea1195b8e1e8c85ad94ac95ae307fe11232e0424b11c5"},
 ]
 
 [package.dependencies]
 click = ">=7.0"
 colorama = {version = ">=0.4", markers = "platform_system == \"Windows\""}
 ghp-import = ">=1.0"
+importlib-metadata = {version = ">=4.3", markers = "python_version < \"3.10\""}
 jinja2 = ">=2.11.1"
 markdown = ">=3.2.1,<3.4"
 mergedeep = ">=1.3.4"
@@ -2115,6 +2235,7 @@ min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4)", "ghp-imp
 name = "mock"
 version = "4.0.3"
 description = "Rolling backport of unittest.mock for all Pythons"
+category = "dev"
 optional = false
 python-versions = ">=3.6"
 files = [
@@ -2129,17 +2250,18 @@ test = ["pytest (<5.4)", "pytest-cov"]
 
 [[package]]
 name = "msal"
-version = "1.22.0"
+version = "1.21.0"
 description = "The Microsoft Authentication Library (MSAL) for Python library enables your app to access the Microsoft Cloud by supporting authentication of users with Microsoft Azure Active Directory accounts (AAD) and Microsoft Accounts (MSA) using industry standard OAuth2 and OpenID Connect."
+category = "main"
 optional = false
 python-versions = "*"
 files = [
-    {file = "msal-1.22.0-py2.py3-none-any.whl", hash = "sha256:9120b7eafdf061c92f7b3d744e5f325fca35873445fa8ffebb40b1086a13dd58"},
-    {file = "msal-1.22.0.tar.gz", hash = "sha256:8a82f5375642c1625c89058018430294c109440dce42ea667d466c2cab520acd"},
+    {file = "msal-1.21.0-py2.py3-none-any.whl", hash = "sha256:e8444617c1eccdff7bb73f5d4f94036002accea4a2c05f8f39c9efb5bd2b0c6a"},
+    {file = "msal-1.21.0.tar.gz", hash = "sha256:96b5c867830fd116e5f7d0ec8ef1b238b4cda4d1aea86d8fecf518260e136fbf"},
 ]
 
 [package.dependencies]
-cryptography = ">=0.6,<43"
+cryptography = ">=0.6,<41"
 PyJWT = {version = ">=1.0.0,<3", extras = ["crypto"]}
 requests = ">=2.0.0,<3"
 
@@ -2150,6 +2272,7 @@ broker = ["pymsalruntime (>=0.13.2,<0.14)"]
 name = "msal-extensions"
 version = "1.0.0"
 description = "Microsoft Authentication Library extensions (MSAL EX) provides a persistence API that can save your data on disk, encrypted on Windows, macOS and Linux. Concurrent data access will be coordinated by a file lock mechanism."
+category = "main"
 optional = false
 python-versions = "*"
 files = [
@@ -2168,6 +2291,7 @@ portalocker = [
 name = "msrest"
 version = "0.7.1"
 description = "AutoRest swagger generator Python client runtime."
+category = "main"
 optional = false
 python-versions = ">=3.6"
 files = [
@@ -2189,6 +2313,7 @@ async = ["aiodns", "aiohttp (>=3.0)"]
 name = "msrestazure"
 version = "0.6.4"
 description = "AutoRest swagger generator Python client runtime. Azure-specific module."
+category = "main"
 optional = false
 python-versions = "*"
 files = [
@@ -2205,6 +2330,7 @@ six = "*"
 name = "multidict"
 version = "6.0.4"
 description = "multidict implementation"
+category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -2286,47 +2412,44 @@ files = [
 
 [[package]]
 name = "mypy"
-version = "1.3.0"
+version = "0.961"
 description = "Optional static typing for Python"
+category = "dev"
 optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.6"
 files = [
-    {file = "mypy-1.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c1eb485cea53f4f5284e5baf92902cd0088b24984f4209e25981cc359d64448d"},
-    {file = "mypy-1.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4c99c3ecf223cf2952638da9cd82793d8f3c0c5fa8b6ae2b2d9ed1e1ff51ba85"},
-    {file = "mypy-1.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:550a8b3a19bb6589679a7c3c31f64312e7ff482a816c96e0cecec9ad3a7564dd"},
-    {file = "mypy-1.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cbc07246253b9e3d7d74c9ff948cd0fd7a71afcc2b77c7f0a59c26e9395cb152"},
-    {file = "mypy-1.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:a22435632710a4fcf8acf86cbd0d69f68ac389a3892cb23fbad176d1cddaf228"},
-    {file = "mypy-1.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6e33bb8b2613614a33dff70565f4c803f889ebd2f859466e42b46e1df76018dd"},
-    {file = "mypy-1.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7d23370d2a6b7a71dc65d1266f9a34e4cde9e8e21511322415db4b26f46f6b8c"},
-    {file = "mypy-1.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:658fe7b674769a0770d4b26cb4d6f005e88a442fe82446f020be8e5f5efb2fae"},
-    {file = "mypy-1.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6e42d29e324cdda61daaec2336c42512e59c7c375340bd202efa1fe0f7b8f8ca"},
-    {file = "mypy-1.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:d0b6c62206e04061e27009481cb0ec966f7d6172b5b936f3ead3d74f29fe3dcf"},
-    {file = "mypy-1.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:76ec771e2342f1b558c36d49900dfe81d140361dd0d2df6cd71b3db1be155409"},
-    {file = "mypy-1.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ebc95f8386314272bbc817026f8ce8f4f0d2ef7ae44f947c4664efac9adec929"},
-    {file = "mypy-1.3.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:faff86aa10c1aa4a10e1a301de160f3d8fc8703b88c7e98de46b531ff1276a9a"},
-    {file = "mypy-1.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:8c5979d0deb27e0f4479bee18ea0f83732a893e81b78e62e2dda3e7e518c92ee"},
-    {file = "mypy-1.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c5d2cc54175bab47011b09688b418db71403aefad07cbcd62d44010543fc143f"},
-    {file = "mypy-1.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:87df44954c31d86df96c8bd6e80dfcd773473e877ac6176a8e29898bfb3501cb"},
-    {file = "mypy-1.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:473117e310febe632ddf10e745a355714e771ffe534f06db40702775056614c4"},
-    {file = "mypy-1.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:74bc9b6e0e79808bf8678d7678b2ae3736ea72d56eede3820bd3849823e7f305"},
-    {file = "mypy-1.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:44797d031a41516fcf5cbfa652265bb994e53e51994c1bd649ffcd0c3a7eccbf"},
-    {file = "mypy-1.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ddae0f39ca146972ff6bb4399f3b2943884a774b8771ea0a8f50e971f5ea5ba8"},
-    {file = "mypy-1.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1c4c42c60a8103ead4c1c060ac3cdd3ff01e18fddce6f1016e08939647a0e703"},
-    {file = "mypy-1.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e86c2c6852f62f8f2b24cb7a613ebe8e0c7dc1402c61d36a609174f63e0ff017"},
-    {file = "mypy-1.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f9dca1e257d4cc129517779226753dbefb4f2266c4eaad610fc15c6a7e14283e"},
-    {file = "mypy-1.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:95d8d31a7713510685b05fbb18d6ac287a56c8f6554d88c19e73f724a445448a"},
-    {file = "mypy-1.3.0-py3-none-any.whl", hash = "sha256:a8763e72d5d9574d45ce5881962bc8e9046bf7b375b0abf031f3e6811732a897"},
-    {file = "mypy-1.3.0.tar.gz", hash = "sha256:e1f4d16e296f5135624b34e8fb741eb0eadedca90862405b1f1fde2040b9bd11"},
-]
-
-[package.dependencies]
-mypy-extensions = ">=1.0.0"
+    {file = "mypy-0.961-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:697540876638ce349b01b6786bc6094ccdaba88af446a9abb967293ce6eaa2b0"},
+    {file = "mypy-0.961-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b117650592e1782819829605a193360a08aa99f1fc23d1d71e1a75a142dc7e15"},
+    {file = "mypy-0.961-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bdd5ca340beffb8c44cb9dc26697628d1b88c6bddf5c2f6eb308c46f269bb6f3"},
+    {file = "mypy-0.961-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3e09f1f983a71d0672bbc97ae33ee3709d10c779beb613febc36805a6e28bb4e"},
+    {file = "mypy-0.961-cp310-cp310-win_amd64.whl", hash = "sha256:e999229b9f3198c0c880d5e269f9f8129c8862451ce53a011326cad38b9ccd24"},
+    {file = "mypy-0.961-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b24be97351084b11582fef18d79004b3e4db572219deee0212078f7cf6352723"},
+    {file = "mypy-0.961-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f4a21d01fc0ba4e31d82f0fff195682e29f9401a8bdb7173891070eb260aeb3b"},
+    {file = "mypy-0.961-cp36-cp36m-win_amd64.whl", hash = "sha256:439c726a3b3da7ca84a0199a8ab444cd8896d95012c4a6c4a0d808e3147abf5d"},
+    {file = "mypy-0.961-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5a0b53747f713f490affdceef835d8f0cb7285187a6a44c33821b6d1f46ed813"},
+    {file = "mypy-0.961-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0e9f70df36405c25cc530a86eeda1e0867863d9471fe76d1273c783df3d35c2e"},
+    {file = "mypy-0.961-cp37-cp37m-win_amd64.whl", hash = "sha256:b88f784e9e35dcaa075519096dc947a388319cb86811b6af621e3523980f1c8a"},
+    {file = "mypy-0.961-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:d5aaf1edaa7692490f72bdb9fbd941fbf2e201713523bdb3f4038be0af8846c6"},
+    {file = "mypy-0.961-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9f5f5a74085d9a81a1f9c78081d60a0040c3efb3f28e5c9912b900adf59a16e6"},
+    {file = "mypy-0.961-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f4b794db44168a4fc886e3450201365c9526a522c46ba089b55e1f11c163750d"},
+    {file = "mypy-0.961-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:64759a273d590040a592e0f4186539858c948302c653c2eac840c7a3cd29e51b"},
+    {file = "mypy-0.961-cp38-cp38-win_amd64.whl", hash = "sha256:63e85a03770ebf403291ec50097954cc5caf2a9205c888ce3a61bd3f82e17569"},
+    {file = "mypy-0.961-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5f1332964963d4832a94bebc10f13d3279be3ce8f6c64da563d6ee6e2eeda932"},
+    {file = "mypy-0.961-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:006be38474216b833eca29ff6b73e143386f352e10e9c2fbe76aa8549e5554f5"},
+    {file = "mypy-0.961-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9940e6916ed9371809b35b2154baf1f684acba935cd09928952310fbddaba648"},
+    {file = "mypy-0.961-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a5ea0875a049de1b63b972456542f04643daf320d27dc592d7c3d9cd5d9bf950"},
+    {file = "mypy-0.961-cp39-cp39-win_amd64.whl", hash = "sha256:1ece702f29270ec6af25db8cf6185c04c02311c6bb21a69f423d40e527b75c56"},
+    {file = "mypy-0.961-py3-none-any.whl", hash = "sha256:03c6cc893e7563e7b2949b969e63f02c000b32502a1b4d1314cabe391aa87d66"},
+    {file = "mypy-0.961.tar.gz", hash = "sha256:f730d56cb924d371c26b8eaddeea3cc07d78ff51c521c6d04899ac6904b75492"},
+]
+
+[package.dependencies]
+mypy-extensions = ">=0.4.3"
 tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
 typing-extensions = ">=3.10"
 
 [package.extras]
 dmypy = ["psutil (>=4.0)"]
-install-types = ["pip"]
 python2 = ["typed-ast (>=1.4.0,<2)"]
 reports = ["lxml"]
 
@@ -2334,6 +2457,7 @@ reports = ["lxml"]
 name = "mypy-extensions"
 version = "1.0.0"
 description = "Type system extensions for programs checked with the mypy type checker."
+category = "main"
 optional = false
 python-versions = ">=3.5"
 files = [
@@ -2345,6 +2469,7 @@ files = [
 name = "netaddr"
 version = "0.8.0"
 description = "A network address manipulation library for Python"
+category = "main"
 optional = false
 python-versions = "*"
 files = [
@@ -2356,6 +2481,7 @@ files = [
 name = "networkx"
 version = "2.8.3"
 description = "Python package for creating and manipulating graphs and networks"
+category = "main"
 optional = false
 python-versions = ">=3.8"
 files = [
@@ -2374,6 +2500,7 @@ test = ["codecov (>=2.1)", "pytest (>=7.1)", "pytest-cov (>=3.0)"]
 name = "ninja"
 version = "1.11.1"
 description = "Ninja is a small build system with a focus on speed"
+category = "dev"
 optional = false
 python-versions = "*"
 files = [
@@ -2401,46 +2528,48 @@ test = ["codecov (>=2.0.5)", "coverage (>=4.2)", "flake8 (>=3.0.4)", "pytest (>=
 
 [[package]]
 name = "numpy"
-version = "1.24.3"
+version = "1.24.2"
 description = "Fundamental package for array computing in Python"
+category = "main"
 optional = false
 python-versions = ">=3.8"
 files = [
-    {file = "numpy-1.24.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3c1104d3c036fb81ab923f507536daedc718d0ad5a8707c6061cdfd6d184e570"},
-    {file = "numpy-1.24.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:202de8f38fc4a45a3eea4b63e2f376e5f2dc64ef0fa692838e31a808520efaf7"},
-    {file = "numpy-1.24.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8535303847b89aa6b0f00aa1dc62867b5a32923e4d1681a35b5eef2d9591a463"},
-    {file = "numpy-1.24.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d926b52ba1367f9acb76b0df6ed21f0b16a1ad87c6720a1121674e5cf63e2b6"},
-    {file = "numpy-1.24.3-cp310-cp310-win32.whl", hash = "sha256:f21c442fdd2805e91799fbe044a7b999b8571bb0ab0f7850d0cb9641a687092b"},
-    {file = "numpy-1.24.3-cp310-cp310-win_amd64.whl", hash = "sha256:ab5f23af8c16022663a652d3b25dcdc272ac3f83c3af4c02eb8b824e6b3ab9d7"},
-    {file = "numpy-1.24.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9a7721ec204d3a237225db3e194c25268faf92e19338a35f3a224469cb6039a3"},
-    {file = "numpy-1.24.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d6cc757de514c00b24ae8cf5c876af2a7c3df189028d68c0cb4eaa9cd5afc2bf"},
-    {file = "numpy-1.24.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76e3f4e85fc5d4fd311f6e9b794d0c00e7002ec122be271f2019d63376f1d385"},
-    {file = "numpy-1.24.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1d3c026f57ceaad42f8231305d4653d5f05dc6332a730ae5c0bea3513de0950"},
-    {file = "numpy-1.24.3-cp311-cp311-win32.whl", hash = "sha256:c91c4afd8abc3908e00a44b2672718905b8611503f7ff87390cc0ac3423fb096"},
-    {file = "numpy-1.24.3-cp311-cp311-win_amd64.whl", hash = "sha256:5342cf6aad47943286afa6f1609cad9b4266a05e7f2ec408e2cf7aea7ff69d80"},
-    {file = "numpy-1.24.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7776ea65423ca6a15255ba1872d82d207bd1e09f6d0894ee4a64678dd2204078"},
-    {file = "numpy-1.24.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ae8d0be48d1b6ed82588934aaaa179875e7dc4f3d84da18d7eae6eb3f06c242c"},
-    {file = "numpy-1.24.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ecde0f8adef7dfdec993fd54b0f78183051b6580f606111a6d789cd14c61ea0c"},
-    {file = "numpy-1.24.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4749e053a29364d3452c034827102ee100986903263e89884922ef01a0a6fd2f"},
-    {file = "numpy-1.24.3-cp38-cp38-win32.whl", hash = "sha256:d933fabd8f6a319e8530d0de4fcc2e6a61917e0b0c271fded460032db42a0fe4"},
-    {file = "numpy-1.24.3-cp38-cp38-win_amd64.whl", hash = "sha256:56e48aec79ae238f6e4395886b5eaed058abb7231fb3361ddd7bfdf4eed54289"},
-    {file = "numpy-1.24.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4719d5aefb5189f50887773699eaf94e7d1e02bf36c1a9d353d9f46703758ca4"},
-    {file = "numpy-1.24.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0ec87a7084caa559c36e0a2309e4ecb1baa03b687201d0a847c8b0ed476a7187"},
-    {file = "numpy-1.24.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea8282b9bcfe2b5e7d491d0bf7f3e2da29700cec05b49e64d6246923329f2b02"},
-    {file = "numpy-1.24.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:210461d87fb02a84ef243cac5e814aad2b7f4be953b32cb53327bb49fd77fbb4"},
-    {file = "numpy-1.24.3-cp39-cp39-win32.whl", hash = "sha256:784c6da1a07818491b0ffd63c6bbe5a33deaa0e25a20e1b3ea20cf0e43f8046c"},
-    {file = "numpy-1.24.3-cp39-cp39-win_amd64.whl", hash = "sha256:d5036197ecae68d7f491fcdb4df90082b0d4960ca6599ba2659957aafced7c17"},
-    {file = "numpy-1.24.3-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:352ee00c7f8387b44d19f4cada524586f07379c0d49270f87233983bc5087ca0"},
-    {file = "numpy-1.24.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a7d6acc2e7524c9955e5c903160aa4ea083736fde7e91276b0e5d98e6332812"},
-    {file = "numpy-1.24.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:35400e6a8d102fd07c71ed7dcadd9eb62ee9a6e84ec159bd48c28235bbb0f8e4"},
-    {file = "numpy-1.24.3.tar.gz", hash = "sha256:ab344f1bf21f140adab8e47fdbc7c35a477dc01408791f8ba00d018dd0bc5155"},
+    {file = "numpy-1.24.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eef70b4fc1e872ebddc38cddacc87c19a3709c0e3e5d20bf3954c147b1dd941d"},
+    {file = "numpy-1.24.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e8d2859428712785e8a8b7d2b3ef0a1d1565892367b32f915c4a4df44d0e64f5"},
+    {file = "numpy-1.24.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6524630f71631be2dabe0c541e7675db82651eb998496bbe16bc4f77f0772253"},
+    {file = "numpy-1.24.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a51725a815a6188c662fb66fb32077709a9ca38053f0274640293a14fdd22978"},
+    {file = "numpy-1.24.2-cp310-cp310-win32.whl", hash = "sha256:2620e8592136e073bd12ee4536149380695fbe9ebeae845b81237f986479ffc9"},
+    {file = "numpy-1.24.2-cp310-cp310-win_amd64.whl", hash = "sha256:97cf27e51fa078078c649a51d7ade3c92d9e709ba2bfb97493007103c741f1d0"},
+    {file = "numpy-1.24.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7de8fdde0003f4294655aa5d5f0a89c26b9f22c0a58790c38fae1ed392d44a5a"},
+    {file = "numpy-1.24.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4173bde9fa2a005c2c6e2ea8ac1618e2ed2c1c6ec8a7657237854d42094123a0"},
+    {file = "numpy-1.24.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4cecaed30dc14123020f77b03601559fff3e6cd0c048f8b5289f4eeabb0eb281"},
+    {file = "numpy-1.24.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a23f8440561a633204a67fb44617ce2a299beecf3295f0d13c495518908e910"},
+    {file = "numpy-1.24.2-cp311-cp311-win32.whl", hash = "sha256:e428c4fbfa085f947b536706a2fc349245d7baa8334f0c5723c56a10595f9b95"},
+    {file = "numpy-1.24.2-cp311-cp311-win_amd64.whl", hash = "sha256:557d42778a6869c2162deb40ad82612645e21d79e11c1dc62c6e82a2220ffb04"},
+    {file = "numpy-1.24.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d0a2db9d20117bf523dde15858398e7c0858aadca7c0f088ac0d6edd360e9ad2"},
+    {file = "numpy-1.24.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c72a6b2f4af1adfe193f7beb91ddf708ff867a3f977ef2ec53c0ffb8283ab9f5"},
+    {file = "numpy-1.24.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c29e6bd0ec49a44d7690ecb623a8eac5ab8a923bce0bea6293953992edf3a76a"},
+    {file = "numpy-1.24.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2eabd64ddb96a1239791da78fa5f4e1693ae2dadc82a76bc76a14cbb2b966e96"},
+    {file = "numpy-1.24.2-cp38-cp38-win32.whl", hash = "sha256:e3ab5d32784e843fc0dd3ab6dcafc67ef806e6b6828dc6af2f689be0eb4d781d"},
+    {file = "numpy-1.24.2-cp38-cp38-win_amd64.whl", hash = "sha256:76807b4063f0002c8532cfeac47a3068a69561e9c8715efdad3c642eb27c0756"},
+    {file = "numpy-1.24.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4199e7cfc307a778f72d293372736223e39ec9ac096ff0a2e64853b866a8e18a"},
+    {file = "numpy-1.24.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:adbdce121896fd3a17a77ab0b0b5eedf05a9834a18699db6829a64e1dfccca7f"},
+    {file = "numpy-1.24.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:889b2cc88b837d86eda1b17008ebeb679d82875022200c6e8e4ce6cf549b7acb"},
+    {file = "numpy-1.24.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f64bb98ac59b3ea3bf74b02f13836eb2e24e48e0ab0145bbda646295769bd780"},
+    {file = "numpy-1.24.2-cp39-cp39-win32.whl", hash = "sha256:63e45511ee4d9d976637d11e6c9864eae50e12dc9598f531c035265991910468"},
+    {file = "numpy-1.24.2-cp39-cp39-win_amd64.whl", hash = "sha256:a77d3e1163a7770164404607b7ba3967fb49b24782a6ef85d9b5f54126cc39e5"},
+    {file = "numpy-1.24.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:92011118955724465fb6853def593cf397b4a1367495e0b59a7e69d40c4eb71d"},
+    {file = "numpy-1.24.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9006288bcf4895917d02583cf3411f98631275bc67cce355a7f39f8c14338fa"},
+    {file = "numpy-1.24.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:150947adbdfeceec4e5926d956a06865c1c690f2fd902efede4ca6fe2e657c3f"},
+    {file = "numpy-1.24.2.tar.gz", hash = "sha256:003a9f530e880cb2cd177cba1af7220b9aa42def9c4afc2a2fc3ee6be7eb2b22"},
 ]
 
 [[package]]
 name = "oauth2client"
 version = "4.1.3"
 description = "OAuth 2.0 client library"
-optional = false
+category = "main"
+optional = true
 python-versions = "*"
 files = [
     {file = "oauth2client-4.1.3-py2.py3-none-any.whl", hash = "sha256:b8a81cc5d60e2d364f0b1b98f958dbd472887acaf1a5b05e21c28c31a2d6d3ac"},
@@ -2458,6 +2587,7 @@ six = ">=1.6.1"
 name = "oauthlib"
 version = "3.2.2"
 description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic"
+category = "main"
 optional = false
 python-versions = ">=3.6"
 files = [
@@ -2472,19 +2602,21 @@ signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"]
 
 [[package]]
 name = "packaging"
-version = "23.1"
+version = "23.0"
 description = "Core utilities for Python packages"
+category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
-    {file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"},
-    {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"},
+    {file = "packaging-23.0-py3-none-any.whl", hash = "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2"},
+    {file = "packaging-23.0.tar.gz", hash = "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97"},
 ]
 
 [[package]]
 name = "pandas"
 version = "1.5.3"
 description = "Powerful data structures for data analysis, time series, and statistics"
+category = "main"
 optional = false
 python-versions = ">=3.8"
 files = [
@@ -2518,7 +2650,10 @@ files = [
 ]
 
 [package.dependencies]
-numpy = {version = ">=1.21.0", markers = "python_version >= \"3.10\""}
+numpy = [
+    {version = ">=1.20.3", markers = "python_version < \"3.10\""},
+    {version = ">=1.21.0", markers = "python_version >= \"3.10\""},
+]
 python-dateutil = ">=2.8.1"
 pytz = ">=2020.1"
 
@@ -2527,24 +2662,26 @@ test = ["hypothesis (>=5.5.3)", "pytest (>=6.0)", "pytest-xdist (>=1.31)"]
 
 [[package]]
 name = "platformdirs"
-version = "3.5.3"
+version = "3.2.0"
 description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
+category = "dev"
 optional = false
 python-versions = ">=3.7"
 files = [
-    {file = "platformdirs-3.5.3-py3-none-any.whl", hash = "sha256:0ade98a4895e87dc51d47151f7d2ec290365a585151d97b4d8d6312ed6132fed"},
-    {file = "platformdirs-3.5.3.tar.gz", hash = "sha256:e48fabd87db8f3a7df7150a4a5ea22c546ee8bc39bc2473244730d4b56d2cc4e"},
+    {file = "platformdirs-3.2.0-py3-none-any.whl", hash = "sha256:ebe11c0d7a805086e99506aa331612429a72ca7cd52a1f0d277dc4adc20cb10e"},
+    {file = "platformdirs-3.2.0.tar.gz", hash = "sha256:d5b638ca397f25f979350ff789db335903d7ea010ab28903f57b27e1b16c2b08"},
 ]
 
 [package.extras]
-docs = ["furo (>=2023.5.20)", "proselint (>=0.13)", "sphinx (>=7.0.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"]
-test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.3.1)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)"]
+docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx (>=6.1.3)", "sphinx-autodoc-typehints (>=1.22,!=1.23.4)"]
+test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.2.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"]
 
 [[package]]
 name = "ply"
 version = "3.8"
 description = "Python Lex & Yacc"
-optional = false
+category = "main"
+optional = true
 python-versions = "*"
 files = [
     {file = "ply-3.8.tar.gz", hash = "sha256:e7d1bdff026beb159c9942f7a17e102c375638d9478a7ecd4cc0c76afd8de0b8"},
@@ -2554,6 +2691,7 @@ files = [
 name = "portalocker"
 version = "2.7.0"
 description = "Wraps the portalocker recipe for easy usage"
+category = "main"
 optional = false
 python-versions = ">=3.5"
 files = [
@@ -2571,13 +2709,13 @@ tests = ["pytest (>=5.4.1)", "pytest-cov (>=2.8.1)", "pytest-mypy (>=0.8.0)", "p
 
 [[package]]
 name = "prettytable"
-version = "3.8.0"
+version = "3.7.0"
 description = "A simple Python library for easily displaying tabular data in a visually appealing ASCII table format"
+category = "main"
 optional = false
-python-versions = ">=3.8"
+python-versions = ">=3.7"
 files = [
-    {file = "prettytable-3.8.0-py3-none-any.whl", hash = "sha256:03481bca25ae0c28958c8cd6ac5165c159ce89f7ccde04d5c899b24b68bb13b7"},
-    {file = "prettytable-3.8.0.tar.gz", hash = "sha256:031eae6a9102017e8c7c7906460d150b7ed78b20fd1d8c8be4edaf88556c07ce"},
+    {file = "prettytable-3.7.0-py3-none-any.whl", hash = "sha256:f4aaf2ed6e6062a82fd2e6e5289bbbe705ec2788fe401a3a1f62a1cea55526d2"},
 ]
 
 [package.dependencies]
@@ -2588,13 +2726,14 @@ tests = ["pytest", "pytest-cov", "pytest-lazy-fixture"]
 
 [[package]]
 name = "prometheus-client"
-version = "0.17.0"
+version = "0.16.0"
 description = "Python client for the Prometheus monitoring system."
-optional = false
+category = "main"
+optional = true
 python-versions = ">=3.6"
 files = [
-    {file = "prometheus_client-0.17.0-py3-none-any.whl", hash = "sha256:a77b708cf083f4d1a3fb3ce5c95b4afa32b9c521ae363354a4a910204ea095ce"},
-    {file = "prometheus_client-0.17.0.tar.gz", hash = "sha256:9c3b26f1535945e85b8934fb374678d263137b78ef85f305b1156c7c881cd11b"},
+    {file = "prometheus_client-0.16.0-py3-none-any.whl", hash = "sha256:0836af6eb2c8f4fed712b2f279f6c0a8bbab29f9f4aa15276b91c7cb0d1616ab"},
+    {file = "prometheus_client-0.16.0.tar.gz", hash = "sha256:a03e35b359f14dd1630898543e2120addfdeacd1a6069c1367ae90fd93ad3f48"},
 ]
 
 [package.extras]
@@ -2604,7 +2743,8 @@ twisted = ["twisted"]
 name = "prompt-toolkit"
 version = "3.0.38"
 description = "Library for building powerful interactive command lines in Python"
-optional = false
+category = "main"
+optional = true
 python-versions = ">=3.7.0"
 files = [
     {file = "prompt_toolkit-3.0.38-py3-none-any.whl", hash = "sha256:45ea77a2f7c60418850331366c81cf6b5b9cf4c7fd34616f733c5427e6abbb1f"},
@@ -2618,6 +2758,7 @@ wcwidth = "*"
 name = "proto-plus"
 version = "1.19.6"
 description = "Beautiful, Pythonic protocol buffers."
+category = "main"
 optional = false
 python-versions = ">=3.6"
 files = [
@@ -2635,6 +2776,7 @@ testing = ["google-api-core[grpc] (>=1.22.2)"]
 name = "protobuf"
 version = "3.12.2"
 description = "Protocol Buffers"
+category = "main"
 optional = false
 python-versions = "*"
 files = [
@@ -2666,7 +2808,8 @@ six = ">=1.9"
 name = "psq"
 version = "0.2.1"
 description = "A simple task queue using Google Cloud Pub/Sub"
-optional = false
+category = "main"
+optional = true
 python-versions = "*"
 files = [
     {file = "psq-0.2.1-py2.py3-none-any.whl", hash = "sha256:8b327ecbbb98127367da9b7c3cb546882de2fc4e6a34b20d7fe54a13d5028121"},
@@ -2681,25 +2824,26 @@ werkzeug = "*"
 
 [[package]]
 name = "psutil"
-version = "5.9.5"
+version = "5.9.4"
 description = "Cross-platform lib for process and system monitoring in Python."
+category = "main"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
 files = [
-    {file = "psutil-5.9.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:be8929ce4313f9f8146caad4272f6abb8bf99fc6cf59344a3167ecd74f4f203f"},
-    {file = "psutil-5.9.5-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ab8ed1a1d77c95453db1ae00a3f9c50227ebd955437bcf2a574ba8adbf6a74d5"},
-    {file = "psutil-5.9.5-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:4aef137f3345082a3d3232187aeb4ac4ef959ba3d7c10c33dd73763fbc063da4"},
-    {file = "psutil-5.9.5-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:ea8518d152174e1249c4f2a1c89e3e6065941df2fa13a1ab45327716a23c2b48"},
-    {file = "psutil-5.9.5-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:acf2aef9391710afded549ff602b5887d7a2349831ae4c26be7c807c0a39fac4"},
-    {file = "psutil-5.9.5-cp27-none-win32.whl", hash = "sha256:5b9b8cb93f507e8dbaf22af6a2fd0ccbe8244bf30b1baad6b3954e935157ae3f"},
-    {file = "psutil-5.9.5-cp27-none-win_amd64.whl", hash = "sha256:8c5f7c5a052d1d567db4ddd231a9d27a74e8e4a9c3f44b1032762bd7b9fdcd42"},
-    {file = "psutil-5.9.5-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:3c6f686f4225553615612f6d9bc21f1c0e305f75d7d8454f9b46e901778e7217"},
-    {file = "psutil-5.9.5-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7a7dd9997128a0d928ed4fb2c2d57e5102bb6089027939f3b722f3a210f9a8da"},
-    {file = "psutil-5.9.5-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89518112647f1276b03ca97b65cc7f64ca587b1eb0278383017c2a0dcc26cbe4"},
-    {file = "psutil-5.9.5-cp36-abi3-win32.whl", hash = "sha256:104a5cc0e31baa2bcf67900be36acde157756b9c44017b86b2c049f11957887d"},
-    {file = "psutil-5.9.5-cp36-abi3-win_amd64.whl", hash = "sha256:b258c0c1c9d145a1d5ceffab1134441c4c5113b2417fafff7315a917a026c3c9"},
-    {file = "psutil-5.9.5-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:c607bb3b57dc779d55e1554846352b4e358c10fff3abf3514a7a6601beebdb30"},
-    {file = "psutil-5.9.5.tar.gz", hash = "sha256:5410638e4df39c54d957fc51ce03048acd8e6d60abc0f5107af51e5fb566eb3c"},
+    {file = "psutil-5.9.4-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:c1ca331af862803a42677c120aff8a814a804e09832f166f226bfd22b56feee8"},
+    {file = "psutil-5.9.4-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:68908971daf802203f3d37e78d3f8831b6d1014864d7a85937941bb35f09aefe"},
+    {file = "psutil-5.9.4-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:3ff89f9b835100a825b14c2808a106b6fdcc4b15483141482a12c725e7f78549"},
+    {file = "psutil-5.9.4-cp27-cp27m-win32.whl", hash = "sha256:852dd5d9f8a47169fe62fd4a971aa07859476c2ba22c2254d4a1baa4e10b95ad"},
+    {file = "psutil-5.9.4-cp27-cp27m-win_amd64.whl", hash = "sha256:9120cd39dca5c5e1c54b59a41d205023d436799b1c8c4d3ff71af18535728e94"},
+    {file = "psutil-5.9.4-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:6b92c532979bafc2df23ddc785ed116fced1f492ad90a6830cf24f4d1ea27d24"},
+    {file = "psutil-5.9.4-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:efeae04f9516907be44904cc7ce08defb6b665128992a56957abc9b61dca94b7"},
+    {file = "psutil-5.9.4-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:54d5b184728298f2ca8567bf83c422b706200bcbbfafdc06718264f9393cfeb7"},
+    {file = "psutil-5.9.4-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:16653106f3b59386ffe10e0bad3bb6299e169d5327d3f187614b1cb8f24cf2e1"},
+    {file = "psutil-5.9.4-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54c0d3d8e0078b7666984e11b12b88af2db11d11249a8ac8920dd5ef68a66e08"},
+    {file = "psutil-5.9.4-cp36-abi3-win32.whl", hash = "sha256:149555f59a69b33f056ba1c4eb22bb7bf24332ce631c44a319cec09f876aaeff"},
+    {file = "psutil-5.9.4-cp36-abi3-win_amd64.whl", hash = "sha256:fd8522436a6ada7b4aad6638662966de0d61d241cb821239b2ae7013d41a43d4"},
+    {file = "psutil-5.9.4-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:6001c809253a29599bc0dfd5179d9f8a5779f9dffea1da0f13c53ee568115e1e"},
+    {file = "psutil-5.9.4.tar.gz", hash = "sha256:3d7f9739eb435d4b1338944abe23f49584bde5395f27487d2ee25ad9a8774a62"},
 ]
 
 [package.extras]
@@ -2709,6 +2853,7 @@ test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"]
 name = "pyarrow"
 version = "8.0.0"
 description = "Python library for Apache Arrow"
+category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -2749,33 +2894,36 @@ numpy = ">=1.16.6"
 
 [[package]]
 name = "pyasn1"
-version = "0.5.0"
-description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)"
+version = "0.4.8"
+description = "ASN.1 types and codecs"
+category = "main"
 optional = false
-python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7"
+python-versions = "*"
 files = [
-    {file = "pyasn1-0.5.0-py2.py3-none-any.whl", hash = "sha256:87a2121042a1ac9358cabcaf1d07680ff97ee6404333bacca15f76aa8ad01a57"},
-    {file = "pyasn1-0.5.0.tar.gz", hash = "sha256:97b7290ca68e62a832558ec3976f15cbf911bf5d7c7039d8b861c2a0ece69fde"},
+    {file = "pyasn1-0.4.8-py2.py3-none-any.whl", hash = "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d"},
+    {file = "pyasn1-0.4.8.tar.gz", hash = "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba"},
 ]
 
 [[package]]
 name = "pyasn1-modules"
-version = "0.3.0"
-description = "A collection of ASN.1-based protocols modules"
+version = "0.2.8"
+description = "A collection of ASN.1-based protocols modules."
+category = "main"
 optional = false
-python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7"
+python-versions = "*"
 files = [
-    {file = "pyasn1_modules-0.3.0-py2.py3-none-any.whl", hash = "sha256:d3ccd6ed470d9ffbc716be08bd90efbd44d0734bc9303818f7336070984a162d"},
-    {file = "pyasn1_modules-0.3.0.tar.gz", hash = "sha256:5bd01446b736eb9d31512a30d46c1ac3395d676c6f3cafa4c03eb54b9925631c"},
+    {file = "pyasn1-modules-0.2.8.tar.gz", hash = "sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e"},
+    {file = "pyasn1_modules-0.2.8-py2.py3-none-any.whl", hash = "sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74"},
 ]
 
 [package.dependencies]
-pyasn1 = ">=0.4.6,<0.6.0"
+pyasn1 = ">=0.4.6,<0.5.0"
 
 [[package]]
 name = "pycparser"
 version = "2.21"
 description = "C parser in Python"
+category = "main"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
 files = [
@@ -2785,90 +2933,95 @@ files = [
 
 [[package]]
 name = "pycryptodome"
-version = "3.18.0"
+version = "3.17"
 description = "Cryptographic library for Python"
+category = "main"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
 files = [
-    {file = "pycryptodome-3.18.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:d1497a8cd4728db0e0da3c304856cb37c0c4e3d0b36fcbabcc1600f18504fc54"},
-    {file = "pycryptodome-3.18.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:928078c530da78ff08e10eb6cada6e0dff386bf3d9fa9871b4bbc9fbc1efe024"},
-    {file = "pycryptodome-3.18.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:157c9b5ba5e21b375f052ca78152dd309a09ed04703fd3721dce3ff8ecced148"},
-    {file = "pycryptodome-3.18.0-cp27-cp27m-manylinux2014_aarch64.whl", hash = "sha256:d20082bdac9218649f6abe0b885927be25a917e29ae0502eaf2b53f1233ce0c2"},
-    {file = "pycryptodome-3.18.0-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:e8ad74044e5f5d2456c11ed4cfd3e34b8d4898c0cb201c4038fe41458a82ea27"},
-    {file = "pycryptodome-3.18.0-cp27-cp27m-win32.whl", hash = "sha256:62a1e8847fabb5213ccde38915563140a5b338f0d0a0d363f996b51e4a6165cf"},
-    {file = "pycryptodome-3.18.0-cp27-cp27m-win_amd64.whl", hash = "sha256:16bfd98dbe472c263ed2821284118d899c76968db1a6665ade0c46805e6b29a4"},
-    {file = "pycryptodome-3.18.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:7a3d22c8ee63de22336679e021c7f2386f7fc465477d59675caa0e5706387944"},
-    {file = "pycryptodome-3.18.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:78d863476e6bad2a592645072cc489bb90320972115d8995bcfbee2f8b209918"},
-    {file = "pycryptodome-3.18.0-cp27-cp27mu-manylinux2014_aarch64.whl", hash = "sha256:b6a610f8bfe67eab980d6236fdc73bfcdae23c9ed5548192bb2d530e8a92780e"},
-    {file = "pycryptodome-3.18.0-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:422c89fd8df8a3bee09fb8d52aaa1e996120eafa565437392b781abec2a56e14"},
-    {file = "pycryptodome-3.18.0-cp35-abi3-macosx_10_9_universal2.whl", hash = "sha256:9ad6f09f670c466aac94a40798e0e8d1ef2aa04589c29faa5b9b97566611d1d1"},
-    {file = "pycryptodome-3.18.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:53aee6be8b9b6da25ccd9028caf17dcdce3604f2c7862f5167777b707fbfb6cb"},
-    {file = "pycryptodome-3.18.0-cp35-abi3-manylinux2014_aarch64.whl", hash = "sha256:10da29526a2a927c7d64b8f34592f461d92ae55fc97981aab5bbcde8cb465bb6"},
-    {file = "pycryptodome-3.18.0-cp35-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f21efb8438971aa16924790e1c3dba3a33164eb4000106a55baaed522c261acf"},
-    {file = "pycryptodome-3.18.0-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4944defabe2ace4803f99543445c27dd1edbe86d7d4edb87b256476a91e9ffa4"},
-    {file = "pycryptodome-3.18.0-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:51eae079ddb9c5f10376b4131be9589a6554f6fd84f7f655180937f611cd99a2"},
-    {file = "pycryptodome-3.18.0-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:83c75952dcf4a4cebaa850fa257d7a860644c70a7cd54262c237c9f2be26f76e"},
-    {file = "pycryptodome-3.18.0-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:957b221d062d5752716923d14e0926f47670e95fead9d240fa4d4862214b9b2f"},
-    {file = "pycryptodome-3.18.0-cp35-abi3-win32.whl", hash = "sha256:795bd1e4258a2c689c0b1f13ce9684fa0dd4c0e08680dcf597cf9516ed6bc0f3"},
-    {file = "pycryptodome-3.18.0-cp35-abi3-win_amd64.whl", hash = "sha256:b1d9701d10303eec8d0bd33fa54d44e67b8be74ab449052a8372f12a66f93fb9"},
-    {file = "pycryptodome-3.18.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:cb1be4d5af7f355e7d41d36d8eec156ef1382a88638e8032215c215b82a4b8ec"},
-    {file = "pycryptodome-3.18.0-pp27-pypy_73-win32.whl", hash = "sha256:fc0a73f4db1e31d4a6d71b672a48f3af458f548059aa05e83022d5f61aac9c08"},
-    {file = "pycryptodome-3.18.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f022a4fd2a5263a5c483a2bb165f9cb27f2be06f2f477113783efe3fe2ad887b"},
-    {file = "pycryptodome-3.18.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:363dd6f21f848301c2dcdeb3c8ae5f0dee2286a5e952a0f04954b82076f23825"},
-    {file = "pycryptodome-3.18.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12600268763e6fec3cefe4c2dcdf79bde08d0b6dc1813887e789e495cb9f3403"},
-    {file = "pycryptodome-3.18.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:4604816adebd4faf8810782f137f8426bf45fee97d8427fa8e1e49ea78a52e2c"},
-    {file = "pycryptodome-3.18.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:01489bbdf709d993f3058e2996f8f40fee3f0ea4d995002e5968965fa2fe89fb"},
-    {file = "pycryptodome-3.18.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3811e31e1ac3069988f7a1c9ee7331b942e605dfc0f27330a9ea5997e965efb2"},
-    {file = "pycryptodome-3.18.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f4b967bb11baea9128ec88c3d02f55a3e338361f5e4934f5240afcb667fdaec"},
-    {file = "pycryptodome-3.18.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:9c8eda4f260072f7dbe42f473906c659dcbadd5ae6159dfb49af4da1293ae380"},
-    {file = "pycryptodome-3.18.0.tar.gz", hash = "sha256:c9adee653fc882d98956e33ca2c1fb582e23a8af7ac82fee75bd6113c55a0413"},
+    {file = "pycryptodome-3.17-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:2c5631204ebcc7ae33d11c43037b2dafe25e2ab9c1de6448eb6502ac69c19a56"},
+    {file = "pycryptodome-3.17-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:04779cc588ad8f13c80a060b0b1c9d1c203d051d8a43879117fe6b8aaf1cd3fa"},
+    {file = "pycryptodome-3.17-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:f812d58c5af06d939b2baccdda614a3ffd80531a26e5faca2c9f8b1770b2b7af"},
+    {file = "pycryptodome-3.17-cp27-cp27m-manylinux2014_aarch64.whl", hash = "sha256:9453b4e21e752df8737fdffac619e93c9f0ec55ead9a45df782055eb95ef37d9"},
+    {file = "pycryptodome-3.17-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:121d61663267f73692e8bde5ec0d23c9146465a0d75cad75c34f75c752527b01"},
+    {file = "pycryptodome-3.17-cp27-cp27m-win32.whl", hash = "sha256:ba2d4fcb844c6ba5df4bbfee9352ad5352c5ae939ac450e06cdceff653280450"},
+    {file = "pycryptodome-3.17-cp27-cp27m-win_amd64.whl", hash = "sha256:87e2ca3aa557781447428c4b6c8c937f10ff215202ab40ece5c13a82555c10d6"},
+    {file = "pycryptodome-3.17-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:f44c0d28716d950135ff21505f2c764498eda9d8806b7c78764165848aa419bc"},
+    {file = "pycryptodome-3.17-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:5a790bc045003d89d42e3b9cb3cc938c8561a57a88aaa5691512e8540d1ae79c"},
+    {file = "pycryptodome-3.17-cp27-cp27mu-manylinux2014_aarch64.whl", hash = "sha256:d086d46774e27b280e4cece8ab3d87299cf0d39063f00f1e9290d096adc5662a"},
+    {file = "pycryptodome-3.17-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:5587803d5b66dfd99e7caa31ed91fba0fdee3661c5d93684028ad6653fce725f"},
+    {file = "pycryptodome-3.17-cp35-abi3-macosx_10_9_universal2.whl", hash = "sha256:e7debd9c439e7b84f53be3cf4ba8b75b3d0b6e6015212355d6daf44ac672e210"},
+    {file = "pycryptodome-3.17-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ca1ceb6303be1282148f04ac21cebeebdb4152590842159877778f9cf1634f09"},
+    {file = "pycryptodome-3.17-cp35-abi3-manylinux2014_aarch64.whl", hash = "sha256:dc22cc00f804485a3c2a7e2010d9f14a705555f67020eb083e833cabd5bd82e4"},
+    {file = "pycryptodome-3.17-cp35-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80ea8333b6a5f2d9e856ff2293dba2e3e661197f90bf0f4d5a82a0a6bc83a626"},
+    {file = "pycryptodome-3.17-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c133f6721fba313722a018392a91e3c69d3706ae723484841752559e71d69dc6"},
+    {file = "pycryptodome-3.17-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:333306eaea01fde50a73c4619e25631e56c4c61bd0fb0a2346479e67e3d3a820"},
+    {file = "pycryptodome-3.17-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:1a30f51b990994491cec2d7d237924e5b6bd0d445da9337d77de384ad7f254f9"},
+    {file = "pycryptodome-3.17-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:909e36a43fe4a8a3163e9c7fc103867825d14a2ecb852a63d3905250b308a4e5"},
+    {file = "pycryptodome-3.17-cp35-abi3-win32.whl", hash = "sha256:a3228728a3808bc9f18c1797ec1179a0efb5068c817b2ffcf6bcd012494dffb2"},
+    {file = "pycryptodome-3.17-cp35-abi3-win_amd64.whl", hash = "sha256:9ec565e89a6b400eca814f28d78a9ef3f15aea1df74d95b28b7720739b28f37f"},
+    {file = "pycryptodome-3.17-pp27-pypy_73-macosx_10_9_x86_64.whl", hash = "sha256:e1819b67bcf6ca48341e9b03c2e45b1c891fa8eb1a8458482d14c2805c9616f2"},
+    {file = "pycryptodome-3.17-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:f8e550caf52472ae9126953415e4fc554ab53049a5691c45b8816895c632e4d7"},
+    {file = "pycryptodome-3.17-pp27-pypy_73-win32.whl", hash = "sha256:afbcdb0eda20a0e1d44e3a1ad6d4ec3c959210f4b48cabc0e387a282f4c7deb8"},
+    {file = "pycryptodome-3.17-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a74f45aee8c5cc4d533e585e0e596e9f78521e1543a302870a27b0ae2106381e"},
+    {file = "pycryptodome-3.17-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38bbd6717eac084408b4094174c0805bdbaba1f57fc250fd0309ae5ec9ed7e09"},
+    {file = "pycryptodome-3.17-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f68d6c8ea2974a571cacb7014dbaada21063a0375318d88ac1f9300bc81e93c3"},
+    {file = "pycryptodome-3.17-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:8198f2b04c39d817b206ebe0db25a6653bb5f463c2319d6f6d9a80d012ac1e37"},
+    {file = "pycryptodome-3.17-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3a232474cd89d3f51e4295abe248a8b95d0332d153bf46444e415409070aae1e"},
+    {file = "pycryptodome-3.17-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4992ec965606054e8326e83db1c8654f0549cdb26fce1898dc1a20bc7684ec1c"},
+    {file = "pycryptodome-3.17-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53068e33c74f3b93a8158dacaa5d0f82d254a81b1002e0cd342be89fcb3433eb"},
+    {file = "pycryptodome-3.17-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:74794a2e2896cd0cf56fdc9db61ef755fa812b4a4900fa46c49045663a92b8d0"},
+    {file = "pycryptodome-3.17.tar.gz", hash = "sha256:bce2e2d8e82fcf972005652371a3e8731956a0c1fbb719cc897943b3695ad91b"},
 ]
 
 [[package]]
 name = "pycryptodomex"
-version = "3.18.0"
+version = "3.17"
 description = "Cryptographic library for Python"
-optional = false
+category = "main"
+optional = true
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
 files = [
-    {file = "pycryptodomex-3.18.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:160a39a708c36fa0b168ab79386dede588e62aec06eb505add870739329aecc6"},
-    {file = "pycryptodomex-3.18.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:c2953afebf282a444c51bf4effe751706b4d0d63d7ca2cc51db21f902aa5b84e"},
-    {file = "pycryptodomex-3.18.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:ba95abd563b0d1b88401658665a260852a8e6c647026ee6a0a65589287681df8"},
-    {file = "pycryptodomex-3.18.0-cp27-cp27m-manylinux2014_aarch64.whl", hash = "sha256:192306cf881fe3467dda0e174a4f47bb3a8bb24b90c9cdfbdc248eec5fc0578c"},
-    {file = "pycryptodomex-3.18.0-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:f9ab5ef0718f6a8716695dea16d83b671b22c45e9c0c78fd807c32c0192e54b5"},
-    {file = "pycryptodomex-3.18.0-cp27-cp27m-win32.whl", hash = "sha256:50308fcdbf8345e5ec224a5502b4215178bdb5e95456ead8ab1a69ffd94779cb"},
-    {file = "pycryptodomex-3.18.0-cp27-cp27m-win_amd64.whl", hash = "sha256:4d9379c684efea80fdab02a3eb0169372bca7db13f9332cb67483b8dc8b67c37"},
-    {file = "pycryptodomex-3.18.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:5594a125dae30d60e94f37797fc67ce3c744522de7992c7c360d02fdb34918f8"},
-    {file = "pycryptodomex-3.18.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:8ff129a5a0eb5ff16e45ca4fa70a6051da7f3de303c33b259063c19be0c43d35"},
-    {file = "pycryptodomex-3.18.0-cp27-cp27mu-manylinux2014_aarch64.whl", hash = "sha256:3d9314ac785a5b75d5aaf924c5f21d6ca7e8df442e5cf4f0fefad4f6e284d422"},
-    {file = "pycryptodomex-3.18.0-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:f237278836dda412a325e9340ba2e6a84cb0f56b9244781e5b61f10b3905de88"},
-    {file = "pycryptodomex-3.18.0-cp35-abi3-macosx_10_9_universal2.whl", hash = "sha256:ac614363a86cc53d8ba44b6c469831d1555947e69ab3276ae8d6edc219f570f7"},
-    {file = "pycryptodomex-3.18.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:302a8f37c224e7b5d72017d462a2be058e28f7be627bdd854066e16722d0fc0c"},
-    {file = "pycryptodomex-3.18.0-cp35-abi3-manylinux2014_aarch64.whl", hash = "sha256:6421d23d6a648e83ba2670a352bcd978542dad86829209f59d17a3f087f4afef"},
-    {file = "pycryptodomex-3.18.0-cp35-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d84e105787f5e5d36ec6a581ff37a1048d12e638688074b2a00bcf402f9aa1c2"},
-    {file = "pycryptodomex-3.18.0-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6875eb8666f68ddbd39097867325bd22771f595b4e2b0149739b5623c8bf899b"},
-    {file = "pycryptodomex-3.18.0-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:27072a494ce621cc7a9096bbf60ed66826bb94db24b49b7359509e7951033e74"},
-    {file = "pycryptodomex-3.18.0-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:1949e09ea49b09c36d11a951b16ff2a05a0ffe969dda1846e4686ee342fe8646"},
-    {file = "pycryptodomex-3.18.0-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:6ed3606832987018615f68e8ed716a7065c09a0fe94afd7c9ca1b6777f0ac6eb"},
-    {file = "pycryptodomex-3.18.0-cp35-abi3-win32.whl", hash = "sha256:d56c9ec41258fd3734db9f5e4d2faeabe48644ba9ca23b18e1839b3bdf093222"},
-    {file = "pycryptodomex-3.18.0-cp35-abi3-win_amd64.whl", hash = "sha256:e00a4bacb83a2627e8210cb353a2e31f04befc1155db2976e5e239dd66482278"},
-    {file = "pycryptodomex-3.18.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:2dc4eab20f4f04a2d00220fdc9258717b82d31913552e766d5f00282c031b70a"},
-    {file = "pycryptodomex-3.18.0-pp27-pypy_73-win32.whl", hash = "sha256:75672205148bdea34669173366df005dbd52be05115e919551ee97171083423d"},
-    {file = "pycryptodomex-3.18.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:bec6c80994d4e7a38312072f89458903b65ec99bed2d65aa4de96d997a53ea7a"},
-    {file = "pycryptodomex-3.18.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d35a8ffdc8b05e4b353ba281217c8437f02c57d7233363824e9d794cf753c419"},
-    {file = "pycryptodomex-3.18.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:76f0a46bee539dae4b3dfe37216f678769349576b0080fdbe431d19a02da42ff"},
-    {file = "pycryptodomex-3.18.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:71687eed47df7e965f6e0bf3cadef98f368d5221f0fb89d2132effe1a3e6a194"},
-    {file = "pycryptodomex-3.18.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:73d64b32d84cf48d9ec62106aa277dbe99ab5fbfd38c5100bc7bddd3beb569f7"},
-    {file = "pycryptodomex-3.18.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbdcce0a226d9205560a5936b05208c709b01d493ed8307792075dedfaaffa5f"},
-    {file = "pycryptodomex-3.18.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58fc0aceb9c961b9897facec9da24c6a94c5db04597ec832060f53d4d6a07196"},
-    {file = "pycryptodomex-3.18.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:215be2980a6b70704c10796dd7003eb4390e7be138ac6fb8344bf47e71a8d470"},
-    {file = "pycryptodomex-3.18.0.tar.gz", hash = "sha256:3e3ecb5fe979e7c1bb0027e518340acf7ee60415d79295e5251d13c68dde576e"},
+    {file = "pycryptodomex-3.17-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:12056c38e49d972f9c553a3d598425f8a1c1d35b2e4330f89d5ff1ffb70de041"},
+    {file = "pycryptodomex-3.17-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ab33c2d9f275e05e235dbca1063753b5346af4a5cac34a51fa0da0d4edfb21d7"},
+    {file = "pycryptodomex-3.17-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:caa937ff29d07a665dfcfd7a84f0d4207b2ebf483362fa9054041d67fdfacc20"},
+    {file = "pycryptodomex-3.17-cp27-cp27m-manylinux2014_aarch64.whl", hash = "sha256:db23d7341e21b273d2440ec6faf6c8b1ca95c8894da612e165be0b89a8688340"},
+    {file = "pycryptodomex-3.17-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:f854c8476512cebe6a8681cc4789e4fcff6019c17baa0fd72b459155dc605ab4"},
+    {file = "pycryptodomex-3.17-cp27-cp27m-win32.whl", hash = "sha256:a57e3257bacd719769110f1f70dd901c5b6955e9596ad403af11a3e6e7e3311c"},
+    {file = "pycryptodomex-3.17-cp27-cp27m-win_amd64.whl", hash = "sha256:d38ab9e53b1c09608ba2d9b8b888f1e75d6f66e2787e437adb1fecbffec6b112"},
+    {file = "pycryptodomex-3.17-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:3c2516b42437ae6c7a29ef3ddc73c8d4714e7b6df995b76be4695bbe4b3b5cd2"},
+    {file = "pycryptodomex-3.17-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:5c23482860302d0d9883404eaaa54b0615eefa5274f70529703e2c43cc571827"},
+    {file = "pycryptodomex-3.17-cp27-cp27mu-manylinux2014_aarch64.whl", hash = "sha256:7a8dc3ee7a99aae202a4db52de5a08aa4d01831eb403c4d21da04ec2f79810db"},
+    {file = "pycryptodomex-3.17-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:7cc28dd33f1f3662d6da28ead4f9891035f63f49d30267d3b41194c8778997c8"},
+    {file = "pycryptodomex-3.17-cp35-abi3-macosx_10_9_universal2.whl", hash = "sha256:2d4d395f109faba34067a08de36304e846c791808524614c731431ee048fe70a"},
+    {file = "pycryptodomex-3.17-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:55eed98b4150a744920597c81b3965b632038781bab8a08a12ea1d004213c600"},
+    {file = "pycryptodomex-3.17-cp35-abi3-manylinux2014_aarch64.whl", hash = "sha256:7fa0b52df90343fafe319257b31d909be1d2e8852277fb0376ba89d26d2921db"},
+    {file = "pycryptodomex-3.17-cp35-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78f0ddd4adc64baa39b416f3637aaf99f45acb0bcdc16706f0cc7ebfc6f10109"},
+    {file = "pycryptodomex-3.17-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a4fa037078e92c7cc49f6789a8bac3de06856740bb2038d05f2d9a2e4b165d59"},
+    {file = "pycryptodomex-3.17-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:88b0d5bb87eaf2a31e8a759302b89cf30c97f2f8ca7d83b8c9208abe8acb447a"},
+    {file = "pycryptodomex-3.17-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:6feedf4b0e36b395329b4186a805f60f900129cdf0170e120ecabbfcb763995d"},
+    {file = "pycryptodomex-3.17-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:7a6651a07f67c28b6e978d63aa3a3fccea0feefed9a8453af3f7421a758461b7"},
+    {file = "pycryptodomex-3.17-cp35-abi3-win32.whl", hash = "sha256:32e764322e902bbfac49ca1446604d2839381bbbdd5a57920c9daaf2e0b778df"},
+    {file = "pycryptodomex-3.17-cp35-abi3-win_amd64.whl", hash = "sha256:4b51e826f0a04d832eda0790bbd0665d9bfe73e5a4d8ea93b6a9b38beeebe935"},
+    {file = "pycryptodomex-3.17-pp27-pypy_73-macosx_10_9_x86_64.whl", hash = "sha256:d4cf0128da167562c49b0e034f09e9cedd733997354f2314837c2fa461c87bb1"},
+    {file = "pycryptodomex-3.17-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:c92537b596bd5bffb82f8964cabb9fef1bca8a28a9e0a69ffd3ec92a4a7ad41b"},
+    {file = "pycryptodomex-3.17-pp27-pypy_73-win32.whl", hash = "sha256:599bb4ae4bbd614ca05f49bd4e672b7a250b80b13ae1238f05fd0f09d87ed80a"},
+    {file = "pycryptodomex-3.17-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4c4674f4b040321055c596aac926d12f7f6859dfe98cd12f4d9453b43ab6adc8"},
+    {file = "pycryptodomex-3.17-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67a3648025e4ddb72d43addab764336ba2e670c8377dba5dd752e42285440d31"},
+    {file = "pycryptodomex-3.17-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40e8a11f578bd0851b02719c862d55d3ee18d906c8b68a9c09f8c564d6bb5b92"},
+    {file = "pycryptodomex-3.17-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:23d83b610bd97704f0cd3acc48d99b76a15c8c1540d8665c94d514a49905bad7"},
+    {file = "pycryptodomex-3.17-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:fd29d35ac80755e5c0a99d96b44fb9abbd7e871849581ea6a4cb826d24267537"},
+    {file = "pycryptodomex-3.17-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64b876d57cb894b31056ad8dd6a6ae1099b117ae07a3d39707221133490e5715"},
+    {file = "pycryptodomex-3.17-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee8bf4fdcad7d66beb744957db8717afc12d176e3fd9c5d106835133881a049b"},
+    {file = "pycryptodomex-3.17-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c84689c73358dfc23f9fdcff2cb9e7856e65e2ce3b5ed8ff630d4c9bdeb1867b"},
+    {file = "pycryptodomex-3.17.tar.gz", hash = "sha256:0af93aad8d62e810247beedef0261c148790c52f3cd33643791cc6396dd217c1"},
 ]
 
 [[package]]
 name = "pydot"
 version = "1.4.2"
 description = "Python interface to Graphviz's Dot"
+category = "dev"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
 files = [
@@ -2883,6 +3036,7 @@ pyparsing = ">=2.1.4"
 name = "pyjwt"
 version = "1.7.1"
 description = "JSON Web Token implementation in Python"
+category = "main"
 optional = false
 python-versions = "*"
 files = [
@@ -2902,6 +3056,7 @@ test = ["pytest (>=4.0.1,<5.0.0)", "pytest-cov (>=2.6.0,<3.0.0)", "pytest-runner
 name = "pylint"
 version = "2.10.2"
 description = "python code static checker"
+category = "dev"
 optional = false
 python-versions = "~=3.6"
 files = [
@@ -2921,7 +3076,8 @@ toml = ">=0.7.1"
 name = "pyopenssl"
 version = "21.0.0"
 description = "Python wrapper module around the OpenSSL library"
-optional = false
+category = "main"
+optional = true
 python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*"
 files = [
     {file = "pyOpenSSL-21.0.0-py2.py3-none-any.whl", hash = "sha256:8935bd4920ab9abfebb07c41a4f58296407ed77f04bd1a92914044b848ba1ed6"},
@@ -2940,6 +3096,7 @@ test = ["flaky", "pretend", "pytest (>=3.0.1)"]
 name = "pyparsing"
 version = "2.4.7"
 description = "Python parsing module"
+category = "main"
 optional = false
 python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
 files = [
@@ -2951,6 +3108,7 @@ files = [
 name = "pypdf2"
 version = "2.12.1"
 description = "A pure-python PDF library capable of splitting, merging, cropping, and transforming PDF files"
+category = "main"
 optional = false
 python-versions = ">=3.6"
 files = [
@@ -2958,6 +3116,9 @@ files = [
     {file = "pypdf2-2.12.1-py3-none-any.whl", hash = "sha256:41ff16ee122bad9790d57a4235281a838002d7f1cc8d631d91b6f65d709bd825"},
 ]
 
+[package.dependencies]
+typing_extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""}
+
 [package.extras]
 crypto = ["PyCryptodome"]
 dev = ["black", "flit", "pip-tools", "pre-commit (<2.18.0)", "pytest-cov", "wheel"]
@@ -2969,6 +3130,7 @@ image = ["Pillow"]
 name = "pyperclip"
 version = "1.8.2"
 description = "A cross-platform clipboard module for Python. (Only handles plain text for now.)"
+category = "main"
 optional = false
 python-versions = "*"
 files = [
@@ -2979,6 +3141,7 @@ files = [
 name = "pyreadline3"
 version = "3.4.1"
 description = "A python implementation of GNU readline."
+category = "main"
 optional = false
 python-versions = "*"
 files = [
@@ -2990,6 +3153,7 @@ files = [
 name = "pyrsistent"
 version = "0.19.3"
 description = "Persistent/Functional/Immutable data structures"
+category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -3026,6 +3190,7 @@ files = [
 name = "python-dateutil"
 version = "2.8.2"
 description = "Extensions to the standard Python datetime module"
+category = "main"
 optional = false
 python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
 files = [
@@ -3040,6 +3205,7 @@ six = ">=1.5"
 name = "python-magic"
 version = "0.4.27"
 description = "File type identification using libmagic"
+category = "main"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
 files = [
@@ -3051,6 +3217,7 @@ files = [
 name = "pytype"
 version = "2022.12.15"
 description = "Python type inferencer"
+category = "dev"
 optional = false
 python-versions = "<3.11,>=3.7"
 files = [
@@ -3085,6 +3252,7 @@ typing-extensions = ">=4.3.0"
 name = "pytz"
 version = "2023.3"
 description = "World timezone definitions, modern and historical"
+category = "main"
 optional = false
 python-versions = "*"
 files = [
@@ -3096,6 +3264,7 @@ files = [
 name = "pywin32"
 version = "227"
 description = "Python for Window Extensions"
+category = "main"
 optional = false
 python-versions = "*"
 files = [
@@ -3117,6 +3286,7 @@ files = [
 name = "pyyaml"
 version = "6.0"
 description = "YAML parser and emitter for Python"
+category = "main"
 optional = false
 python-versions = ">=3.6"
 files = [
@@ -3166,6 +3336,7 @@ files = [
 name = "pyyaml-env-tag"
 version = "0.1"
 description = "A custom YAML tag for referencing environment variables in YAML files. "
+category = "dev"
 optional = false
 python-versions = ">=3.6"
 files = [
@@ -3178,17 +3349,18 @@ pyyaml = "*"
 
 [[package]]
 name = "redis"
-version = "4.5.5"
+version = "4.5.4"
 description = "Python client for Redis database and key-value store"
-optional = false
+category = "main"
+optional = true
 python-versions = ">=3.7"
 files = [
-    {file = "redis-4.5.5-py3-none-any.whl", hash = "sha256:77929bc7f5dab9adf3acba2d3bb7d7658f1e0c2f1cafe7eb36434e751c471119"},
-    {file = "redis-4.5.5.tar.gz", hash = "sha256:dc87a0bdef6c8bfe1ef1e1c40be7034390c2ae02d92dcd0c7ca1729443899880"},
+    {file = "redis-4.5.4-py3-none-any.whl", hash = "sha256:2c19e6767c474f2e85167909061d525ed65bea9301c0770bb151e041b7ac89a2"},
+    {file = "redis-4.5.4.tar.gz", hash = "sha256:73ec35da4da267d6847e47f68730fdd5f62e2ca69e3ef5885c6a78a9374c3893"},
 ]
 
 [package.dependencies]
-async-timeout = {version = ">=4.0.2", markers = "python_full_version <= \"3.11.2\""}
+async-timeout = {version = ">=4.0.2", markers = "python_version <= \"3.11.2\""}
 
 [package.extras]
 hiredis = ["hiredis (>=1.0.0)"]
@@ -3198,6 +3370,7 @@ ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==20.0.1)", "requests (>=2.26.0)"
 name = "requests"
 version = "2.25.1"
 description = "Python HTTP for Humans."
+category = "main"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
 files = [
@@ -3219,6 +3392,7 @@ socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"]
 name = "requests-oauthlib"
 version = "1.3.1"
 description = "OAuthlib authentication support for Requests."
+category = "main"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
 files = [
@@ -3237,6 +3411,7 @@ rsa = ["oauthlib[signedtoken] (>=3.0.0)"]
 name = "rsa"
 version = "4.9"
 description = "Pure-Python RSA implementation"
+category = "main"
 optional = false
 python-versions = ">=3.6,<4"
 files = [
@@ -3249,13 +3424,14 @@ pyasn1 = ">=0.1.3"
 
 [[package]]
 name = "s3transfer"
-version = "0.6.1"
+version = "0.6.0"
 description = "An Amazon S3 Transfer Manager"
+category = "main"
 optional = false
 python-versions = ">= 3.7"
 files = [
-    {file = "s3transfer-0.6.1-py3-none-any.whl", hash = "sha256:3c0da2d074bf35d6870ef157158641178a4204a6e689e82546083e31e0311346"},
-    {file = "s3transfer-0.6.1.tar.gz", hash = "sha256:640bb492711f4c0c0905e1f62b6aaeb771881935ad27884852411f8e9cacbca9"},
+    {file = "s3transfer-0.6.0-py3-none-any.whl", hash = "sha256:06176b74f3a15f61f1b4f25a1fc29a4429040b7647133a463da8fa5bd28d5ecd"},
+    {file = "s3transfer-0.6.0.tar.gz", hash = "sha256:2ed07d3866f523cc561bf4a00fc5535827981b117dd7876f036b0c1aca42c947"},
 ]
 
 [package.dependencies]
@@ -3266,24 +3442,26 @@ crt = ["botocore[crt] (>=1.20.29,<2.0a.0)"]
 
 [[package]]
 name = "setuptools"
-version = "67.8.0"
+version = "67.6.1"
 description = "Easily download, build, install, upgrade, and uninstall Python packages"
+category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
-    {file = "setuptools-67.8.0-py3-none-any.whl", hash = "sha256:5df61bf30bb10c6f756eb19e7c9f3b473051f48db77fddbe06ff2ca307df9a6f"},
-    {file = "setuptools-67.8.0.tar.gz", hash = "sha256:62642358adc77ffa87233bc4d2354c4b2682d214048f500964dbe760ccedf102"},
+    {file = "setuptools-67.6.1-py3-none-any.whl", hash = "sha256:e728ca814a823bf7bf60162daf9db95b93d532948c4c0bea762ce62f60189078"},
+    {file = "setuptools-67.6.1.tar.gz", hash = "sha256:257de92a9d50a60b8e22abfcbb771571fde0dbf3ec234463212027a4eeecbe9a"},
 ]
 
 [package.extras]
 docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
-testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
+testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
 testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
 
 [[package]]
 name = "six"
 version = "1.16.0"
 description = "Python 2 and 3 compatibility utilities"
+category = "main"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
 files = [
@@ -3293,35 +3471,33 @@ files = [
 
 [[package]]
 name = "soupsieve"
-version = "2.4.1"
+version = "2.4"
 description = "A modern CSS selector implementation for Beautiful Soup."
+category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
-    {file = "soupsieve-2.4.1-py3-none-any.whl", hash = "sha256:1c1bfee6819544a3447586c889157365a27e10d88cde3ad3da0cf0ddf646feb8"},
-    {file = "soupsieve-2.4.1.tar.gz", hash = "sha256:89d12b2d5dfcd2c9e8c22326da9d9aa9cb3dfab0a83a024f05704076ee8d35ea"},
+    {file = "soupsieve-2.4-py3-none-any.whl", hash = "sha256:49e5368c2cda80ee7e84da9dbe3e110b70a4575f196efb74e51b94549d921955"},
+    {file = "soupsieve-2.4.tar.gz", hash = "sha256:e28dba9ca6c7c00173e34e4ba57448f0688bb681b7c5e8bf4971daafc093d69a"},
 ]
 
 [[package]]
 name = "sqlparse"
-version = "0.4.4"
+version = "0.4.3"
 description = "A non-validating SQL parser."
+category = "dev"
 optional = false
 python-versions = ">=3.5"
 files = [
-    {file = "sqlparse-0.4.4-py3-none-any.whl", hash = "sha256:5430a4fe2ac7d0f93e66f1efc6e1338a41884b7ddf2a350cedd20ccc4d9d28f3"},
-    {file = "sqlparse-0.4.4.tar.gz", hash = "sha256:d446183e84b8349fa3061f0fe7f06ca94ba65b426946ffebe6e3e8295332420c"},
+    {file = "sqlparse-0.4.3-py3-none-any.whl", hash = "sha256:0323c0ec29cd52bceabc1b4d9d579e311f3e4961b98d174201d5622a23b85e34"},
+    {file = "sqlparse-0.4.3.tar.gz", hash = "sha256:69ca804846bb114d2ec380e4360a8a340db83f0ccf3afceeb1404df028f57268"},
 ]
 
-[package.extras]
-dev = ["build", "flake8"]
-doc = ["sphinx"]
-test = ["pytest", "pytest-cov"]
-
 [[package]]
 name = "sshpubkeys"
 version = "3.3.1"
 description = "SSH public key parser"
+category = "main"
 optional = false
 python-versions = ">=3"
 files = [
@@ -3340,6 +3516,7 @@ dev = ["twine", "wheel", "yapf"]
 name = "tabulate"
 version = "0.9.0"
 description = "Pretty-print tabular data"
+category = "dev"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -3354,6 +3531,7 @@ widechars = ["wcwidth"]
 name = "timesketch-api-client"
 version = "20230208"
 description = "Timesketch API client"
+category = "main"
 optional = false
 python-versions = "*"
 files = [
@@ -3374,6 +3552,7 @@ requests = "*"
 name = "timesketch-import-client"
 version = "20210602"
 description = "Timesketch Import Client"
+category = "main"
 optional = false
 python-versions = "*"
 files = [
@@ -3390,6 +3569,7 @@ xlrd = "*"
 name = "toml"
 version = "0.10.2"
 description = "Python Library for Tom's Obvious, Minimal Language"
+category = "dev"
 optional = false
 python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
 files = [
@@ -3401,6 +3581,7 @@ files = [
 name = "tomli"
 version = "2.0.1"
 description = "A lil' TOML parser"
+category = "dev"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -3412,6 +3593,7 @@ files = [
 name = "toolz"
 version = "0.12.0"
 description = "List processing tools and functional utilities"
+category = "main"
 optional = false
 python-versions = ">=3.5"
 files = [
@@ -3421,12 +3603,13 @@ files = [
 
 [[package]]
 name = "turbinia"
-version = "20220216"
+version = "20230315"
 description = "Automation and Scaling of Digital Forensics Tools"
-optional = false
+category = "main"
+optional = true
 python-versions = "*"
 files = [
-    {file = "turbinia-20220216.tar.gz", hash = "sha256:1c0509ccbfe051cb5cbaadcf4f93bb529ab0981f5dafd8db6c54fa18ef1ae15e"},
+    {file = "turbinia-20230315.tar.gz", hash = "sha256:03ec2fae32c5a3bf121ba75634989ee49f8cf0f9b427feb4eb7baaa4081521d1"},
 ]
 
 [package.dependencies]
@@ -3464,10 +3647,42 @@ dev = ["celery (>=4.1,<=4.4.7)", "coverage", "mock", "nose", "yapf"]
 local = ["celery (>=4.1,<5.0)", "kombu (>=4.1,<5.0)", "redis (>=3.0,<4.0)"]
 worker = ["docker-explorer (>=20191104)", "impacket", "plaso (>=20200430)", "pyhindsight (>=20200607)"]
 
+[[package]]
+name = "turbinia-api-lib"
+version = "1.0.1"
+description = "Turbinia API Server"
+category = "main"
+optional = false
+python-versions = ">=3.6"
+files = [
+    {file = "turbinia-api-lib-1.0.1.tar.gz", hash = "sha256:f652638c1ed34e873a440fd4275ec4ea07aadde1f12b7b63ecc587a91dd8d585"},
+    {file = "turbinia_api_lib-1.0.1-py3-none-any.whl", hash = "sha256:c9e2fa3226bc489bbc714c7831cda8d7ee93c8c2e63db3429e7ab8ac40ab63a4"},
+]
+
+[package.dependencies]
+python-dateutil = "*"
+urllib3 = ">=1.25.3"
+
+[[package]]
+name = "turbinia-client"
+version = "1.0.2"
+description = "Turbinia API Client command-line tool."
+category = "main"
+optional = false
+python-versions = ">=3.6"
+files = [
+    {file = "turbinia-client-1.0.2.tar.gz", hash = "sha256:c71b2896960325f97c90c501a45d4c06f241a30704ddd4a44ceca091eb7b5e89"},
+]
+
+[package.dependencies]
+click = "*"
+turbinia-api-lib = "*"
+
 [[package]]
 name = "types-filelock"
 version = "3.2.7"
 description = "Typing stubs for filelock"
+category = "dev"
 optional = false
 python-versions = "*"
 files = [
@@ -3479,6 +3694,7 @@ files = [
 name = "types-protobuf"
 version = "3.20.4.6"
 description = "Typing stubs for protobuf"
+category = "dev"
 optional = false
 python-versions = "*"
 files = [
@@ -3488,56 +3704,49 @@ files = [
 
 [[package]]
 name = "types-pyyaml"
-version = "6.0.12.10"
+version = "6.0.12.9"
 description = "Typing stubs for PyYAML"
+category = "dev"
 optional = false
 python-versions = "*"
 files = [
-    {file = "types-PyYAML-6.0.12.10.tar.gz", hash = "sha256:ebab3d0700b946553724ae6ca636ea932c1b0868701d4af121630e78d695fc97"},
-    {file = "types_PyYAML-6.0.12.10-py3-none-any.whl", hash = "sha256:662fa444963eff9b68120d70cda1af5a5f2aa57900003c2006d7626450eaae5f"},
+    {file = "types-PyYAML-6.0.12.9.tar.gz", hash = "sha256:c51b1bd6d99ddf0aa2884a7a328810ebf70a4262c292195d3f4f9a0005f9eeb6"},
+    {file = "types_PyYAML-6.0.12.9-py3-none-any.whl", hash = "sha256:5aed5aa66bd2d2e158f75dda22b059570ede988559f030cf294871d3b647e3e8"},
 ]
 
 [[package]]
 name = "typing-extensions"
-version = "4.6.3"
+version = "4.5.0"
 description = "Backported and Experimental Type Hints for Python 3.7+"
+category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
-    {file = "typing_extensions-4.6.3-py3-none-any.whl", hash = "sha256:88a4153d8505aabbb4e13aacb7c486c2b4a33ca3b3f807914a9b4c844c471c26"},
-    {file = "typing_extensions-4.6.3.tar.gz", hash = "sha256:d91d5919357fe7f681a9f2b5b4cb2a5f1ef0a1e9f59c4d8ff0d3491e05c0ffd5"},
+    {file = "typing_extensions-4.5.0-py3-none-any.whl", hash = "sha256:fb33085c39dd998ac16d1431ebc293a8b3eedd00fd4a32de0ff79002c19511b4"},
+    {file = "typing_extensions-4.5.0.tar.gz", hash = "sha256:5cb5f4a79139d699607b3ef622a1dedafa84e115ab0024e0d9c044a9479ca7cb"},
 ]
 
 [[package]]
 name = "typing-inspect"
-version = "0.9.0"
+version = "0.8.0"
 description = "Runtime inspection utilities for typing module."
+category = "main"
 optional = false
 python-versions = "*"
 files = [
-    {file = "typing_inspect-0.9.0-py3-none-any.whl", hash = "sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f"},
-    {file = "typing_inspect-0.9.0.tar.gz", hash = "sha256:b23fc42ff6f6ef6954e4852c1fb512cdd18dbea03134f91f856a95ccc9461f78"},
+    {file = "typing_inspect-0.8.0-py3-none-any.whl", hash = "sha256:5fbf9c1e65d4fa01e701fe12a5bca6c6e08a4ffd5bc60bfac028253a447c5188"},
+    {file = "typing_inspect-0.8.0.tar.gz", hash = "sha256:8b1ff0c400943b6145df8119c41c244ca8207f1f10c9c057aeed1560e4806e3d"},
 ]
 
 [package.dependencies]
 mypy-extensions = ">=0.3.0"
 typing-extensions = ">=3.7.4"
 
-[[package]]
-name = "tzdata"
-version = "2023.3"
-description = "Provider of IANA time zone data"
-optional = false
-python-versions = ">=2"
-files = [
-    {file = "tzdata-2023.3-py2.py3-none-any.whl", hash = "sha256:7e65763eef3120314099b6939b5546db7adce1e7d6f2e179e3df563c70511eda"},
-    {file = "tzdata-2023.3.tar.gz", hash = "sha256:11ef1e08e54acb0d4f95bdb1be05da659673de4acbd21bf9c69e94cc5e907a3a"},
-]
-
 [[package]]
 name = "uritemplate"
 version = "4.1.1"
 description = "Implementation of RFC 6570 URI Templates"
+category = "main"
 optional = false
 python-versions = ">=3.6"
 files = [
@@ -3547,13 +3756,14 @@ files = [
 
 [[package]]
 name = "urllib3"
-version = "1.26.16"
+version = "1.26.15"
 description = "HTTP library with thread-safe connection pooling, file post, and more."
+category = "main"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
 files = [
-    {file = "urllib3-1.26.16-py2.py3-none-any.whl", hash = "sha256:8d36afa7616d8ab714608411b4a3b13e58f463aee519024578e062e141dce20f"},
-    {file = "urllib3-1.26.16.tar.gz", hash = "sha256:8f135f6502756bde6b2a9b28989df5fbe87c9970cecaa69041edcce7f0589b14"},
+    {file = "urllib3-1.26.15-py2.py3-none-any.whl", hash = "sha256:aa751d169e23c7479ce47a0cb0da579e3ede798f994f5816a74e4f4500dcea42"},
+    {file = "urllib3-1.26.15.tar.gz", hash = "sha256:8a388717b9476f934a21484e8c8e61875ab60644d29b9b39e11e4b9dc1c6b305"},
 ]
 
 [package.dependencies]
@@ -3572,7 +3782,8 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
 name = "urllib3-secure-extra"
 version = "0.1.0"
 description = "Marker library to detect whether urllib3 was installed with the deprecated [secure] extra"
-optional = false
+category = "main"
+optional = true
 python-versions = "*"
 files = [
     {file = "urllib3-secure-extra-0.1.0.tar.gz", hash = "sha256:ee9409cbfeb4b8609047be4c32fb4317870c602767e53fd8a41005ebe6a41dff"},
@@ -3583,7 +3794,8 @@ files = [
 name = "vine"
 version = "5.0.0"
 description = "Promises, promises, promises."
-optional = false
+category = "main"
+optional = true
 python-versions = ">=3.6"
 files = [
     {file = "vine-5.0.0-py2.py3-none-any.whl", hash = "sha256:4c9dceab6f76ed92105027c49c823800dd33cacce13bdedc5b914e3514b7fb30"},
@@ -3594,6 +3806,7 @@ files = [
 name = "vt-py"
 version = "0.14.0"
 description = "The official Python client library for VirusTotal"
+category = "main"
 optional = false
 python-versions = ">=3.6.0"
 files = [
@@ -3611,6 +3824,7 @@ test = ["pytest", "pytest-asyncio", "pytest-httpserver"]
 name = "watchdog"
 version = "3.0.0"
 description = "Filesystem events monitoring"
+category = "dev"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -3650,6 +3864,7 @@ watchmedo = ["PyYAML (>=3.10)"]
 name = "wcwidth"
 version = "0.2.6"
 description = "Measures the displayed width of unicode strings in a terminal"
+category = "main"
 optional = false
 python-versions = "*"
 files = [
@@ -3659,13 +3874,14 @@ files = [
 
 [[package]]
 name = "websocket-client"
-version = "1.5.3"
+version = "1.5.1"
 description = "WebSocket client for Python with low level API options"
+category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
-    {file = "websocket-client-1.5.3.tar.gz", hash = "sha256:b96f3bce3e54e3486ebe6504bc22bd4c140392bd2eb71764db29be8f2639aa65"},
-    {file = "websocket_client-1.5.3-py3-none-any.whl", hash = "sha256:3566f8467cd350874c4913816355642a4942f6c1ed1e9406e3d42fae6d6c072a"},
+    {file = "websocket-client-1.5.1.tar.gz", hash = "sha256:3f09e6d8230892547132177f575a4e3e73cfdf06526e20cc02aa1c3b47184d40"},
+    {file = "websocket_client-1.5.1-py3-none-any.whl", hash = "sha256:cdf5877568b7e83aa7cf2244ab56a3213de587bbe0ce9d8b9600fc77b455d89e"},
 ]
 
 [package.extras]
@@ -3677,6 +3893,7 @@ test = ["websockets"]
 name = "werkzeug"
 version = "1.0.1"
 description = "The comprehensive WSGI web application library."
+category = "main"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
 files = [
@@ -3692,6 +3909,7 @@ watchdog = ["watchdog"]
 name = "wrapt"
 version = "1.12.1"
 description = "Module for decorators, wrappers and monkey patching."
+category = "dev"
 optional = false
 python-versions = "*"
 files = [
@@ -3702,6 +3920,7 @@ files = [
 name = "xlrd"
 version = "2.0.1"
 description = "Library for developers to extract data from Microsoft Excel (tm) .xls spreadsheet files"
+category = "main"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
 files = [
@@ -3718,6 +3937,7 @@ test = ["pytest", "pytest-cov"]
 name = "yapf"
 version = "0.32.0"
 description = "A formatter for Python code."
+category = "dev"
 optional = false
 python-versions = "*"
 files = [
@@ -3727,92 +3947,112 @@ files = [
 
 [[package]]
 name = "yarl"
-version = "1.9.2"
+version = "1.8.2"
 description = "Yet another URL library"
+category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
-    {file = "yarl-1.9.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8c2ad583743d16ddbdf6bb14b5cd76bf43b0d0006e918809d5d4ddf7bde8dd82"},
-    {file = "yarl-1.9.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:82aa6264b36c50acfb2424ad5ca537a2060ab6de158a5bd2a72a032cc75b9eb8"},
-    {file = "yarl-1.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c0c77533b5ed4bcc38e943178ccae29b9bcf48ffd1063f5821192f23a1bd27b9"},
-    {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee4afac41415d52d53a9833ebae7e32b344be72835bbb589018c9e938045a560"},
-    {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9bf345c3a4f5ba7f766430f97f9cc1320786f19584acc7086491f45524a551ac"},
-    {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2a96c19c52ff442a808c105901d0bdfd2e28575b3d5f82e2f5fd67e20dc5f4ea"},
-    {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:891c0e3ec5ec881541f6c5113d8df0315ce5440e244a716b95f2525b7b9f3608"},
-    {file = "yarl-1.9.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c3a53ba34a636a256d767c086ceb111358876e1fb6b50dfc4d3f4951d40133d5"},
-    {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:566185e8ebc0898b11f8026447eacd02e46226716229cea8db37496c8cdd26e0"},
-    {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:2b0738fb871812722a0ac2154be1f049c6223b9f6f22eec352996b69775b36d4"},
-    {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:32f1d071b3f362c80f1a7d322bfd7b2d11e33d2adf395cc1dd4df36c9c243095"},
-    {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:e9fdc7ac0d42bc3ea78818557fab03af6181e076a2944f43c38684b4b6bed8e3"},
-    {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:56ff08ab5df8429901ebdc5d15941b59f6253393cb5da07b4170beefcf1b2528"},
-    {file = "yarl-1.9.2-cp310-cp310-win32.whl", hash = "sha256:8ea48e0a2f931064469bdabca50c2f578b565fc446f302a79ba6cc0ee7f384d3"},
-    {file = "yarl-1.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:50f33040f3836e912ed16d212f6cc1efb3231a8a60526a407aeb66c1c1956dde"},
-    {file = "yarl-1.9.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:646d663eb2232d7909e6601f1a9107e66f9791f290a1b3dc7057818fe44fc2b6"},
-    {file = "yarl-1.9.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aff634b15beff8902d1f918012fc2a42e0dbae6f469fce134c8a0dc51ca423bb"},
-    {file = "yarl-1.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a83503934c6273806aed765035716216cc9ab4e0364f7f066227e1aaea90b8d0"},
-    {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b25322201585c69abc7b0e89e72790469f7dad90d26754717f3310bfe30331c2"},
-    {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:22a94666751778629f1ec4280b08eb11815783c63f52092a5953faf73be24191"},
-    {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ec53a0ea2a80c5cd1ab397925f94bff59222aa3cf9c6da938ce05c9ec20428d"},
-    {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:159d81f22d7a43e6eabc36d7194cb53f2f15f498dbbfa8edc8a3239350f59fe7"},
-    {file = "yarl-1.9.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:832b7e711027c114d79dffb92576acd1bd2decc467dec60e1cac96912602d0e6"},
-    {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:95d2ecefbcf4e744ea952d073c6922e72ee650ffc79028eb1e320e732898d7e8"},
-    {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d4e2c6d555e77b37288eaf45b8f60f0737c9efa3452c6c44626a5455aeb250b9"},
-    {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:783185c75c12a017cc345015ea359cc801c3b29a2966c2655cd12b233bf5a2be"},
-    {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:b8cc1863402472f16c600e3e93d542b7e7542a540f95c30afd472e8e549fc3f7"},
-    {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:822b30a0f22e588b32d3120f6d41e4ed021806418b4c9f0bc3048b8c8cb3f92a"},
-    {file = "yarl-1.9.2-cp311-cp311-win32.whl", hash = "sha256:a60347f234c2212a9f0361955007fcf4033a75bf600a33c88a0a8e91af77c0e8"},
-    {file = "yarl-1.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:be6b3fdec5c62f2a67cb3f8c6dbf56bbf3f61c0f046f84645cd1ca73532ea051"},
-    {file = "yarl-1.9.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:38a3928ae37558bc1b559f67410df446d1fbfa87318b124bf5032c31e3447b74"},
-    {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac9bb4c5ce3975aeac288cfcb5061ce60e0d14d92209e780c93954076c7c4367"},
-    {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3da8a678ca8b96c8606bbb8bfacd99a12ad5dd288bc6f7979baddd62f71c63ef"},
-    {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:13414591ff516e04fcdee8dc051c13fd3db13b673c7a4cb1350e6b2ad9639ad3"},
-    {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf74d08542c3a9ea97bb8f343d4fcbd4d8f91bba5ec9d5d7f792dbe727f88938"},
-    {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e7221580dc1db478464cfeef9b03b95c5852cc22894e418562997df0d074ccc"},
-    {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:494053246b119b041960ddcd20fd76224149cfea8ed8777b687358727911dd33"},
-    {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:52a25809fcbecfc63ac9ba0c0fb586f90837f5425edfd1ec9f3372b119585e45"},
-    {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:e65610c5792870d45d7b68c677681376fcf9cc1c289f23e8e8b39c1485384185"},
-    {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:1b1bba902cba32cdec51fca038fd53f8beee88b77efc373968d1ed021024cc04"},
-    {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:662e6016409828ee910f5d9602a2729a8a57d74b163c89a837de3fea050c7582"},
-    {file = "yarl-1.9.2-cp37-cp37m-win32.whl", hash = "sha256:f364d3480bffd3aa566e886587eaca7c8c04d74f6e8933f3f2c996b7f09bee1b"},
-    {file = "yarl-1.9.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6a5883464143ab3ae9ba68daae8e7c5c95b969462bbe42e2464d60e7e2698368"},
-    {file = "yarl-1.9.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5610f80cf43b6202e2c33ba3ec2ee0a2884f8f423c8f4f62906731d876ef4fac"},
-    {file = "yarl-1.9.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b9a4e67ad7b646cd6f0938c7ebfd60e481b7410f574c560e455e938d2da8e0f4"},
-    {file = "yarl-1.9.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:83fcc480d7549ccebe9415d96d9263e2d4226798c37ebd18c930fce43dfb9574"},
-    {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fcd436ea16fee7d4207c045b1e340020e58a2597301cfbcfdbe5abd2356c2fb"},
-    {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84e0b1599334b1e1478db01b756e55937d4614f8654311eb26012091be109d59"},
-    {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3458a24e4ea3fd8930e934c129b676c27452e4ebda80fbe47b56d8c6c7a63a9e"},
-    {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:838162460b3a08987546e881a2bfa573960bb559dfa739e7800ceeec92e64417"},
-    {file = "yarl-1.9.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f4e2d08f07a3d7d3e12549052eb5ad3eab1c349c53ac51c209a0e5991bbada78"},
-    {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:de119f56f3c5f0e2fb4dee508531a32b069a5f2c6e827b272d1e0ff5ac040333"},
-    {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:149ddea5abf329752ea5051b61bd6c1d979e13fbf122d3a1f9f0c8be6cb6f63c"},
-    {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:674ca19cbee4a82c9f54e0d1eee28116e63bc6fd1e96c43031d11cbab8b2afd5"},
-    {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:9b3152f2f5677b997ae6c804b73da05a39daa6a9e85a512e0e6823d81cdad7cc"},
-    {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5415d5a4b080dc9612b1b63cba008db84e908b95848369aa1da3686ae27b6d2b"},
-    {file = "yarl-1.9.2-cp38-cp38-win32.whl", hash = "sha256:f7a3d8146575e08c29ed1cd287068e6d02f1c7bdff8970db96683b9591b86ee7"},
-    {file = "yarl-1.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:63c48f6cef34e6319a74c727376e95626f84ea091f92c0250a98e53e62c77c72"},
-    {file = "yarl-1.9.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:75df5ef94c3fdc393c6b19d80e6ef1ecc9ae2f4263c09cacb178d871c02a5ba9"},
-    {file = "yarl-1.9.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c027a6e96ef77d401d8d5a5c8d6bc478e8042f1e448272e8d9752cb0aff8b5c8"},
-    {file = "yarl-1.9.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3b078dbe227f79be488ffcfc7a9edb3409d018e0952cf13f15fd6512847f3f7"},
-    {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59723a029760079b7d991a401386390c4be5bfec1e7dd83e25a6a0881859e716"},
-    {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b03917871bf859a81ccb180c9a2e6c1e04d2f6a51d953e6a5cdd70c93d4e5a2a"},
-    {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c1012fa63eb6c032f3ce5d2171c267992ae0c00b9e164efe4d73db818465fac3"},
-    {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a74dcbfe780e62f4b5a062714576f16c2f3493a0394e555ab141bf0d746bb955"},
-    {file = "yarl-1.9.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c56986609b057b4839968ba901944af91b8e92f1725d1a2d77cbac6972b9ed1"},
-    {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2c315df3293cd521033533d242d15eab26583360b58f7ee5d9565f15fee1bef4"},
-    {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:b7232f8dfbd225d57340e441d8caf8652a6acd06b389ea2d3222b8bc89cbfca6"},
-    {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:53338749febd28935d55b41bf0bcc79d634881195a39f6b2f767870b72514caf"},
-    {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:066c163aec9d3d073dc9ffe5dd3ad05069bcb03fcaab8d221290ba99f9f69ee3"},
-    {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8288d7cd28f8119b07dd49b7230d6b4562f9b61ee9a4ab02221060d21136be80"},
-    {file = "yarl-1.9.2-cp39-cp39-win32.whl", hash = "sha256:b124e2a6d223b65ba8768d5706d103280914d61f5cae3afbc50fc3dfcc016623"},
-    {file = "yarl-1.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:61016e7d582bc46a5378ffdd02cd0314fb8ba52f40f9cf4d9a5e7dbef88dee18"},
-    {file = "yarl-1.9.2.tar.gz", hash = "sha256:04ab9d4b9f587c06d801c2abfe9317b77cdf996c65a90d5e84ecc45010823571"},
+    {file = "yarl-1.8.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:bb81f753c815f6b8e2ddd2eef3c855cf7da193b82396ac013c661aaa6cc6b0a5"},
+    {file = "yarl-1.8.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:47d49ac96156f0928f002e2424299b2c91d9db73e08c4cd6742923a086f1c863"},
+    {file = "yarl-1.8.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3fc056e35fa6fba63248d93ff6e672c096f95f7836938241ebc8260e062832fe"},
+    {file = "yarl-1.8.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58a3c13d1c3005dbbac5c9f0d3210b60220a65a999b1833aa46bd6677c69b08e"},
+    {file = "yarl-1.8.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:10b08293cda921157f1e7c2790999d903b3fd28cd5c208cf8826b3b508026996"},
+    {file = "yarl-1.8.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:de986979bbd87272fe557e0a8fcb66fd40ae2ddfe28a8b1ce4eae22681728fef"},
+    {file = "yarl-1.8.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c4fcfa71e2c6a3cb568cf81aadc12768b9995323186a10827beccf5fa23d4f8"},
+    {file = "yarl-1.8.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae4d7ff1049f36accde9e1ef7301912a751e5bae0a9d142459646114c70ecba6"},
+    {file = "yarl-1.8.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:bf071f797aec5b96abfc735ab97da9fd8f8768b43ce2abd85356a3127909d146"},
+    {file = "yarl-1.8.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:74dece2bfc60f0f70907c34b857ee98f2c6dd0f75185db133770cd67300d505f"},
+    {file = "yarl-1.8.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:df60a94d332158b444301c7f569659c926168e4d4aad2cfbf4bce0e8fb8be826"},
+    {file = "yarl-1.8.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:63243b21c6e28ec2375f932a10ce7eda65139b5b854c0f6b82ed945ba526bff3"},
+    {file = "yarl-1.8.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cfa2bbca929aa742b5084fd4663dd4b87c191c844326fcb21c3afd2d11497f80"},
+    {file = "yarl-1.8.2-cp310-cp310-win32.whl", hash = "sha256:b05df9ea7496df11b710081bd90ecc3a3db6adb4fee36f6a411e7bc91a18aa42"},
+    {file = "yarl-1.8.2-cp310-cp310-win_amd64.whl", hash = "sha256:24ad1d10c9db1953291f56b5fe76203977f1ed05f82d09ec97acb623a7976574"},
+    {file = "yarl-1.8.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2a1fca9588f360036242f379bfea2b8b44cae2721859b1c56d033adfd5893634"},
+    {file = "yarl-1.8.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f37db05c6051eff17bc832914fe46869f8849de5b92dc4a3466cd63095d23dfd"},
+    {file = "yarl-1.8.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:77e913b846a6b9c5f767b14dc1e759e5aff05502fe73079f6f4176359d832581"},
+    {file = "yarl-1.8.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0978f29222e649c351b173da2b9b4665ad1feb8d1daa9d971eb90df08702668a"},
+    {file = "yarl-1.8.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:388a45dc77198b2460eac0aca1efd6a7c09e976ee768b0d5109173e521a19daf"},
+    {file = "yarl-1.8.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2305517e332a862ef75be8fad3606ea10108662bc6fe08509d5ca99503ac2aee"},
+    {file = "yarl-1.8.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42430ff511571940d51e75cf42f1e4dbdded477e71c1b7a17f4da76c1da8ea76"},
+    {file = "yarl-1.8.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3150078118f62371375e1e69b13b48288e44f6691c1069340081c3fd12c94d5b"},
+    {file = "yarl-1.8.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c15163b6125db87c8f53c98baa5e785782078fbd2dbeaa04c6141935eb6dab7a"},
+    {file = "yarl-1.8.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4d04acba75c72e6eb90745447d69f84e6c9056390f7a9724605ca9c56b4afcc6"},
+    {file = "yarl-1.8.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:e7fd20d6576c10306dea2d6a5765f46f0ac5d6f53436217913e952d19237efc4"},
+    {file = "yarl-1.8.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:75c16b2a900b3536dfc7014905a128a2bea8fb01f9ee26d2d7d8db0a08e7cb2c"},
+    {file = "yarl-1.8.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6d88056a04860a98341a0cf53e950e3ac9f4e51d1b6f61a53b0609df342cc8b2"},
+    {file = "yarl-1.8.2-cp311-cp311-win32.whl", hash = "sha256:fb742dcdd5eec9f26b61224c23baea46c9055cf16f62475e11b9b15dfd5c117b"},
+    {file = "yarl-1.8.2-cp311-cp311-win_amd64.whl", hash = "sha256:8c46d3d89902c393a1d1e243ac847e0442d0196bbd81aecc94fcebbc2fd5857c"},
+    {file = "yarl-1.8.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:ceff9722e0df2e0a9e8a79c610842004fa54e5b309fe6d218e47cd52f791d7ef"},
+    {file = "yarl-1.8.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f6b4aca43b602ba0f1459de647af954769919c4714706be36af670a5f44c9c1"},
+    {file = "yarl-1.8.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1684a9bd9077e922300ecd48003ddae7a7474e0412bea38d4631443a91d61077"},
+    {file = "yarl-1.8.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ebb78745273e51b9832ef90c0898501006670d6e059f2cdb0e999494eb1450c2"},
+    {file = "yarl-1.8.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3adeef150d528ded2a8e734ebf9ae2e658f4c49bf413f5f157a470e17a4a2e89"},
+    {file = "yarl-1.8.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57a7c87927a468e5a1dc60c17caf9597161d66457a34273ab1760219953f7f4c"},
+    {file = "yarl-1.8.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:efff27bd8cbe1f9bd127e7894942ccc20c857aa8b5a0327874f30201e5ce83d0"},
+    {file = "yarl-1.8.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a783cd344113cb88c5ff7ca32f1f16532a6f2142185147822187913eb989f739"},
+    {file = "yarl-1.8.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:705227dccbe96ab02c7cb2c43e1228e2826e7ead880bb19ec94ef279e9555b5b"},
+    {file = "yarl-1.8.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:34c09b43bd538bf6c4b891ecce94b6fa4f1f10663a8d4ca589a079a5018f6ed7"},
+    {file = "yarl-1.8.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a48f4f7fea9a51098b02209d90297ac324241bf37ff6be6d2b0149ab2bd51b37"},
+    {file = "yarl-1.8.2-cp37-cp37m-win32.whl", hash = "sha256:0414fd91ce0b763d4eadb4456795b307a71524dbacd015c657bb2a39db2eab89"},
+    {file = "yarl-1.8.2-cp37-cp37m-win_amd64.whl", hash = "sha256:d881d152ae0007809c2c02e22aa534e702f12071e6b285e90945aa3c376463c5"},
+    {file = "yarl-1.8.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5df5e3d04101c1e5c3b1d69710b0574171cc02fddc4b23d1b2813e75f35a30b1"},
+    {file = "yarl-1.8.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7a66c506ec67eb3159eea5096acd05f5e788ceec7b96087d30c7d2865a243918"},
+    {file = "yarl-1.8.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2b4fa2606adf392051d990c3b3877d768771adc3faf2e117b9de7eb977741229"},
+    {file = "yarl-1.8.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e21fb44e1eff06dd6ef971d4bdc611807d6bd3691223d9c01a18cec3677939e"},
+    {file = "yarl-1.8.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:93202666046d9edadfe9f2e7bf5e0782ea0d497b6d63da322e541665d65a044e"},
+    {file = "yarl-1.8.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fc77086ce244453e074e445104f0ecb27530d6fd3a46698e33f6c38951d5a0f1"},
+    {file = "yarl-1.8.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64dd68a92cab699a233641f5929a40f02a4ede8c009068ca8aa1fe87b8c20ae3"},
+    {file = "yarl-1.8.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1b372aad2b5f81db66ee7ec085cbad72c4da660d994e8e590c997e9b01e44901"},
+    {file = "yarl-1.8.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e6f3515aafe0209dd17fb9bdd3b4e892963370b3de781f53e1746a521fb39fc0"},
+    {file = "yarl-1.8.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:dfef7350ee369197106805e193d420b75467b6cceac646ea5ed3049fcc950a05"},
+    {file = "yarl-1.8.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:728be34f70a190566d20aa13dc1f01dc44b6aa74580e10a3fb159691bc76909d"},
+    {file = "yarl-1.8.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:ff205b58dc2929191f68162633d5e10e8044398d7a45265f90a0f1d51f85f72c"},
+    {file = "yarl-1.8.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:baf211dcad448a87a0d9047dc8282d7de59473ade7d7fdf22150b1d23859f946"},
+    {file = "yarl-1.8.2-cp38-cp38-win32.whl", hash = "sha256:272b4f1599f1b621bf2aabe4e5b54f39a933971f4e7c9aa311d6d7dc06965165"},
+    {file = "yarl-1.8.2-cp38-cp38-win_amd64.whl", hash = "sha256:326dd1d3caf910cd26a26ccbfb84c03b608ba32499b5d6eeb09252c920bcbe4f"},
+    {file = "yarl-1.8.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f8ca8ad414c85bbc50f49c0a106f951613dfa5f948ab69c10ce9b128d368baf8"},
+    {file = "yarl-1.8.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:418857f837347e8aaef682679f41e36c24250097f9e2f315d39bae3a99a34cbf"},
+    {file = "yarl-1.8.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ae0eec05ab49e91a78700761777f284c2df119376e391db42c38ab46fd662b77"},
+    {file = "yarl-1.8.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:009a028127e0a1755c38b03244c0bea9d5565630db9c4cf9572496e947137a87"},
+    {file = "yarl-1.8.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3edac5d74bb3209c418805bda77f973117836e1de7c000e9755e572c1f7850d0"},
+    {file = "yarl-1.8.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:da65c3f263729e47351261351b8679c6429151ef9649bba08ef2528ff2c423b2"},
+    {file = "yarl-1.8.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ef8fb25e52663a1c85d608f6dd72e19bd390e2ecaf29c17fb08f730226e3a08"},
+    {file = "yarl-1.8.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bcd7bb1e5c45274af9a1dd7494d3c52b2be5e6bd8d7e49c612705fd45420b12d"},
+    {file = "yarl-1.8.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:44ceac0450e648de86da8e42674f9b7077d763ea80c8ceb9d1c3e41f0f0a9951"},
+    {file = "yarl-1.8.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:97209cc91189b48e7cfe777237c04af8e7cc51eb369004e061809bcdf4e55220"},
+    {file = "yarl-1.8.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:48dd18adcf98ea9cd721a25313aef49d70d413a999d7d89df44f469edfb38a06"},
+    {file = "yarl-1.8.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:e59399dda559688461762800d7fb34d9e8a6a7444fd76ec33220a926c8be1516"},
+    {file = "yarl-1.8.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d617c241c8c3ad5c4e78a08429fa49e4b04bedfc507b34b4d8dceb83b4af3588"},
+    {file = "yarl-1.8.2-cp39-cp39-win32.whl", hash = "sha256:cb6d48d80a41f68de41212f3dfd1a9d9898d7841c8f7ce6696cf2fd9cb57ef83"},
+    {file = "yarl-1.8.2-cp39-cp39-win_amd64.whl", hash = "sha256:6604711362f2dbf7160df21c416f81fac0de6dbcf0b5445a2ef25478ecc4c778"},
+    {file = "yarl-1.8.2.tar.gz", hash = "sha256:49d43402c6e3013ad0978602bf6bf5328535c48d192304b91b97a3c6790b1562"},
 ]
 
 [package.dependencies]
 idna = ">=2.0"
 multidict = ">=4.0"
 
+[[package]]
+name = "zipp"
+version = "3.15.0"
+description = "Backport of pathlib-compatible object wrapper for zip files"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+files = [
+    {file = "zipp-3.15.0-py3-none-any.whl", hash = "sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556"},
+    {file = "zipp-3.15.0.tar.gz", hash = "sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b"},
+]
+
+[package.extras]
+docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
+testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"]
+
+[extras]
+turbinia-legacy = ["turbinia"]
+
 [metadata]
 lock-version = "2.0"
-python-versions = ">=3.10,<3.11"
-content-hash = "c2565d083a911c637c56b714f0311bc2bee42ce78fa631478eb62b9ea1717558"
+python-versions = ">=3.9,<3.11"
+content-hash = "8e0429e785882641dd238f27812dd9976dc6d47450f1c1ca32e08ded0d8836f9"
diff --git a/pyproject.toml b/pyproject.toml
index 6634f3659..8507f4211 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -11,6 +11,8 @@ dftimewolf = 'dftimewolf.cli.dftimewolf_recipes:Main'
 [tool.poetry.dependencies]
 python = ">=3.10,<3.11"
 docker = "^5.0.3"
+filelock = "*"
+google-cloud-storage = "*"
 google-cloud-logging = "2.3.1"
 google-cloud-bigquery = "*"
 libcloudforensics = "^20220718"
@@ -25,9 +27,14 @@ prettytable = "^3.3.0"
 vt-py = "^0.14.0"
 boto3 = "^1.24.31"
 python-magic = "^0.4.27"
-turbinia = "20220216"
+turbinia = {version = "20230315", optional = true}
+turbinia-api-lib = "*"
+turbinia-client = "*"
 grr-api-client = "^3.4.6"
 
+[tool.poetry.extras]
+turbinia_legacy = ["turbinia"]
+
 [tool.poetry.group.dev.dependencies]
 mock = "^4.0.3"
 pylint = "2.10.*"


Test patch:
diff --git a/tests/cli/main_tool.py b/tests/cli/main_tool.py
index 184afe8ba..300dcbb5b 100644
--- a/tests/cli/main_tool.py
+++ b/tests/cli/main_tool.py
@@ -77,15 +77,16 @@ def testRecipeSetupArgs(self):
       preflights = recipe.contents.get('preflights', [])
       for module in modules + preflights:
         runtime_name = module.get('runtime_name', module['name'])
-        setup_func = self.tool.state._module_pool[runtime_name].SetUp
-        expected_args = set(inspect.getfullargspec(setup_func).args)
-        expected_args.remove('self')
-        provided_args = set(module['args'])
-
-        self.assertEqual(
-          expected_args,
-          provided_args,
-          f'Error in {recipe.name}:{runtime_name}')
+        if runtime_name in self.tool.state._module_pool:
+          setup_func = self.tool.state._module_pool[runtime_name].SetUp
+          expected_args = set(inspect.getfullargspec(setup_func).args)
+          expected_args.remove('self')
+          provided_args = set(module['args'])
+
+          self.assertEqual(
+            expected_args,
+            provided_args,
+            f'Error in {recipe.name}:{runtime_name}')
 
   def testRecipeValidators(self):
     """Tests that recipes do not specify invalid validators."""
diff --git a/tests/lib/exporters/timesketch.py b/tests/lib/exporters/timesketch.py
index f55ae9f28..a13a9e25d 100644
--- a/tests/lib/exporters/timesketch.py
+++ b/tests/lib/exporters/timesketch.py
@@ -94,15 +94,19 @@ def testNewSketchCreation(self, mock_GetApiClient, _):
     self.assertEqual(report.text_format, 'markdown')
 
   # pylint: disable=invalid-name
+  @mock.patch('dftimewolf.lib.exporters.timesketch.TimesketchExporter._RunAnalyzers')  # pylint: disable=line-too-long'')
   @mock.patch('time.sleep')
   @mock.patch('timesketch_import_client.importer.ImportStreamer')
   @mock.patch('dftimewolf.lib.timesketch_utils.GetApiClient')
-  def testWaitForTimeline(
-        self, mock_GetApiClient, unused_streamer, unused_sleep):
+  def testWaitForTimeline(self,
+      mock_GetApiClient,
+      unused_streamer,
+      unused_sleep,
+      mock_RunAnalyzers):
     """Tests the SetUp function."""
     mock_sketch = mock.Mock(id=1234, my_acl=['write'])
     mock_sketch.api.api_root = 'timesketch.com/api/v1'
-
+    mock_RunAnalyzers.return_value = None
     mock_api_client = mock.Mock()
     mock_api_client.get_sketch.return_value = mock_sketch
     mock_api_client.create_sketch.return_value = mock_sketch
diff --git a/tests/lib/processors/test_data/c4e9abd577db475484b2ded34a011b96.tgz b/tests/lib/processors/test_data/c4e9abd577db475484b2ded34a011b96.tgz
new file mode 100644
index 000000000..5d8eb319e
Binary files /dev/null and b/tests/lib/processors/test_data/c4e9abd577db475484b2ded34a011b96.tgz differ
diff --git a/tests/lib/processors/test_data/turbinia_request_status.json b/tests/lib/processors/test_data/turbinia_request_status.json
new file mode 100644
index 000000000..1e9e0d938
--- /dev/null
+++ b/tests/lib/processors/test_data/turbinia_request_status.json
@@ -0,0 +1,871 @@
+{
+  "failed_tasks": 0,
+  "last_task_update_time": "2023-04-04T01:52:40.545676Z",
+  "queued_tasks": 0,
+  "reason": "4444",
+  "request_id": "3a5759372b594c0bb2a81cda805ca9a0",
+  "requester": "vscode",
+  "running_tasks": 0,
+  "status": "successful",
+  "successful_tasks": 34,
+  "task_count": 34,
+  "tasks": [
+    {
+      "all_args": "",
+      "evidence_name": "GoogleCloudDisk",
+      "group_id": "6b8410cd41a046c1937635e992ccbe54",
+      "group_name": "",
+      "id": "c4e9abd577db475484b2ded34a011b96",
+      "instance": "osdfir-cluster",
+      "job_id": "1da9975596ef497ca2c3c2cbf5bd866d",
+      "last_update": "2023-04-03T23:47:37.346124Z",
+      "name": "PlasoParserTask",
+      "reason": "4444",
+      "report_data": null,
+      "report_priority": 50,
+      "request_id": "3a5759372b594c0bb2a81cda805ca9a0",
+      "requester": "vscode",
+      "run_time": 492.280975,
+      "saved_paths": [
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680565159-c4e9abd577db475484b2ded34a011b96-PlasoParserTask/c4e9abd577db475484b2ded34a011b96.log",
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680565159-c4e9abd577db475484b2ded34a011b96-PlasoParserTask/stderr-mz1la0ba.txt",
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680565159-c4e9abd577db475484b2ded34a011b96-PlasoParserTask/stdout-fpoxxbyd.txt",
+        "/tmp/3a5759372b594c0bb2a81cda805ca9a0/1680565159-c4e9abd577db475484b2ded34a011b96-PlasoParserTask/c4e9abd577db475484b2ded34a011b96.plaso",
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680565159-c4e9abd577db475484b2ded34a011b96-PlasoParserTask/c4e9abd577db475484b2ded34a011b96.plaso",
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680565159-c4e9abd577db475484b2ded34a011b96-PlasoParserTask/c4e9abd577db475484b2ded34a011b96.plaso.metadata.json",
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680565159-c4e9abd577db475484b2ded34a011b96-PlasoParserTask/worker-log.txt"
+      ],
+      "status": "Completed successfully in 0:08:12.280975 on turbinia-worker-5867565d6c-pjsdg",
+      "successful": true,
+      "worker_name": "turbinia-worker-5867565d6c-pjsdg"
+    },
+    {
+      "all_args": "",
+      "evidence_name": "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561550-3595e021bfd945fbbd2ea9a33ee38769-FileSystemTimelineTask/file_system.bodyfile",
+      "group_id": "6b8410cd41a046c1937635e992ccbe54",
+      "group_name": "",
+      "id": "86bfb48ec9564ec5b4eb790b075f6a42",
+      "instance": "osdfir-cluster",
+      "job_id": "58e1f3bd3d85424897bcff8589c9603a",
+      "last_update": "2023-04-03T22:40:40.562544Z",
+      "name": "PlasoParserTask",
+      "reason": "4444",
+      "report_data": null,
+      "report_priority": 50,
+      "request_id": "3a5759372b594c0bb2a81cda805ca9a0",
+      "requester": "vscode",
+      "run_time": 64.157938,
+      "saved_paths": [
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561570-86bfb48ec9564ec5b4eb790b075f6a42-PlasoParserTask/86bfb48ec9564ec5b4eb790b075f6a42.log",
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561570-86bfb48ec9564ec5b4eb790b075f6a42-PlasoParserTask/stderr-9j__ppo2.txt",
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561570-86bfb48ec9564ec5b4eb790b075f6a42-PlasoParserTask/stdout-lwr8rbg_.txt",
+        "/tmp/3a5759372b594c0bb2a81cda805ca9a0/1680561570-86bfb48ec9564ec5b4eb790b075f6a42-PlasoParserTask/86bfb48ec9564ec5b4eb790b075f6a42.plaso",
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561570-86bfb48ec9564ec5b4eb790b075f6a42-PlasoParserTask/86bfb48ec9564ec5b4eb790b075f6a42.plaso",
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561570-86bfb48ec9564ec5b4eb790b075f6a42-PlasoParserTask/86bfb48ec9564ec5b4eb790b075f6a42.plaso.metadata.json",
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561570-86bfb48ec9564ec5b4eb790b075f6a42-PlasoParserTask/worker-log.txt"
+      ],
+      "status": "Completed successfully in 0:01:04.157938 on turbinia-worker-5867565d6c-56m7t",
+      "successful": true,
+      "worker_name": "turbinia-worker-5867565d6c-56m7t"
+    },
+    {
+      "all_args": "",
+      "evidence_name": "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680573030-b41e8b5abbff497a9447e17c0d140ff3-PsortTask/b41e8b5abbff497a9447e17c0d140ff3.csv",
+      "group_id": "6b8410cd41a046c1937635e992ccbe54",
+      "group_name": "",
+      "id": "b238f821f6c6416488f99b37d24391b3",
+      "instance": "osdfir-cluster",
+      "job_id": "9eb70539af994eb1a03be6d5ebfc66c9",
+      "last_update": "2023-04-04T01:52:40.545676Z",
+      "name": "GrepTask",
+      "reason": "4444",
+      "report_data": null,
+      "report_priority": 50,
+      "request_id": "3a5759372b594c0bb2a81cda805ca9a0",
+      "requester": "vscode",
+      "run_time": 0.00001,
+      "saved_paths": [
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680573150-b238f821f6c6416488f99b37d24391b3-GrepTask/worker-log.txt"
+      ],
+      "status": "No patterns supplied, exit task",
+      "successful": true,
+      "worker_name": "turbinia-worker-5867565d6c-jcng2"
+    },
+    {
+      "all_args": "",
+      "evidence_name": "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680572399-a8b36b35ceaa495cab255e19691dc023-PlasoHasherTask/a8b36b35ceaa495cab255e19691dc023.plaso",
+      "group_id": "6b8410cd41a046c1937635e992ccbe54",
+      "group_name": "",
+      "id": "b41e8b5abbff497a9447e17c0d140ff3",
+      "instance": "osdfir-cluster",
+      "job_id": "c47156c828d44f9497c32c8927885fde",
+      "last_update": "2023-04-04T01:52:30.515949Z",
+      "name": "PsortTask",
+      "reason": "4444",
+      "report_data": null,
+      "report_priority": 50,
+      "request_id": "3a5759372b594c0bb2a81cda805ca9a0",
+      "requester": "vscode",
+      "run_time": 115.844667,
+      "saved_paths": [
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680573030-b41e8b5abbff497a9447e17c0d140ff3-PsortTask/stderr-nspgmbcg.txt",
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680573030-b41e8b5abbff497a9447e17c0d140ff3-PsortTask/b41e8b5abbff497a9447e17c0d140ff3.log",
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680573030-b41e8b5abbff497a9447e17c0d140ff3-PsortTask/stdout-qupp0x1p.txt",
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680573030-b41e8b5abbff497a9447e17c0d140ff3-PsortTask/b41e8b5abbff497a9447e17c0d140ff3.csv",
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680573030-b41e8b5abbff497a9447e17c0d140ff3-PsortTask/b41e8b5abbff497a9447e17c0d140ff3.csv",
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680573030-b41e8b5abbff497a9447e17c0d140ff3-PsortTask/worker-log.txt"
+      ],
+      "status": "Completed successfully in 0:01:55.844667 on turbinia-worker-5867565d6c-f662j",
+      "successful": true,
+      "worker_name": "turbinia-worker-5867565d6c-f662j"
+    },
+    {
+      "all_args": "",
+      "evidence_name": "GoogleCloudDisk:/p1",
+      "group_id": "6b8410cd41a046c1937635e992ccbe54",
+      "group_name": "",
+      "id": "e0e0dcabfedb443280ee24025ed0d27a",
+      "instance": "osdfir-cluster",
+      "job_id": "981543f0f4684918bf9206457d90772a",
+      "last_update": "2023-04-03T22:39:30.302376Z",
+      "name": "PostgresAccountAnalysisTask",
+      "reason": "4444",
+      "report_data": "",
+      "report_priority": 80,
+      "request_id": "3a5759372b594c0bb2a81cda805ca9a0",
+      "requester": "vscode",
+      "run_time": 3.75357,
+      "saved_paths": [
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561560-e0e0dcabfedb443280ee24025ed0d27a-PostgresAccountAnalysisTask/postgres_account_analysis.txt",
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561560-e0e0dcabfedb443280ee24025ed0d27a-PostgresAccountAnalysisTask/postgres_account_analysis.txt",
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561560-e0e0dcabfedb443280ee24025ed0d27a-PostgresAccountAnalysisTask/worker-log.txt"
+      ],
+      "status": "No weak passwords found",
+      "successful": true,
+      "worker_name": "turbinia-worker-5867565d6c-56m7t"
+    },
+    {
+      "all_args": "",
+      "evidence_name": "GoogleCloudDisk:/p2",
+      "group_id": "6b8410cd41a046c1937635e992ccbe54",
+      "group_name": "",
+      "id": "b294b233def54e3e92c91d751ab2b088",
+      "instance": "osdfir-cluster",
+      "job_id": "b8cf9ad1d5b1467f97bcfe3292a95c7c",
+      "last_update": "2023-04-03T22:39:30.309349Z",
+      "name": "DockerContainersEnumerationTask",
+      "reason": "4444",
+      "report_data": "Found 0 containers: ",
+      "report_priority": 80,
+      "request_id": "3a5759372b594c0bb2a81cda805ca9a0",
+      "requester": "vscode",
+      "run_time": 0.001447,
+      "saved_paths": [
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561560-b294b233def54e3e92c91d751ab2b088-DockerContainersEnumerationTask/worker-log.txt"
+      ],
+      "status": "Found 0 containers: ",
+      "successful": true,
+      "worker_name": "turbinia-worker-5867565d6c-z5v2j"
+    },
+    {
+      "all_args": "",
+      "evidence_name": "GoogleCloudDisk:/p1",
+      "group_id": "6b8410cd41a046c1937635e992ccbe54",
+      "group_name": "",
+      "id": "ff5261429a42484193b184fe56ae6314",
+      "instance": "osdfir-cluster",
+      "job_id": "afe7758adeac4879818ceb8eb31329f6",
+      "last_update": "2023-04-03T22:39:30.291611Z",
+      "name": "DockerContainersEnumerationTask",
+      "reason": "4444",
+      "report_data": "Found 0 containers: ",
+      "report_priority": 80,
+      "request_id": "3a5759372b594c0bb2a81cda805ca9a0",
+      "requester": "vscode",
+      "run_time": 0.000651,
+      "saved_paths": [
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561560-ff5261429a42484193b184fe56ae6314-DockerContainersEnumerationTask/worker-log.txt"
+      ],
+      "status": "Found 0 containers: ",
+      "successful": true,
+      "worker_name": "turbinia-worker-5867565d6c-s8vjp"
+    },
+    {
+      "all_args": "",
+      "evidence_name": "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561570-86bfb48ec9564ec5b4eb790b075f6a42-PlasoParserTask/86bfb48ec9564ec5b4eb790b075f6a42.plaso",
+      "group_id": "6b8410cd41a046c1937635e992ccbe54",
+      "group_name": "",
+      "id": "8bea8901e39c4c969acb99a55a7d2c4f",
+      "instance": "osdfir-cluster",
+      "job_id": "04252f5cb4cf43cbb6b95d8c61c7877b",
+      "last_update": "2023-04-03T23:42:35.489222Z",
+      "name": "PsortTask",
+      "reason": "4444",
+      "report_data": null,
+      "report_priority": 50,
+      "request_id": "3a5759372b594c0bb2a81cda805ca9a0",
+      "requester": "vscode",
+      "run_time": 79.006585,
+      "saved_paths": [
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680565270-8bea8901e39c4c969acb99a55a7d2c4f-PsortTask/stderr-pv00y74f.txt",
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680565270-8bea8901e39c4c969acb99a55a7d2c4f-PsortTask/stdout-hvxx029b.txt",
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680565270-8bea8901e39c4c969acb99a55a7d2c4f-PsortTask/8bea8901e39c4c969acb99a55a7d2c4f.csv",
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680565270-8bea8901e39c4c969acb99a55a7d2c4f-PsortTask/8bea8901e39c4c969acb99a55a7d2c4f.csv",
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680565270-8bea8901e39c4c969acb99a55a7d2c4f-PsortTask/worker-log.txt"
+      ],
+      "status": "Completed successfully in 0:01:19.006585 on turbinia-worker-5867565d6c-fvgbq",
+      "successful": true,
+      "worker_name": "turbinia-worker-5867565d6c-fvgbq"
+    },
+    {
+      "all_args": "",
+      "evidence_name": "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680565657-4e665e61bb9d442a9ceb71efcfef9685-PsortTask/4e665e61bb9d442a9ceb71efcfef9685.csv",
+      "group_id": "6b8410cd41a046c1937635e992ccbe54",
+      "group_name": "",
+      "id": "7dc2f123fa5b4e6dba330aa258650b5a",
+      "instance": "osdfir-cluster",
+      "job_id": "712706e4e6d5421d8c7b55b366045e10",
+      "last_update": "2023-04-03T23:47:57.648989Z",
+      "name": "GrepTask",
+      "reason": "4444",
+      "report_data": null,
+      "report_priority": 50,
+      "request_id": "3a5759372b594c0bb2a81cda805ca9a0",
+      "requester": "vscode",
+      "run_time": 0.000012,
+      "saved_paths": [
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680565667-7dc2f123fa5b4e6dba330aa258650b5a-GrepTask/worker-log.txt"
+      ],
+      "status": "No patterns supplied, exit task",
+      "successful": true,
+      "worker_name": "turbinia-worker-5867565d6c-khxtx"
+    },
+    {
+      "all_args": "",
+      "evidence_name": "GoogleCloudDisk",
+      "group_id": "6b8410cd41a046c1937635e992ccbe54",
+      "group_name": "",
+      "id": "7b9d1fb69b194f069ab6c368e212c379",
+      "instance": "osdfir-cluster",
+      "job_id": "97c66458cfbc4d56aa4396c1de0e211a",
+      "last_update": "2023-04-03T22:39:20.161487Z",
+      "name": "FileArtifactExtractionTask",
+      "reason": "4444",
+      "report_data": null,
+      "report_priority": 50,
+      "request_id": "3a5759372b594c0bb2a81cda805ca9a0",
+      "requester": "vscode",
+      "run_time": 4.714955,
+      "saved_paths": [
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561550-7b9d1fb69b194f069ab6c368e212c379-FileArtifactExtractionTask/stdout-8zbi4hqg.txt",
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561550-7b9d1fb69b194f069ab6c368e212c379-FileArtifactExtractionTask/7b9d1fb69b194f069ab6c368e212c379.log",
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561550-7b9d1fb69b194f069ab6c368e212c379-FileArtifactExtractionTask/stderr-zj8q79xu.txt",
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561550-7b9d1fb69b194f069ab6c368e212c379-FileArtifactExtractionTask/worker-log.txt"
+      ],
+      "status": "Extracted 0 new GKEDockerContainerLogs artifacts",
+      "successful": true,
+      "worker_name": "turbinia-worker-5867565d6c-xndvm"
+    },
+    {
+      "all_args": "",
+      "evidence_name": "GoogleCloudDisk",
+      "group_id": "6b8410cd41a046c1937635e992ccbe54",
+      "group_name": "",
+      "id": "a6eef51984ae4092be69153f3b669aae",
+      "instance": "osdfir-cluster",
+      "job_id": "5b431d8884d94495a923a5446251e1d2",
+      "last_update": "2023-04-03T22:39:20.226457Z",
+      "name": "WindowsAccountAnalysisTask",
+      "reason": "4444",
+      "report_data": null,
+      "report_priority": 50,
+      "request_id": "3a5759372b594c0bb2a81cda805ca9a0",
+      "requester": "vscode",
+      "run_time": 4.73318,
+      "saved_paths": [
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561550-a6eef51984ae4092be69153f3b669aae-WindowsAccountAnalysisTask/worker-log.txt"
+      ],
+      "status": "No Windows account files found",
+      "successful": true,
+      "worker_name": "turbinia-worker-5867565d6c-s8vjp"
+    },
+    {
+      "all_args": "",
+      "evidence_name": "GoogleCloudDisk:/p1",
+      "group_id": "6b8410cd41a046c1937635e992ccbe54",
+      "group_name": "",
+      "id": "9de55e449806466ca2d702702c7dbb66",
+      "instance": "osdfir-cluster",
+      "job_id": "77562102bd6a4b029218ae77c45259c7",
+      "last_update": "2023-04-03T22:39:30.295106Z",
+      "name": "FsstatTask",
+      "reason": "4444",
+      "report_data": null,
+      "report_priority": 50,
+      "request_id": "3a5759372b594c0bb2a81cda805ca9a0",
+      "requester": "vscode",
+      "run_time": 0.084762,
+      "saved_paths": [
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561560-9de55e449806466ca2d702702c7dbb66-FsstatTask/fsstat.txt",
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561560-9de55e449806466ca2d702702c7dbb66-FsstatTask/fsstat.txt",
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561560-9de55e449806466ca2d702702c7dbb66-FsstatTask/fsstat.txt",
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561560-9de55e449806466ca2d702702c7dbb66-FsstatTask/worker-log.txt"
+      ],
+      "status": "Completed successfully in 0:00:00.084762 on turbinia-worker-5867565d6c-f5sk7",
+      "successful": true,
+      "worker_name": "turbinia-worker-5867565d6c-f5sk7"
+    },
+    {
+      "all_args": "",
+      "evidence_name": "GoogleCloudDisk",
+      "group_id": "6b8410cd41a046c1937635e992ccbe54",
+      "group_name": "",
+      "id": "431f5735f7b74e658acd91fe606ae303",
+      "instance": "osdfir-cluster",
+      "job_id": "1fbf662f538347439c4ad7dcd5042b3b",
+      "last_update": "2023-04-03T22:39:20.208550Z",
+      "name": "PartitionEnumerationTask",
+      "reason": "4444",
+      "report_data": "#### Found 2 partition(s) in [GoogleCloudDisk]\n##### /p1:\n* Important: True\n* Filesystem: TSK\n* Source evidence is a volume image\n##### /p2:\n* Important: True\n* Filesystem: XFS\n* Source evidence is a volume image",
+      "report_priority": 80,
+      "request_id": "3a5759372b594c0bb2a81cda805ca9a0",
+      "requester": "vscode",
+      "run_time": 0.011851,
+      "saved_paths": [
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561550-431f5735f7b74e658acd91fe606ae303-PartitionEnumerationTask/worker-log.txt"
+      ],
+      "status": "Found 2 partition(s) in [GoogleCloudDisk] Processing 2 partitions:",
+      "successful": true,
+      "worker_name": "turbinia-worker-5867565d6c-nhg4b"
+    },
+    {
+      "all_args": "",
+      "evidence_name": "GoogleCloudDisk",
+      "group_id": "6b8410cd41a046c1937635e992ccbe54",
+      "group_name": "",
+      "id": "e520e5145ff7422da73beb14f73ad17d",
+      "instance": "osdfir-cluster",
+      "job_id": "9c20056f13e74f9d9b08c066d51320cb",
+      "last_update": "2023-04-03T22:39:20.211395Z",
+      "name": "FileArtifactExtractionTask",
+      "reason": "4444",
+      "report_data": null,
+      "report_priority": 50,
+      "request_id": "3a5759372b594c0bb2a81cda805ca9a0",
+      "requester": "vscode",
+      "run_time": 4.512253,
+      "saved_paths": [
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561550-e520e5145ff7422da73beb14f73ad17d-FileArtifactExtractionTask/stdout-p2k7rmz3.txt",
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561550-e520e5145ff7422da73beb14f73ad17d-FileArtifactExtractionTask/e520e5145ff7422da73beb14f73ad17d.log",
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561550-e520e5145ff7422da73beb14f73ad17d-FileArtifactExtractionTask/stderr-1n7u4m5s.txt",
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561550-e520e5145ff7422da73beb14f73ad17d-FileArtifactExtractionTask/worker-log.txt"
+      ],
+      "status": "Extracted 0 new RedisConfigFile artifacts",
+      "successful": true,
+      "worker_name": "turbinia-worker-5867565d6c-6kq95"
+    },
+    {
+      "all_args": "",
+      "evidence_name": "GoogleCloudDisk",
+      "group_id": "6b8410cd41a046c1937635e992ccbe54",
+      "group_name": "",
+      "id": "11464a03e05d4def886cb7aeff14b7be",
+      "instance": "osdfir-cluster",
+      "job_id": "558895ace6064eda9a821222dee18654",
+      "last_update": "2023-04-03T22:39:40.359080Z",
+      "name": "WordpressCredsAnalysisTask",
+      "reason": "4444",
+      "report_data": "",
+      "report_priority": 80,
+      "request_id": "3a5759372b594c0bb2a81cda805ca9a0",
+      "requester": "vscode",
+      "run_time": 20.65051,
+      "saved_paths": [
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561550-11464a03e05d4def886cb7aeff14b7be-WordpressCredsAnalysisTask/wordpress_creds_analysis.txt",
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561550-11464a03e05d4def886cb7aeff14b7be-WordpressCredsAnalysisTask/wordpress_creds_analysis.txt",
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561550-11464a03e05d4def886cb7aeff14b7be-WordpressCredsAnalysisTask/worker-log.txt"
+      ],
+      "status": "No weak passwords found",
+      "successful": true,
+      "worker_name": "turbinia-worker-5867565d6c-k5pfs"
+    },
+    {
+      "all_args": "",
+      "evidence_name": "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561550-aadf2b9f713d45e499cedf19f4c02023-FileArtifactExtractionTask/export/etc/ssh/sshd_config",
+      "group_id": "6b8410cd41a046c1937635e992ccbe54",
+      "group_name": "",
+      "id": "92633996ccfc4720968cce512df9b660",
+      "instance": "osdfir-cluster",
+      "job_id": "16a8ed789ce24da7bc853d5b795fe85b",
+      "last_update": "2023-04-03T22:39:30.315385Z",
+      "name": "SSHDAnalysisTask",
+      "reason": "4444",
+      "report_data": "No issues found in SSH configuration",
+      "report_priority": 80,
+      "request_id": "3a5759372b594c0bb2a81cda805ca9a0",
+      "requester": "vscode",
+      "run_time": 0.009384,
+      "saved_paths": [
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561560-92633996ccfc4720968cce512df9b660-SSHDAnalysisTask/sshd_config_analysis.txt",
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561560-92633996ccfc4720968cce512df9b660-SSHDAnalysisTask/sshd_config_analysis.txt",
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561560-92633996ccfc4720968cce512df9b660-SSHDAnalysisTask/worker-log.txt"
+      ],
+      "status": "No issues found in SSH configuration",
+      "successful": true,
+      "worker_name": "turbinia-worker-5867565d6c-qbsjg"
+    },
+    {
+      "all_args": "",
+      "evidence_name": "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680565159-c4e9abd577db475484b2ded34a011b96-PlasoParserTask/c4e9abd577db475484b2ded34a011b96.plaso",
+      "group_id": "6b8410cd41a046c1937635e992ccbe54",
+      "group_name": "",
+      "id": "4e665e61bb9d442a9ceb71efcfef9685",
+      "instance": "osdfir-cluster",
+      "job_id": "93e75b2f52c94c8da916aaf3a5c5b8fb",
+      "last_update": "2023-04-03T23:47:47.584262Z",
+      "name": "PsortTask",
+      "reason": "4444",
+      "report_data": null,
+      "report_priority": 50,
+      "request_id": "3a5759372b594c0bb2a81cda805ca9a0",
+      "requester": "vscode",
+      "run_time": 1.737752,
+      "saved_paths": [
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680565657-4e665e61bb9d442a9ceb71efcfef9685-PsortTask/stdout-sggj55zt.txt",
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680565657-4e665e61bb9d442a9ceb71efcfef9685-PsortTask/stderr-nuiu1avo.txt",
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680565657-4e665e61bb9d442a9ceb71efcfef9685-PsortTask/4e665e61bb9d442a9ceb71efcfef9685.log",
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680565657-4e665e61bb9d442a9ceb71efcfef9685-PsortTask/4e665e61bb9d442a9ceb71efcfef9685.csv",
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680565657-4e665e61bb9d442a9ceb71efcfef9685-PsortTask/4e665e61bb9d442a9ceb71efcfef9685.csv",
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680565657-4e665e61bb9d442a9ceb71efcfef9685-PsortTask/worker-log.txt"
+      ],
+      "status": "Completed successfully in 0:00:01.737752 on turbinia-worker-5867565d6c-g9prg",
+      "successful": true,
+      "worker_name": "turbinia-worker-5867565d6c-g9prg"
+    },
+    {
+      "all_args": "",
+      "evidence_name": "GoogleCloudDisk:/p2",
+      "group_id": "6b8410cd41a046c1937635e992ccbe54",
+      "group_name": "",
+      "id": "73a9d13c40ad42669ae152e023111d0d",
+      "instance": "osdfir-cluster",
+      "job_id": "17d120ee53b34eb388e9b25d7bfc55ab",
+      "last_update": "2023-04-03T22:39:30.305552Z",
+      "name": "ContainerdEnumerationTask",
+      "reason": "4444",
+      "report_data": null,
+      "report_priority": 50,
+      "request_id": "3a5759372b594c0bb2a81cda805ca9a0",
+      "requester": "vscode",
+      "run_time": 0.018902,
+      "saved_paths": [
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561560-73a9d13c40ad42669ae152e023111d0d-ContainerdEnumerationTask/worker-log.txt"
+      ],
+      "status": "Found 0 containers",
+      "successful": true,
+      "worker_name": "turbinia-worker-5867565d6c-nhg4b"
+    },
+    {
+      "all_args": "",
+      "evidence_name": "GoogleCloudDisk:/p2",
+      "group_id": "6b8410cd41a046c1937635e992ccbe54",
+      "group_name": "",
+      "id": "2ecaa2ed49e7498d95978d58193adddd",
+      "instance": "osdfir-cluster",
+      "job_id": "a78bfcc39d1e44ecb204ce3e634879bf",
+      "last_update": "2023-04-03T22:41:00.620949Z",
+      "name": "YaraAnalysisTask",
+      "reason": "4444",
+      "report_data": "#### **Yara analysis found 2 alert(s)**\n* **2 alerts(s) found:**\n    * /etc/sudoers - 4fb99705d53cda191da5a54c227084c375fbc7f18910a130e8dfb77522d3ad05 - VULN_Linux_Sudoers_Commands - Detects sudoers config with commands which might allow privilege escalation to root - https://wiki.archlinux.org/title/sudo - 50\n    * /usr/share/doc/sudo/examples/sudoers - 8345bfffacc0f1678e0fdd1e2eee7dcf40c6d77e62b02950891b04ba18c22a9f - VULN_Linux_Sudoers_Commands - Detects sudoers config with commands which might allow privilege escalation to root - https://wiki.archlinux.org/title/sudo - 50",
+      "report_priority": 20,
+      "request_id": "3a5759372b594c0bb2a81cda805ca9a0",
+      "requester": "vscode",
+      "run_time": 96.378152,
+      "saved_paths": [
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561560-2ecaa2ed49e7498d95978d58193adddd-YaraAnalysisTask/2ecaa2ed49e7498d95978d58193adddd_fraken_stderr.log",
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561560-2ecaa2ed49e7498d95978d58193adddd-YaraAnalysisTask/2ecaa2ed49e7498d95978d58193adddd_fraken_stdout.log",
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561560-2ecaa2ed49e7498d95978d58193adddd-YaraAnalysisTask/yara_analysis.txt",
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561560-2ecaa2ed49e7498d95978d58193adddd-YaraAnalysisTask/yara_analysis.txt",
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561560-2ecaa2ed49e7498d95978d58193adddd-YaraAnalysisTask/worker-log.txt"
+      ],
+      "status": "Yara analysis found 2 alert(s)",
+      "successful": true,
+      "worker_name": "turbinia-worker-5867565d6c-xndvm"
+    },
+    {
+      "all_args": "",
+      "evidence_name": "GoogleCloudDisk",
+      "group_id": "6b8410cd41a046c1937635e992ccbe54",
+      "group_name": "",
+      "id": "e1ff8a4c17fd4f6db089ce4758db8bf1",
+      "instance": "osdfir-cluster",
+      "job_id": "97c66458cfbc4d56aa4396c1de0e211a",
+      "last_update": "2023-04-03T22:39:20.166402Z",
+      "name": "FileArtifactExtractionTask",
+      "reason": "4444",
+      "report_data": null,
+      "report_priority": 50,
+      "request_id": "3a5759372b594c0bb2a81cda805ca9a0",
+      "requester": "vscode",
+      "run_time": 8.350869,
+      "saved_paths": [
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561550-e1ff8a4c17fd4f6db089ce4758db8bf1-FileArtifactExtractionTask/e1ff8a4c17fd4f6db089ce4758db8bf1.log",
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561550-e1ff8a4c17fd4f6db089ce4758db8bf1-FileArtifactExtractionTask/stdout-jdnnqs4n.txt",
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561550-e1ff8a4c17fd4f6db089ce4758db8bf1-FileArtifactExtractionTask/stderr-njuz_ma3.txt",
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561550-e1ff8a4c17fd4f6db089ce4758db8bf1-FileArtifactExtractionTask/worker-log.txt"
+      ],
+      "status": "Extracted 0 new ApacheAccessLogs artifacts",
+      "successful": true,
+      "worker_name": "turbinia-worker-5867565d6c-z5v2j"
+    },
+    {
+      "all_args": "",
+      "evidence_name": "GoogleCloudDisk",
+      "group_id": "6b8410cd41a046c1937635e992ccbe54",
+      "group_name": "",
+      "id": "6fd5cbe891c24c71b7e1e71a4993e1f1",
+      "instance": "osdfir-cluster",
+      "job_id": "9ac139e14fcf483eb2d28186be1d99c5",
+      "last_update": "2023-04-03T22:39:20.171192Z",
+      "name": "LinuxAccountAnalysisTask",
+      "reason": "4444",
+      "report_data": "",
+      "report_priority": 80,
+      "request_id": "3a5759372b594c0bb2a81cda805ca9a0",
+      "requester": "vscode",
+      "run_time": 4.852442,
+      "saved_paths": [
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561550-6fd5cbe891c24c71b7e1e71a4993e1f1-LinuxAccountAnalysisTask/worker-log.txt"
+      ],
+      "status": "No weak passwords found",
+      "successful": true,
+      "worker_name": "turbinia-worker-5867565d6c-qbsjg"
+    },
+    {
+      "all_args": "",
+      "evidence_name": "GoogleCloudDisk",
+      "group_id": "6b8410cd41a046c1937635e992ccbe54",
+      "group_name": "",
+      "id": "3595e021bfd945fbbd2ea9a33ee38769",
+      "instance": "osdfir-cluster",
+      "job_id": "59a203efec904830b9d21162dc5f1451",
+      "last_update": "2023-04-03T22:39:30.280833Z",
+      "name": "FileSystemTimelineTask",
+      "reason": "4444",
+      "report_data": null,
+      "report_priority": 50,
+      "request_id": "3a5759372b594c0bb2a81cda805ca9a0",
+      "requester": "vscode",
+      "run_time": 15.155272,
+      "saved_paths": [
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561550-3595e021bfd945fbbd2ea9a33ee38769-FileSystemTimelineTask/file_system.bodyfile",
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561550-3595e021bfd945fbbd2ea9a33ee38769-FileSystemTimelineTask/file_system.bodyfile",
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561550-3595e021bfd945fbbd2ea9a33ee38769-FileSystemTimelineTask/worker-log.txt"
+      ],
+      "status": "Generated file system timeline containing [89321] entries",
+      "successful": true,
+      "worker_name": "turbinia-worker-5867565d6c-qkl6v"
+    },
+    {
+      "all_args": "",
+      "evidence_name": "GoogleCloudDisk:/p1",
+      "group_id": "6b8410cd41a046c1937635e992ccbe54",
+      "group_name": "",
+      "id": "382491cf495d4be9982e675f2e0a3949",
+      "instance": "osdfir-cluster",
+      "job_id": "2176724e55ac4521848a0f416b640af5",
+      "last_update": "2023-04-03T22:39:30.288523Z",
+      "name": "ContainerdEnumerationTask",
+      "reason": "4444",
+      "report_data": null,
+      "report_priority": 50,
+      "request_id": "3a5759372b594c0bb2a81cda805ca9a0",
+      "requester": "vscode",
+      "run_time": 0.020521,
+      "saved_paths": [
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561560-382491cf495d4be9982e675f2e0a3949-ContainerdEnumerationTask/worker-log.txt"
+      ],
+      "status": "Found 0 containers",
+      "successful": true,
+      "worker_name": "turbinia-worker-5867565d6c-6kq95"
+    },
+    {
+      "all_args": "",
+      "evidence_name": "GoogleCloudDisk:/p2",
+      "group_id": "6b8410cd41a046c1937635e992ccbe54",
+      "group_name": "",
+      "id": "d77495074348479eb5b576cc21f34b8b",
+      "instance": "osdfir-cluster",
+      "job_id": "471ed2363104464b91e4007709673ba6",
+      "last_update": "2023-04-03T22:39:30.312375Z",
+      "name": "FsstatTask",
+      "reason": "4444",
+      "report_data": null,
+      "report_priority": 50,
+      "request_id": "3a5759372b594c0bb2a81cda805ca9a0",
+      "requester": "vscode",
+      "run_time": 0.000276,
+      "saved_paths": [
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561560-d77495074348479eb5b576cc21f34b8b-FsstatTask/worker-log.txt"
+      ],
+      "status": "Not running fsstat since partition is not supported",
+      "successful": true,
+      "worker_name": "turbinia-worker-5867565d6c-khxtx"
+    },
+    {
+      "all_args": "",
+      "evidence_name": "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680565270-8bea8901e39c4c969acb99a55a7d2c4f-PsortTask/8bea8901e39c4c969acb99a55a7d2c4f.csv",
+      "group_id": "6b8410cd41a046c1937635e992ccbe54",
+      "group_name": "",
+      "id": "a98d9e3135fc498ba2facbc2ed39238e",
+      "instance": "osdfir-cluster",
+      "job_id": "75225a5d107644e180c61c45047489bc",
+      "last_update": "2023-04-03T23:42:45.533486Z",
+      "name": "GrepTask",
+      "reason": "4444",
+      "report_data": null,
+      "report_priority": 50,
+      "request_id": "3a5759372b594c0bb2a81cda805ca9a0",
+      "requester": "vscode",
+      "run_time": 0.000009,
+      "saved_paths": [
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680565355-a98d9e3135fc498ba2facbc2ed39238e-GrepTask/worker-log.txt"
+      ],
+      "status": "No patterns supplied, exit task",
+      "successful": true,
+      "worker_name": "turbinia-worker-5867565d6c-p5jwc"
+    },
+    {
+      "all_args": "",
+      "evidence_name": "GoogleCloudDisk",
+      "group_id": "6b8410cd41a046c1937635e992ccbe54",
+      "group_name": "",
+      "id": "84d0e1f901f94c23801150c383765534",
+      "instance": "osdfir-cluster",
+      "job_id": "0782b1fdbb9e41c0a961df009c89d4c6",
+      "last_update": "2023-04-03T22:39:20.221218Z",
+      "name": "FileArtifactExtractionTask",
+      "reason": "4444",
+      "report_data": null,
+      "report_priority": 50,
+      "request_id": "3a5759372b594c0bb2a81cda805ca9a0",
+      "requester": "vscode",
+      "run_time": 4.754647,
+      "saved_paths": [
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561550-84d0e1f901f94c23801150c383765534-FileArtifactExtractionTask/stderr-20gre6im.txt",
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561550-84d0e1f901f94c23801150c383765534-FileArtifactExtractionTask/stdout-tpgk2146.txt",
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561550-84d0e1f901f94c23801150c383765534-FileArtifactExtractionTask/84d0e1f901f94c23801150c383765534.log",
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561550-84d0e1f901f94c23801150c383765534-FileArtifactExtractionTask/worker-log.txt"
+      ],
+      "status": "Extracted 0 new TomcatFiles artifacts",
+      "successful": true,
+      "worker_name": "turbinia-worker-5867565d6c-7m25v"
+    },
+    {
+      "all_args": "",
+      "evidence_name": "GoogleCloudDisk",
+      "group_id": "6b8410cd41a046c1937635e992ccbe54",
+      "group_name": "",
+      "id": "a8b36b35ceaa495cab255e19691dc023",
+      "instance": "osdfir-cluster",
+      "job_id": "1da9975596ef497ca2c3c2cbf5bd866d",
+      "last_update": "2023-04-04T01:50:30.165164Z",
+      "name": "PlasoHasherTask",
+      "reason": "4444",
+      "report_data": null,
+      "report_priority": 50,
+      "request_id": "3a5759372b594c0bb2a81cda805ca9a0",
+      "requester": "vscode",
+      "run_time": 627.349119,
+      "saved_paths": [
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680572399-a8b36b35ceaa495cab255e19691dc023-PlasoHasherTask/stderr-_8t687tl.txt",
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680572399-a8b36b35ceaa495cab255e19691dc023-PlasoHasherTask/stdout-d3t8zrby.txt",
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680572399-a8b36b35ceaa495cab255e19691dc023-PlasoHasherTask/a8b36b35ceaa495cab255e19691dc023.log",
+        "/tmp/3a5759372b594c0bb2a81cda805ca9a0/1680572399-a8b36b35ceaa495cab255e19691dc023-PlasoHasherTask/a8b36b35ceaa495cab255e19691dc023.plaso",
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680572399-a8b36b35ceaa495cab255e19691dc023-PlasoHasherTask/a8b36b35ceaa495cab255e19691dc023.plaso",
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680572399-a8b36b35ceaa495cab255e19691dc023-PlasoHasherTask/a8b36b35ceaa495cab255e19691dc023.plaso.metadata.json",
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680572399-a8b36b35ceaa495cab255e19691dc023-PlasoHasherTask/worker-log.txt"
+      ],
+      "status": "Completed successfully in 0:10:27.349119 on turbinia-worker-5867565d6c-tmgck",
+      "successful": true,
+      "worker_name": "turbinia-worker-5867565d6c-tmgck"
+    },
+    {
+      "all_args": "",
+      "evidence_name": "GoogleCloudDisk",
+      "group_id": "6b8410cd41a046c1937635e992ccbe54",
+      "group_name": "",
+      "id": "aadf2b9f713d45e499cedf19f4c02023",
+      "instance": "osdfir-cluster",
+      "job_id": "d9bbdadb13234dbe8ed5808945208356",
+      "last_update": "2023-04-03T22:39:20.218421Z",
+      "name": "FileArtifactExtractionTask",
+      "reason": "4444",
+      "report_data": null,
+      "report_priority": 50,
+      "request_id": "3a5759372b594c0bb2a81cda805ca9a0",
+      "requester": "vscode",
+      "run_time": 4.756657,
+      "saved_paths": [
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561550-aadf2b9f713d45e499cedf19f4c02023-FileArtifactExtractionTask/aadf2b9f713d45e499cedf19f4c02023.log",
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561550-aadf2b9f713d45e499cedf19f4c02023-FileArtifactExtractionTask/stderr-82mlj9kt.txt",
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561550-aadf2b9f713d45e499cedf19f4c02023-FileArtifactExtractionTask/stdout-pj5_rg8f.txt",
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561550-aadf2b9f713d45e499cedf19f4c02023-FileArtifactExtractionTask/export/etc/ssh/sshd_config",
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561550-aadf2b9f713d45e499cedf19f4c02023-FileArtifactExtractionTask/export/etc/ssh/sshd_config",
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561550-aadf2b9f713d45e499cedf19f4c02023-FileArtifactExtractionTask/worker-log.txt"
+      ],
+      "status": "Extracted 1 new SshdConfigFile artifacts",
+      "successful": true,
+      "worker_name": "turbinia-worker-5867565d6c-f5sk7"
+    },
+    {
+      "all_args": "",
+      "evidence_name": "GoogleCloudDisk",
+      "group_id": "6b8410cd41a046c1937635e992ccbe54",
+      "group_name": "",
+      "id": "e5a40f09cb404fb18649935d78e56ab2",
+      "instance": "osdfir-cluster",
+      "job_id": "97c66458cfbc4d56aa4396c1de0e211a",
+      "last_update": "2023-04-03T22:39:20.163794Z",
+      "name": "FileArtifactExtractionTask",
+      "reason": "4444",
+      "report_data": null,
+      "report_priority": 50,
+      "request_id": "3a5759372b594c0bb2a81cda805ca9a0",
+      "requester": "vscode",
+      "run_time": 4.793538,
+      "saved_paths": [
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561550-e5a40f09cb404fb18649935d78e56ab2-FileArtifactExtractionTask/e5a40f09cb404fb18649935d78e56ab2.log",
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561550-e5a40f09cb404fb18649935d78e56ab2-FileArtifactExtractionTask/stdout-3mbjyxra.txt",
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561550-e5a40f09cb404fb18649935d78e56ab2-FileArtifactExtractionTask/stderr-3otvuj1j.txt",
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561550-e5a40f09cb404fb18649935d78e56ab2-FileArtifactExtractionTask/worker-log.txt"
+      ],
+      "status": "Extracted 0 new NginxAccessLogs artifacts",
+      "successful": true,
+      "worker_name": "turbinia-worker-5867565d6c-7kljc"
+    },
+    {
+      "all_args": "",
+      "evidence_name": "GoogleCloudDisk",
+      "group_id": "6b8410cd41a046c1937635e992ccbe54",
+      "group_name": "",
+      "id": "b3ad42956aac45e6b48d09ed98cc36fe",
+      "instance": "osdfir-cluster",
+      "job_id": "4b32ccb6ebd045e7bfa8874cf4289545",
+      "last_update": "2023-04-03T22:39:20.159010Z",
+      "name": "DfdeweyTask",
+      "reason": "4444",
+      "report_data": null,
+      "report_priority": 50,
+      "request_id": "3a5759372b594c0bb2a81cda805ca9a0",
+      "requester": "vscode",
+      "run_time": 0.000338,
+      "saved_paths": [
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561550-b3ad42956aac45e6b48d09ed98cc36fe-DfdeweyTask/worker-log.txt"
+      ],
+      "status": "Not running dfDewey. Case was not provided in task config.",
+      "successful": true,
+      "worker_name": "turbinia-worker-5867565d6c-56m7t"
+    },
+    {
+      "all_args": "",
+      "evidence_name": "GoogleCloudDisk:/p1",
+      "group_id": "6b8410cd41a046c1937635e992ccbe54",
+      "group_name": "",
+      "id": "5e754481f5714f2d9d8358fb7760be53",
+      "instance": "osdfir-cluster",
+      "job_id": "fa5a633ed49e4e4da3c04973dbeaf416",
+      "last_update": "2023-04-03T22:39:30.297954Z",
+      "name": "YaraAnalysisTask",
+      "reason": "4444",
+      "report_data": "",
+      "report_priority": 80,
+      "request_id": "3a5759372b594c0bb2a81cda805ca9a0",
+      "requester": "vscode",
+      "run_time": 0.717501,
+      "saved_paths": [
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561560-5e754481f5714f2d9d8358fb7760be53-YaraAnalysisTask/5e754481f5714f2d9d8358fb7760be53_fraken_stderr.log",
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561560-5e754481f5714f2d9d8358fb7760be53-YaraAnalysisTask/worker-log.txt"
+      ],
+      "status": "No Yara rules matched",
+      "successful": true,
+      "worker_name": "turbinia-worker-5867565d6c-7m25v"
+    },
+    {
+      "all_args": "",
+      "evidence_name": "GoogleCloudDisk:/p2",
+      "group_id": "6b8410cd41a046c1937635e992ccbe54",
+      "group_name": "",
+      "id": "681687d654294e249adc09070ff3f8e1",
+      "instance": "osdfir-cluster",
+      "job_id": "d0f999ccbc0343d08269fe9848a5085a",
+      "last_update": "2023-04-03T22:39:40.364820Z",
+      "name": "PostgresAccountAnalysisTask",
+      "reason": "4444",
+      "report_data": "",
+      "report_priority": 80,
+      "request_id": "3a5759372b594c0bb2a81cda805ca9a0",
+      "requester": "vscode",
+      "run_time": 10.980598,
+      "saved_paths": [
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561560-681687d654294e249adc09070ff3f8e1-PostgresAccountAnalysisTask/postgres_account_analysis.txt",
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561560-681687d654294e249adc09070ff3f8e1-PostgresAccountAnalysisTask/postgres_account_analysis.txt",
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561560-681687d654294e249adc09070ff3f8e1-PostgresAccountAnalysisTask/worker-log.txt"
+      ],
+      "status": "No weak passwords found",
+      "successful": true,
+      "worker_name": "turbinia-worker-5867565d6c-7kljc"
+    },
+    {
+      "all_args": "",
+      "evidence_name": "GoogleCloudDisk",
+      "group_id": "6b8410cd41a046c1937635e992ccbe54",
+      "group_name": "",
+      "id": "ae0a60de04e844729e96f9539b68656d",
+      "instance": "osdfir-cluster",
+      "job_id": "f9bbf033fd3f471a96c13513af603a0f",
+      "last_update": "2023-04-03T22:39:30.284626Z",
+      "name": "JenkinsAnalysisTask",
+      "reason": "4444",
+      "report_data": "#### No Jenkins instance found\n* Jenkins version: Unknown",
+      "report_priority": 80,
+      "request_id": "3a5759372b594c0bb2a81cda805ca9a0",
+      "requester": "vscode",
+      "run_time": 19.818566,
+      "saved_paths": [
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561550-ae0a60de04e844729e96f9539b68656d-JenkinsAnalysisTask/jenkins_analysis.txt",
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561550-ae0a60de04e844729e96f9539b68656d-JenkinsAnalysisTask/jenkins_analysis.txt",
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561550-ae0a60de04e844729e96f9539b68656d-JenkinsAnalysisTask/worker-log.txt"
+      ],
+      "status": "No Jenkins instance found",
+      "successful": true,
+      "worker_name": "turbinia-worker-5867565d6c-wh9mf"
+    },
+    {
+      "all_args": "",
+      "evidence_name": "GoogleCloudDisk",
+      "group_id": "6b8410cd41a046c1937635e992ccbe54",
+      "group_name": "",
+      "id": "79949e00419a4bfaa5e1ab78c77ef38f",
+      "instance": "osdfir-cluster",
+      "job_id": "d49220e9e70040fe90a0fbc3114c2d15",
+      "last_update": "2023-04-03T22:39:20.168786Z",
+      "name": "FileArtifactExtractionTask",
+      "reason": "4444",
+      "report_data": null,
+      "report_priority": 50,
+      "request_id": "3a5759372b594c0bb2a81cda805ca9a0",
+      "requester": "vscode",
+      "run_time": 4.678717,
+      "saved_paths": [
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561550-79949e00419a4bfaa5e1ab78c77ef38f-FileArtifactExtractionTask/stderr-srjl2zto.txt",
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561550-79949e00419a4bfaa5e1ab78c77ef38f-FileArtifactExtractionTask/stdout-u95zjv35.txt",
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561550-79949e00419a4bfaa5e1ab78c77ef38f-FileArtifactExtractionTask/79949e00419a4bfaa5e1ab78c77ef38f.log",
+        "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680561550-79949e00419a4bfaa5e1ab78c77ef38f-FileArtifactExtractionTask/worker-log.txt"
+      ],
+      "status": "Extracted 0 new JupyterConfigFile artifacts",
+      "successful": true,
+      "worker_name": "turbinia-worker-5867565d6c-khxtx"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/tests/lib/processors/turbinia_artifact.py b/tests/lib/processors/turbinia_artifact_legacy.py
similarity index 80%
rename from tests/lib/processors/turbinia_artifact.py
rename to tests/lib/processors/turbinia_artifact_legacy.py
index 91ed62fca..1e25b4fa6 100644
--- a/tests/lib/processors/turbinia_artifact.py
+++ b/tests/lib/processors/turbinia_artifact_legacy.py
@@ -15,26 +15,33 @@
 os.environ['TURBINIA_CONFIG_PATH'] = os.path.join(current_dir, 'test_data')
 # pylint: disable=wrong-import-position
 from dftimewolf.lib.containers import containers
-from dftimewolf.lib.processors import turbinia_artifact
-
 from dftimewolf import config
 
-# Manually set TURBINIA_PROJECT to the value we expect.
-# pylint: disable=wrong-import-position, wrong-import-order
-from turbinia import config as turbinia_config
-from turbinia import message as turbinia_message
+HAS_TURBINIA = False
+
+try:
+  from dftimewolf.lib.processors import turbinia_artifact_legacy
+  from turbinia import config as turbinia_config
+  from turbinia import message as turbinia_message
+  # Manually set TURBINIA_PROJECT to the value we expect.
+  turbinia_config.TURBINIA_PROJECT = 'turbinia-project'
+  HAS_TURBINIA = True
+except ImportError:
+  pass
 
-turbinia_config.TURBINIA_PROJECT = 'turbinia-project'
 YARA_RULE = """rule dummy { condition: false }"""
 
 
+@unittest.skipIf(not HAS_TURBINIA, 'Missing Turbinia dependency.')
 class TurbiniaArtifactProcessorTest(unittest.TestCase):
   """Tests for the Turbinia processor."""
 
   def testInitialization(self):
     """Tests that the processor can be initialized."""
     test_state = state.DFTimewolfState(config.Config)
-    turbinia_processor = turbinia_artifact.TurbiniaArtifactProcessor(test_state)
+    # pylint: disable=line-too-long
+    turbinia_processor = turbinia_artifact_legacy.TurbiniaArtifactProcessorLegacy(
+        test_state)
     self.assertIsNotNone(turbinia_processor)
 
   @mock.patch('turbinia.client.get_turbinia_client')
@@ -42,7 +49,9 @@ def testInitialization(self):
   def testSetup(self, _mock_TurbiniaClient):
     """Tests that the processor is set up correctly."""
     test_state = state.DFTimewolfState(config.Config)
-    turbinia_processor = turbinia_artifact.TurbiniaArtifactProcessor(test_state)
+    # pylint: disable=line-too-long
+    turbinia_processor = turbinia_artifact_legacy.TurbiniaArtifactProcessorLegacy(
+        test_state)
     turbinia_processor.SetUp(
         turbinia_config_file=None,
         project='turbinia-project',
@@ -51,7 +60,8 @@ def testSetup(self, _mock_TurbiniaClient):
         output_directory='/tmp/outputdir',
         sketch_id=123)
     # pylint: disable=line-too-long
-    turbinia_processor.client.create_request.return_value = turbinia_message.TurbiniaRequest()
+    turbinia_processor.client.create_request.return_value = turbinia_message.TurbiniaRequest(
+    )
     self.assertEqual(turbinia_processor.project, 'turbinia-project')
     self.assertEqual(turbinia_processor.turbinia_recipe, None)
     self.assertEqual(turbinia_processor.turbinia_zone, 'europe-west1')
@@ -70,7 +80,9 @@ def testProcess(self, _mock_TurbiniaClient):
         received from the state.
     """
     test_state = state.DFTimewolfState(config.Config)
-    turbinia_processor = turbinia_artifact.TurbiniaArtifactProcessor(test_state)
+    # pylint: disable=line-too-long
+    turbinia_processor = turbinia_artifact_legacy.TurbiniaArtifactProcessorLegacy(
+        test_state)
     turbinia_processor.StoreContainer(
         containers.RemoteFSPath(hostname='remotehost', path='/tmp/file.ext'))
     turbinia_processor.SetUp(
@@ -81,7 +93,8 @@ def testProcess(self, _mock_TurbiniaClient):
         output_directory='/tmp/outputdir',
         sketch_id=123)
     # pylint: disable=line-too-long
-    turbinia_processor.client.create_request.return_value = turbinia_message.TurbiniaRequest()
+    turbinia_processor.client.create_request.return_value = turbinia_message.TurbiniaRequest(
+    )
     turbinia_processor.client.get_task_data.return_value = [{
         'saved_paths': [
             '/fake/data.plaso',
diff --git a/tests/lib/processors/turbinia_base.py b/tests/lib/processors/turbinia_base.py
new file mode 100644
index 000000000..059484fd0
--- /dev/null
+++ b/tests/lib/processors/turbinia_base.py
@@ -0,0 +1,116 @@
+"""Tests the Turbinia processor."""
+
+import unittest
+import json
+import os
+import mock
+
+from dftimewolf.lib.processors import turbinia_base
+from dftimewolf import config
+from dftimewolf.lib import state
+
+YARA_RULE = """rule dummy { condition: false }"""
+CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))
+TASK_ID = "3a5759372b594c0bb2a81cda805ca9a0"
+# pylint: disable=line-too-long
+TEST_TASK_PATH = "/mnt/turbiniavolume/output/3a5759372b594c0bb2a81cda805ca9a0/1680565159-c4e9abd577db475484b2ded34a011b96-PlasoParserTask/c4e9abd577db475484b2ded34a011b96.plaso"
+
+
+class TurbiniaBaseTest(unittest.TestCase):
+  """Tests for the Turbinia processor."""
+
+  def setUp(self):
+    """Tests that the processor can be initialized."""
+    self.logger = mock.MagicMock()
+    test_state = state.DFTimewolfState(config.Config)
+    self.turbinia_processor = turbinia_base.TurbiniaProcessorBase(
+        test_state, self.logger)
+    file_path = os.path.join(
+        CURRENT_DIR, "test_data", "turbinia_request_status.json")
+    self._request_status = json.load(open(file_path))
+
+  # pylint: disable=line-too-long
+  @mock.patch(
+      "turbinia_api_lib.api.turbinia_configuration_api.TurbiniaConfigurationApi.read_config"
+  )
+  def testTurbiniaSetup(self, _mock_read_config):
+    """Tests the TurbiniaSetup method."""
+    _mock_read_config.return_value = {"OUTPUT_DIR": "/tmp"}
+    self.turbinia_processor.TurbiniaSetUp(
+        project="turbinia-project",
+        turbinia_auth=False,
+        turbinia_recipe=None,
+        turbinia_zone="us-central1f",
+        turbinia_api="http://localhost:8001",
+        incident_id="123456789",
+        sketch_id="12345",
+    )
+    self.assertEqual(self.turbinia_processor.project, "turbinia-project")
+    self.assertEqual(self.turbinia_processor.turbinia_zone, "us-central1f")
+    self.assertEqual(
+        self.turbinia_processor.turbinia_api, "http://localhost:8001")
+    self.assertEqual(self.turbinia_processor.incident_id, "123456789")
+    self.assertEqual(self.turbinia_processor.sketch_id, "12345")
+    self.assertEqual(self.turbinia_processor.output_path, "/tmp")
+    self.assertEqual(self.turbinia_processor.turbinia_recipe, None)
+
+  # pylint: disable=line-too-long
+  @mock.patch(
+      "turbinia_api_lib.api.turbinia_requests_api.TurbiniaRequestsApi.create_request"
+  )
+  def testTurbiniaStart(self, mock_create_request):
+    """Tests the TurbiniaStart method."""
+    mock_create_request.return_value = {
+        "request_id": "41483253079448e59685d88f37ab91f7"
+    }
+    mock_api_instance = mock.MagicMock()
+    mock_api_instance.create_request = mock_create_request
+    self.turbinia_processor.requests_api_instance = mock_api_instance
+    evidence = {
+        "type": "GoogleCloudDisk",
+        "disk_name": "disk-1",
+        "project": "project-1",
+        "zone": "us-central1-f",
+    }
+    request_id = self.turbinia_processor.TurbiniaStart(
+        evidence=evidence, yara_rules=YARA_RULE)
+    self.assertEqual(request_id, "41483253079448e59685d88f37ab91f7")
+
+  # pylint: disable=line-too-long
+  @mock.patch(
+      "turbinia_api_lib.api.turbinia_requests_api.TurbiniaRequestsApi.get_request_status"
+  )
+  @mock.patch("time.sleep")
+  def testTurbiniaWait(self, mock_get_request_status, _):
+    """Tests the TurbiniaWait method."""
+    mock_api_instance = mock.MagicMock()
+    mock_api_instance.create_request = mock_get_request_status
+    self.turbinia_processor.requests_api_instance = mock_api_instance
+    mock_get_request_status.return_value = self._request_status
+    for task, path in self.turbinia_processor.TurbiniaWait(TASK_ID):
+      # Check that the task and path are correct for a PlasoParserTask
+      if task["id"] == TASK_ID:
+        self.assertEqual(task, self._request_status["tasks"][0])
+        self.assertEqual(path, TEST_TASK_PATH)
+      break
+
+  def testIsInterestingPath(self):
+    """Tests the _isInterestingPath method in TurbiniaProcessorBase."""
+    # pylint: disable=protected-access
+    self.assertTrue(self.turbinia_processor._isInterestingPath(TEST_TASK_PATH))
+
+  @mock.patch('tempfile.mkdtemp')
+  def testExtractPath(self, mock_tempdir):
+    """Tests the _ExtractFiles method in TurbiniaProcessorBase."""
+    mock_tempdir.return_value = '/tmp'
+    file_path = os.path.join(
+        CURRENT_DIR, "test_data", "c4e9abd577db475484b2ded34a011b96.tgz")
+    expected_local_path = f"/tmp{TEST_TASK_PATH}"
+    # pylint: disable=protected-access
+    local_path = self.turbinia_processor._ExtractFiles(
+        file_path, TEST_TASK_PATH)
+    self.assertEqual(local_path, expected_local_path)
+
+
+if __name__ == "__main__":
+  unittest.main()
diff --git a/tests/lib/processors/turbinia_gcp.py b/tests/lib/processors/turbinia_gcp_legacy.py
similarity index 88%
rename from tests/lib/processors/turbinia_gcp.py
rename to tests/lib/processors/turbinia_gcp_legacy.py
index 0aa37af5a..3b44b2937 100644
--- a/tests/lib/processors/turbinia_gcp.py
+++ b/tests/lib/processors/turbinia_gcp_legacy.py
@@ -1,5 +1,3 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
 """Tests the Turbinia processor."""
 
 import os
@@ -17,27 +15,32 @@
 os.environ['TURBINIA_CONFIG_PATH'] = os.path.join(current_dir, 'test_data')
 # pylint: disable=wrong-import-position
 from dftimewolf.lib.containers import containers
-from dftimewolf.lib.processors import turbinia_gcp
-
 from dftimewolf import config
 
-# Manually set TURBINIA_PROJECT to the value we expect.
-# pylint: disable=wrong-import-position, wrong-import-order
-from turbinia import config as turbinia_config
-from turbinia import message as turbinia_message
+HAS_TURBINIA = False
 
-turbinia_config.TURBINIA_PROJECT = 'turbinia-project'
+try:
+  from dftimewolf.lib.processors import turbinia_gcp_legacy
+  from turbinia import config as turbinia_config
+  from turbinia import message as turbinia_message
+  # Manually set TURBINIA_PROJECT to the value we expect.
+  turbinia_config.TURBINIA_PROJECT = 'turbinia-project'
+  HAS_TURBINIA = True
+except ImportError:
+  pass
 
 YARA_RULE = """rule dummy { condition: false }"""
 
 
+@unittest.skipIf(not HAS_TURBINIA, 'Missing Turbinia dependency.')
 class TurbiniaGCPProcessorTest(unittest.TestCase):
   """Tests for the Turbinia processor."""
 
   def testInitialization(self):
     """Tests that the processor can be initialized."""
     test_state = state.DFTimewolfState(config.Config)
-    turbinia_processor = turbinia_gcp.TurbiniaGCPProcessor(test_state)
+    turbinia_processor = turbinia_gcp_legacy.TurbiniaGCPProcessorLegacy(
+        test_state)
     self.assertIsNotNone(turbinia_processor)
 
   @mock.patch('turbinia.client.get_turbinia_client')
@@ -45,7 +48,8 @@ def testInitialization(self):
   def testSetup(self, _mock_TurbiniaClient):
     """Tests that the processor is set up correctly."""
     test_state = state.DFTimewolfState(config.Config)
-    turbinia_processor = turbinia_gcp.TurbiniaGCPProcessor(test_state)
+    turbinia_processor = turbinia_gcp_legacy.TurbiniaGCPProcessorLegacy(
+        test_state)
     turbinia_processor.SetUp(
         turbinia_config_file=None,
         disk_names='disk-1',
@@ -54,7 +58,8 @@ def testSetup(self, _mock_TurbiniaClient):
         turbinia_zone='europe-west1',
         sketch_id=123)
     # pylint: disable=line-too-long
-    turbinia_processor.client.create_request.return_value = turbinia_message.TurbiniaRequest()
+    turbinia_processor.client.create_request.return_value = turbinia_message.TurbiniaRequest(
+    )
     self.assertEqual(turbinia_processor.project, 'turbinia-project')
     self.assertEqual(turbinia_processor.turbinia_zone, 'europe-west1')
     self.assertEqual(turbinia_processor.turbinia_recipe, None)
@@ -71,7 +76,7 @@ def testSetup(self, _mock_TurbiniaClient):
     # pylint: disable=no-member
     self.assertEqual(
         turbinia_processor.turbinia_region,
-        turbinia_gcp.turbinia_config.TURBINIA_REGION)
+        turbinia_gcp_legacy.turbinia_config.TURBINIA_REGION)
     # pylint: disable=protected-access
     six.assertRegex(
         self, turbinia_processor._output_path, '(/tmp/tmp|/var/folders).+')
@@ -81,7 +86,8 @@ def testSetup(self, _mock_TurbiniaClient):
   def testWrongProject(self, _mock_TurbiniaClient):
     """Tests that specifying the wrong Turbinia project generates an error."""
     test_state = state.DFTimewolfState(config.Config)
-    turbinia_processor = turbinia_gcp.TurbiniaGCPProcessor(test_state)
+    turbinia_processor = turbinia_gcp_legacy.TurbiniaGCPProcessorLegacy(
+        test_state)
     with self.assertRaises(errors.DFTimewolfError) as error:
       turbinia_processor.SetUp(
           turbinia_config_file=None,
@@ -91,7 +97,8 @@ def testWrongProject(self, _mock_TurbiniaClient):
           turbinia_zone='europe-west1',
           sketch_id=1234)
       # pylint: disable=line-too-long
-      turbinia_processor.client.create_request.return_value = turbinia_message.TurbiniaRequest()
+      turbinia_processor.client.create_request.return_value = turbinia_message.TurbiniaRequest(
+      )
     self.assertEqual(len(test_state.errors), 1)
     self.assertEqual(test_state.errors[0], error.exception)
     error_msg = error.exception.message
@@ -131,7 +138,8 @@ def testWrongSetup(self, _mock_TurbiniaClient, mock_turbinia_config):
       mock_turbinia_config.TURBINIA_PROJECT = combination['project']
       mock_turbinia_config.TURBINIA_ZONE = combination['turbinia_zone']
       test_state = state.DFTimewolfState(config.Config)
-      turbinia_processor = turbinia_gcp.TurbiniaGCPProcessor(test_state)
+      turbinia_processor = turbinia_gcp_legacy.TurbiniaGCPProcessorLegacy(
+          test_state)
       with self.assertRaises(errors.DFTimewolfError) as error:
         turbinia_processor.SetUp(**combination)
 
@@ -154,7 +162,8 @@ def testProcessFromParams(
     passed in as a parameter."""
 
     test_state = state.DFTimewolfState(config.Config)
-    turbinia_processor = turbinia_gcp.TurbiniaGCPProcessor(test_state)
+    turbinia_processor = turbinia_gcp_legacy.TurbiniaGCPProcessorLegacy(
+        test_state)
     turbinia_processor.StoreContainer(
         containers.YaraRule(
             name='dummy_yara', rule_text="rule dummy { condition: false }"))
@@ -231,7 +240,8 @@ def testProcessFromParams(
         'gs://BinaryExtractorTask.tar.gz',
         local_output_dir=turbinia_processor._output_path)
     self.assertEqual(test_state.errors, [])
-    ti_containers = turbinia_processor.GetContainers(containers.ThreatIntelligence)
+    ti_containers = turbinia_processor.GetContainers(
+        containers.ThreatIntelligence)
     file_containers = turbinia_processor.GetContainers(containers.File)
 
     # Make sure that file.txt is ignored
@@ -257,13 +267,13 @@ def testProcessFromState(
     received from the state.
     """
     test_state = state.DFTimewolfState(config.Config)
-    turbinia_processor = turbinia_gcp.TurbiniaGCPProcessor(test_state)
+    turbinia_processor = turbinia_gcp_legacy.TurbiniaGCPProcessorLegacy(
+        test_state)
     turbinia_processor.StoreContainer(
-      containers.YaraRule(
-        name='dummy_yara', rule_text="rule dummy { condition: false }")
-    )
-    turbinia_processor.StoreContainer(containers.GCEDisk(
-        name='disk-1', project='turbinia-project'))
+        containers.YaraRule(
+            name='dummy_yara', rule_text="rule dummy { condition: false }"))
+    turbinia_processor.StoreContainer(
+        containers.GCEDisk(name='disk-1', project='turbinia-project'))
     turbinia_processor.SetUp(
         turbinia_config_file=None,
         project='turbinia-project',
@@ -333,7 +343,8 @@ def testProcessFromState(
         'gs://BinaryExtractorTask.tar.gz',
         local_output_dir=turbinia_processor._output_path)
     self.assertEqual(test_state.errors, [])
-    ti_containers = turbinia_processor.GetContainers(containers.ThreatIntelligence)
+    ti_containers = turbinia_processor.GetContainers(
+        containers.ThreatIntelligence)
     file_containers = turbinia_processor.GetContainers(containers.File)
 
     # Make sure that file.txt is ignored
@@ -350,14 +361,13 @@ def testProcessFromState(
   @mock.patch('turbinia.evidence.GoogleCloudDisk')
   @mock.patch('turbinia.client.get_turbinia_client')
   # pylint: disable=invalid-name
-  def testProcessCrossProject(self,
-                              _mock_TurbiniaClient,
-                              mock_GoogleCloudDisk):
+  def testProcessCrossProject(self, _mock_TurbiniaClient, mock_GoogleCloudDisk):
     """Tests that process does nothing if the disks are in another project."""
     test_state = state.DFTimewolfState(config.Config)
-    turbinia_processor = turbinia_gcp.TurbiniaGCPProcessor(test_state)
-    turbinia_processor.StoreContainer(containers.GCEDisk(
-        name='disk-1', project='another-project'))
+    turbinia_processor = turbinia_gcp_legacy.TurbiniaGCPProcessorLegacy(
+        test_state)
+    turbinia_processor.StoreContainer(
+        containers.GCEDisk(name='disk-1', project='another-project'))
     turbinia_processor.SetUp(
         turbinia_config_file=None,
         project='turbinia-project',
@@ -390,7 +400,8 @@ def _fake_copy(filename):
       return '/fake/local/' + filename.rsplit('/')[-1]
 
     test_state = state.DFTimewolfState(config.Config)
-    turbinia_processor = turbinia_gcp.TurbiniaGCPProcessor(test_state)
+    turbinia_processor = turbinia_gcp_legacy.TurbiniaGCPProcessorLegacy(
+        test_state)
     mock_GCSOutputWriter.return_value.copy_from = _fake_copy
     fake_paths = ['gs://hashes.json', 'gs://results.plaso']
     # pylint: disable=protected-access
@@ -402,7 +413,8 @@ def _fake_copy(filename):
   def testDeterminePaths(self):
     """Tests _DeterminePaths"""
     test_state = state.DFTimewolfState(config.Config)
-    turbinia_processor = turbinia_gcp.TurbiniaGCPProcessor(test_state)
+    turbinia_processor = turbinia_gcp_legacy.TurbiniaGCPProcessorLegacy(
+        test_state)
     fake_task_data = [{
         'saved_paths': ['/local/path.plaso', '/ignoreme/'],
     }, {
@@ -422,7 +434,8 @@ def testPreProcess(self, _mockTurbiniaClient):
     We store a ForensicsVM container in the state, run PreProcess, then check
     that the state contains a GCEDisk as expected."""
     test_state = state.DFTimewolfState(config.Config)
-    turbinia_processor = turbinia_gcp.TurbiniaGCPProcessor(test_state)
+    turbinia_processor = turbinia_gcp_legacy.TurbiniaGCPProcessorLegacy(
+        test_state)
     turbinia_processor.StoreContainer(
         containers.ForensicsVM(
             name='ForensicsVM',
@@ -439,7 +452,8 @@ def testPreProcess(self, _mockTurbiniaClient):
         turbinia_zone='europe-west1',
         sketch_id=4567)
     # pylint: disable=line-too-long
-    turbinia_processor.client.create_request.return_value = turbinia_message.TurbiniaRequest()
+    turbinia_processor.client.create_request.return_value = turbinia_message.TurbiniaRequest(
+    )
     turbinia_processor.PreProcess()
 
     out_containers = turbinia_processor.GetContainers(containers.GCEDisk)