Repository: AgnostiqHQ/covalent
Pull request #1396 (2022-10-28 18:12:52)

Updating doc branch from develop
<!--
⚠️ Your pull request title should be short detailed and understandable for all.
⚠️ Increment the version number in the /VERSION file. If this is a bugfix, increment the patch version (the rightmost number), for example 0.18.2 becomes 0.18.3. If it's a new feature, increment the minor version (the middle number), for example 0.18.2 becomes 0.19.0.
⚠️ Add a note to /CHANGELOG.md with your version number and the date, summarizing the changes.
⚠️ If your pull request fixes an open issue, please link to the issue.
⚠️ Rebase the latest changes from the develop branch. Assuming origin points to https://github.com/AgnostiqHQ/covalent, you should run git rebase origin/develop.
-->

- [ ] I have added the tests to cover my changes.
- [x] I have updated the documentation and CHANGELOG accordingly.
- [x] I have read the CONTRIBUTING document.



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1229a808..a613f6ce 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,45 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ## [UNRELEASED]
 
+### Added
+
+- Support for transferring the contents of folders to and from S3 buckets using the file transfer module.
+
+### Fixed
+
+- Result objects now print correctly when nodes fail
+
+## [0.207.0-rc.0] - 2022-10-26
+
+### Authors
+
+- Alejandro Esquivel <ae@alejandro.ltd>
+- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
+
+
+### Changed
+
+- Running migrations automatically if none have run in the past (fresh installs, after purging)
+
+## [0.206.0-rc.0] - 2022-10-26
+
+### Authors
+
+- Akalanka <8133713+boneyag@users.noreply.github.com>
+- Co-authored-by: Will Cunningham <wjcunningham7@users.noreply.github.com>
+- Co-authored-by: Scott Wyman Neagle <scott@agnostiq.ai>
+- Scott Wyman Neagle <wymnea@protonmail.com>
+- Co-authored-by: Will Cunningham <wjcunningham7@gmail.com>
+- Co-authored-by: Alejandro Esquivel <ae@alejandro.ltd>
+- Co-authored-by: Faiyaz Hasan <faiyaz@agnostiq.ai>
+- Casey Jao <casey@agnostiq.ai>
+- Venkat Bala <15014089+venkatBala@users.noreply.github.com>
+
+
+### Docs
+
+- Updated AWS Lambda executor docs to address conflict with using public ecr registries
+
 ### Docs
 
 - Fixed missing RTD content under API section for covalent, cli, leptons, deps, data transfer
@@ -14,6 +53,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 ### Fixed
 
 - Enabling logging by default
+- Removed debugging output
+- Clarify cli output when `covalent db migrate` needs to be run
 
 ### Changed
 
diff --git a/README.md b/README.md
index 5998f3e1..1e30fe5e 100644
--- a/README.md
+++ b/README.md
@@ -13,65 +13,34 @@
 
 </div>
 
-## What is Covalent?
+## 🤔 What is Covalent?
 
-Covalent is a Python-language – and [Pythonic(https://www.udacity.com/blog/2020/09/what-is-pythonic-style.html)] – workflow tool for computational scientists, AI/ML software engineers, and anyone who needs a way to run experiments on limited or expensive computing resources. Such resources can include quantum computers, HPC clusters, GPU arrays, and cloud services. Covalent manages workflows in heterogeneous environments that contain any or all of these advanced platforms.
+Covalent is a Pythonic workflow tool used to execute HPC and quantum tasks in heterogenous environments. Computational scientists and engineers use Covalent to...
 
-Covalent enables you to:
+- rapidly iterate prototypes and exploratory research models
+- automate, manage, and share reproducible experiments
+- visualize data and task dependencies in an interactive user interface
+- run code in heterogenous compute environments, including in hybrid-cloud and hybrid-quantum configurations
+- understand where time and money is spent across a project
 
-Isolate operations that don't require advanced compute resources so you can run them on commonly available hardware.
+Covalent may be deployed locally or on a remote server. Covalent is rapidly expanding to include support for a variety of cloud interfaces, including HPC infrastructure tools developed by major cloud providers and emerging quantum APIs. It has never been easier to deploy your code on the world's most advanced computing hardware with Covalent.
 
-> Covalent dispatches tasks to any number of executors backed by heterogeneous hardware platforms. Run non-critical tasks on commodity hardware, using advanced hardware only for the heavy computations: Using a heterogeneous environment is almost as easy as running with a monolithic backend.
+Read more in the official [documentation](https://covalent.readthedocs.io/en/latest/).
 
-Test individual functions or groups of functions on local hardware before committing them to advanced hardware.
+## ✨ Features
 
-> Not only does Covalent run different workflow tasks on different platforms; but reassigning a task to a different platform is as easy as changing a single attribute in a function decorator. Test locally, then do the real work on big hardware.
+- **Purely Pythonic**: No need to learn any new syntax or mess around with YAML. Construct your complex workflow programmatically with native Python functions. By just adding one-line decorators to your functions, you can supercharge your experiments.
+- **Native parallelization**: Covalent natively parallelizes mutually independent parts of your workflow.
+- **Monitor with UI**: Covalent provides an intuitive and aesthetically beautiful browser-based user interface to monitor and manage your workflows.
+- **Abstracted dataflow**: No need to worry about the details of the underlying data structures. Covalent takes care of data dependencies in the background while you concentrate on understanding the big picture.
+- **Result management**: Covalent manages the results of your workflows. Whenever you need to modify parts of your workflow, from inputs to components, Covalent stores and saves the run of every experiment in a reproducible format.
+- **Little-to-no overhead**: Covalent is designed to be as lightweight as possible and is optimized for the most common use cases. Covalent's overhead is less than 0.1% of the total runtime for typical high compute applications and often has a constant overhead of ~ 10-100μs -- and this is constantly being optimized.
+- **Interactive**: Unlike other workflow tools, Covalent is interactive. You can view, modify, and re-submit workflows directly within a Jupyter notebook.
 
-Iteratively run prototypes and exploratory research models and save results.
-
-> Covalent saves the data required to reproduce every run of every workflow, along with its results. Manage running workflows, view task dependencies, and examine results in Covalent's intuitive browser-based UI.
-
-Automate and manage workflows from a Jupyter notebook or any other interactive Python environment.
-
-> The Covalent scheduler runs locally or on a server. Run experiments as you normally would, swapping parameters and datasets, from Jupyter or any other interactive Python environment.
-
-Run experiments faster with parallel processing.
-
-> Covalent automatically parallelizes independent tasks to accelerate job completion.
-
-Ready to try it? Skip to [Getting Started](https://covalent.readthedocs.io/en/latest/getting_started/index.html)
-
-Covalent is designed and developed from the ground up for data experimentation and prototyping. Covalent features:
-
-Pure Python
-
-> Covalent automatically constructs complex workflows when you program with native Python functions. Make your code Covalent-ready simply by adding one-line decorators to your functions.
-
-A robust user interface
-
-> Covalent provides an intuitive and aesthetic browser-based UI to monitor and manage your workflows.
-
-Result management
-
-> Covalent manages the results of your workflows. Whenever you need to modify parts of your workflow, from inputs to components, Covalent stores and saves the run of every experiment in a reproducible format.
-
-Low overhead
-
-> Covalent is designed to be as lightweight as possible and is optimized for the most common use cases. Covalent's overhead is less than 0.1% of the total runtime for typical high-compute applications and often has a constant overhead of ~ 10-100μs – and this is constantly being optimized.
-
-Interactive
-
-> Unlike other workflow tools, Covalent is interactive. You can view, modify, and re-submit workflows directly within a Jupyter notebook.
-
-Ready to try it? Skip to [Getting Started](https://covalent.readthedocs.io/en/latest/getting_started/index.html)
-
-## Is It Really That Easy?
-
-Don't be fooled the simplicity of Covalent's decorator-based code syntax. Each decorator is a wrapper hiding sophisticated task management software that analyzes function input and output and enables the Covalent server to abstract the code from the backend implementation.
+<div align="center">
 
-You do have some responsibilities when you use a Covalent decorator. Your code should be task-oriented and Pythonic. Task-oriented, because the more you use small, independent, single-purpose tasks, the more efficiently Covalent can manage your workflow. Long, shambolic scripts are not a good candidate for Covalent scheduling – at least not without refactoring.
+![covalent user interface](https://raw.githubusercontent.com/AgnostiqHQ/covalent/master/doc/source/_static/workflow_demo_image.png)
 
-Pythonic, because using Python best practices enables Covalent to automatically construct workflow graphs without having to specify graph edges explicitly in another language such as YAML.
 </div>
 
 For a more in-depth description of Covalent's features and how they work, refer to the [Concepts](https://covalent.readthedocs.io/en/latest/concepts/concepts.html) page in the documentation.
diff --git a/VERSION b/VERSION
index 222dea8d..7710f553 100644
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-0.205.0-rc.0
\ No newline at end of file
+0.207.0-rc.0
\ No newline at end of file
diff --git a/covalent/_file_transfer/strategies/s3_strategy.py b/covalent/_file_transfer/strategies/s3_strategy.py
index 37ed92bc..2ef6c5a3 100644
--- a/covalent/_file_transfer/strategies/s3_strategy.py
+++ b/covalent/_file_transfer/strategies/s3_strategy.py
@@ -69,42 +69,104 @@ class S3(FileTransferStrategy):
 
     # return callable to download here implies 'from' is a remote source
     def download(self, from_file: File, to_file: File = File()) -> File:
-        from_filepath = from_file.filepath.strip("/")
+        """Download files or the contents of folders from S3 bucket."""
+        app_log.debug(f"Is dir: {from_file._is_dir}")
+
+        from_filepath = from_file.filepath
         to_filepath = to_file.filepath
+
+        if from_filepath.startswith("/"):
+            from_filepath = from_filepath[1:]
+
         bucket_name = furl(from_file.uri).origin[5:]
         app_log.debug(
             f"S3 download bucket: {bucket_name}, from_filepath: {from_filepath}, to_filepath {to_filepath}."
         )
 
-        def callable():
-            import boto3
+        if from_file._is_dir:
 
-            s3 = boto3.client(
-                "s3",
-                region_name=self.region_name,
-            )
-            s3.download_file(bucket_name, from_filepath, to_filepath)
+            def callable():
+                """Download files from a folder in s3 bucket."""
+                from pathlib import Path
+
+                import boto3
+
+                s3 = boto3.client(
+                    "s3",
+                    region_name=self.region_name,
+                )
+                for obj_metadata in s3.list_objects(Bucket=bucket_name, Prefix=from_filepath)[
+                    "Contents"
+                ]:
+                    obj_key = obj_metadata["Key"]
+                    obj_destination_filepath = Path(to_filepath) / obj_key
+                    if obj_key.endswith("/"):
+                        obj_destination_filepath.mkdir(parents=True, exist_ok=True)
+                    else:
+                        s3.download_file(bucket_name, obj_key, str(obj_destination_filepath))
+
+        else:
+
+            def callable():
+                """Download file from s3 bucket."""
+                import boto3
+
+                s3 = boto3.client(
+                    "s3",
+                    region_name=self.region_name,
+                )
+                s3.download_file(bucket_name, from_filepath, to_filepath)
 
         return callable
 
     # return callable to download here implies 'to' is a remote source
     def upload(self, from_file: File, to_file: File = File()) -> File:
-
+        """Upload files or folders to S3 bucket."""
         from_filepath = from_file.filepath
-        to_filepath = to_file.filepath.strip("/")
+        to_filepath = to_file.filepath
+
+        if to_filepath.startswith("/"):
+            to_filepath = to_filepath.strip("/")
+
         bucket_name = furl(to_file.uri).origin[5:]
         app_log.debug(
-            f"S3 download bucket: {bucket_name}, from_filepath: {from_filepath}, to_filepath {to_filepath}."
+            f"S3 upload bucket: {bucket_name}, from_filepath: {from_filepath}, to_filepath {to_filepath}."
         )
 
-        def callable():
-            import boto3
+        if from_file._is_dir:
 
-            s3 = boto3.client(
-                "s3",
-                region_name=self.region_name,
-            )
-            s3.upload_file(from_filepath, bucket_name, to_filepath)
+            def callable():
+                """List and upload files from a directory to the remote S3 bucket."""
+                import os
+                from pathlib import Path
+
+                import boto3
+
+                s3 = boto3.client(
+                    "s3",
+                    region_name=self.region_name,
+                )
+
+                for dir_, _, files in os.walk(from_filepath):
+                    for file_name in files:
+                        rel_dir = os.path.relpath(dir_, from_filepath)
+                        rel_file = os.path.join(rel_dir, file_name)
+                        obj_from_filepath = str(Path(from_filepath) / rel_file)
+                        obj_to_filepath = str(Path(to_filepath) / rel_file)
+                        app_log.debug(f"Uploading from {obj_from_filepath} to {obj_to_filepath}.")
+                        s3.upload_file(obj_from_filepath, bucket_name, obj_to_filepath)
+
+        else:
+
+            def callable():
+                """Upload file to remote S3 bucket."""
+                import boto3
+
+                s3 = boto3.client(
+                    "s3",
+                    region_name=self.region_name,
+                )
+                s3.upload_file(from_filepath, bucket_name, to_filepath)
 
         return callable
 
diff --git a/covalent/_results_manager/result.py b/covalent/_results_manager/result.py
index 6851175b..c0571336 100644
--- a/covalent/_results_manager/result.py
+++ b/covalent/_results_manager/result.py
@@ -124,7 +124,7 @@ Node Outputs
 
         node_outputs = self.get_all_node_outputs()
         for k, v in node_outputs.items():
-            show_result_str += f"{k}: {v.object_string}\n"
+            show_result_str += f"{k}: {TransportableObject.make_transportable(v).object_string}\n"
 
         return show_result_str
 
diff --git a/covalent_dispatcher/_cli/service.py b/covalent_dispatcher/_cli/service.py
index fdf3b4c8..2e5d937d 100644
--- a/covalent_dispatcher/_cli/service.py
+++ b/covalent_dispatcher/_cli/service.py
@@ -49,8 +49,10 @@ UI_PIDFILE = get_config("dispatcher.cache_dir") + "/ui.pid"
 UI_LOGFILE = get_config("user_interface.log_dir") + "/covalent_ui.log"
 UI_SRVDIR = f"{os.path.dirname(os.path.abspath(__file__))}/../../covalent_ui"
 
-MIGRATION_WARNING_MSG = "There have been changes applied to the database."
-MIGRATION_COMMAND_MSG = '   (use "covalent db migrate" to run database migrations)'
+MIGRATION_WARNING_MSG = "Covalent not started. The database needs to be upgraded."
+MIGRATION_COMMAND_MSG = (
+    '   (use "covalent db migrate" to run database migrations and then retry "covalent start")'
+)
 
 
 def _read_pid(filename: str) -> int:
@@ -317,11 +319,22 @@ def start(
         set_config({"sdk.log_level": "debug"})
 
     db = DataStore.factory()
+
+    # No migrations have run as of yet - run them automatically
+    if not ignore_migrations and db.current_revision() is None:
+        db.run_migrations(logging_enabled=False)
+
     if db.is_migration_pending and not ignore_migrations:
         click.secho(MIGRATION_WARNING_MSG, fg="yellow")
         click.echo(MIGRATION_COMMAND_MSG)
         return ctx.exit(1)
 
+    if ignore_migrations and db.is_migration_pending:
+        click.secho(
+            'Warning: Ignoring migrations is not recommended and may have unanticipated side effects. Use "covalent db migrate" to run migrations.',
+            fg="yellow",
+        )
+
     set_config("user_interface.port", port)
     set_config("dispatcher.port", port)
 
diff --git a/covalent_dispatcher/_db/dispatchdb.py b/covalent_dispatcher/_db/dispatchdb.py
index 54ae98e7..f8cf1c70 100644
--- a/covalent_dispatcher/_db/dispatchdb.py
+++ b/covalent_dispatcher/_db/dispatchdb.py
@@ -31,8 +31,6 @@ from covalent._shared_files import logger
 from covalent._shared_files.config import get_config
 from covalent._shared_files.util_classes import Status
 
-from .datastore import DataStore
-
 app_log = logger.app_log
 log_stack_info = logger.log_stack_info
 # DB Schema:
@@ -177,8 +175,3 @@ class DispatchDB:
 
     def __exit__(self, exc_type, exc_value, traceback):
         return False
-
-    def _get_data_store(self, initialize_db: bool = False) -> DataStore:
-        """Return the DataStore instance to write records."""
-
-        return DataStore(db_URL=f"sqlite+pysqlite:///{self._dbpath}", initialize_db=initialize_db)
diff --git a/covalent_dispatcher/_db/write_result_to_db.py b/covalent_dispatcher/_db/write_result_to_db.py
index b5ad3fff..366981d7 100644
--- a/covalent_dispatcher/_db/write_result_to_db.py
+++ b/covalent_dispatcher/_db/write_result_to_db.py
@@ -318,7 +318,6 @@ def update_electrons_data(
 ) -> None:
     """This function updates the electrons record."""
 
-    print("DEBUG: update_electrons_data called on node", transport_graph_node_id)
     with workflow_db.session() as session:
         parent_lattice_id = (
             session.query(Lattice).where(Lattice.dispatch_id == parent_dispatch_id).all()[0].id
diff --git a/covalent_ui/app.py b/covalent_ui/app.py
index 82804b51..7d4e2c18 100644
--- a/covalent_ui/app.py
+++ b/covalent_ui/app.py
@@ -28,7 +28,6 @@ from fastapi.templating import Jinja2Templates
 
 from covalent._shared_files import logger
 from covalent._shared_files.config import get_config
-from covalent_dispatcher._db.datastore import DataStore
 from covalent_dispatcher._service.app_dask import DaskCluster
 from covalent_ui.api.main import app as fastapi_app
 from covalent_ui.api.main import sio
@@ -102,9 +101,6 @@ if __name__ == "__main__":
         dask_cluster = DaskCluster(name="LocalDaskCluster", logger=app_log)
         dask_cluster.start()
 
-    # Initialize the database
-    DataStore(initialize_db=True)
-
     # Start covalent main app
     uvicorn.run(
         "app:fastapi_app",
diff --git a/doc/.gitignore b/doc/.gitignore
index e29b7d92..9cac819c 100644
--- a/doc/.gitignore
+++ b/doc/.gitignore
@@ -1,4 +1,4 @@
-# Copyright 2021-2022 Agnostiq Inc.
+# Copyright 2021 Agnostiq Inc.
 #
 # This file is part of Covalent.
 #
diff --git a/doc/Makefile b/doc/Makefile
index 59a31a04..60a830a7 100644
--- a/doc/Makefile
+++ b/doc/Makefile
@@ -1,4 +1,4 @@
-# Copyright 2021-2022 Agnostiq Inc.
+# Copyright 2021 Agnostiq Inc.
 #
 # This file is part of Covalent.
 #
diff --git a/doc/generate_docs.py b/doc/generate_docs.py
index 20483f16..8feb0124 100644
--- a/doc/generate_docs.py
+++ b/doc/generate_docs.py
@@ -1,4 +1,4 @@
-# Copyright 2021-2022 Agnostiq Inc.
+# Copyright 2021 Agnostiq Inc.
 #
 # This file is part of Covalent.
 #
diff --git a/doc/source/api/executors/awslambda.rst b/doc/source/api/executors/awslambda.rst
index 95ab7fe0..45f7b86b 100644
--- a/doc/source/api/executors/awslambda.rst
+++ b/doc/source/api/executors/awslambda.rst
@@ -7,14 +7,19 @@
 
 With this executor, users can execute tasks (electrons) or entire lattices using the AWS Lambda serverless compute service. It is appropriate
 to use this plugin for electrons that are expected to be short lived, low in compute intensity. This plugin can also be used for workflows with a high number of electrons
-that are embarassingly parallel aka fully independent of each other.
+that are embarassingly parallel (fully independent of each other).
 
-The AWS resources required by this executor are quite minimal 1) S3 bucket for caching objects 2) Container based AWS lambda function 3) IAM role for  the Lambda.
+The following AWS resources are required by this executor
+
+* Container based `AWS Lambda <https://docs.aws.amazon.com/lambda/latest/dg/welcome.html>`_ function
+* `AWS S3 <https://docs.aws.amazon.com/AmazonS3/latest/userguide/Welcome.html>`_ bucket for caching objects
+* `IAM <https://docs.aws.amazon.com/iam/index.html>`_ role for Lambda
+* `ECR <https://docs.aws.amazon.com/ecr/index.html>`_ container registry for storing docker images
 
-The following snippet shows the required terraform to spin up the necessary resources and can be used as a base template
 
 1. Installation
 ###############
+
 To use this plugin with Covalent, simply install it using `pip`:
 
 .. code-block:: shell
@@ -170,8 +175,7 @@ Alternatively, users can customize this executor entirely by providing their own
 4. Required AWS Resources
 ###########################
 
-In order for the executor to work end-to-end, the following resources need to be configured
-either with `Terraform <https://www.terraform.io/>`_ or manually provisioned on the `AWS Dashboard <https://aws.amazon.com/>`_:
+In order for the executor to work end-to-end, the following resources need to be provisioned apriori.
 
 .. list-table:: Title
    :widths: 25 25 50
@@ -189,6 +193,9 @@ either with `Terraform <https://www.terraform.io/>`_ or manually provisioned on
    * - AWS Lambda function
      - function_name
      - Name of the AWS lambda function created in AWS
+   * - AWS Elastic Container Registry (ECR)
+     - ``-``
+     - The container registry that contains the docker images used by the lambda function to execute tasks
 
 The following JSON policy document shows the necessary IAM permissions required for the executor
 to properly run tasks using the AWS Lambda compute service:
@@ -299,7 +306,7 @@ Users can use the following `Terraform <https://www.terraform.io/>`_ snippet as
     provider aws {}
 
     resource aws_s3_bucket bucket {
-        ...
+        bucket = "my-s3-bucket"
     }
 
     resource aws_iam_role lambda_iam {
@@ -320,14 +327,36 @@ Users can use the following `Terraform <https://www.terraform.io/>`_ snippet as
         managed_policy_arns = [ "arn:aws:iam::aws:policy/AWSLambdaExecute" ]
     }
 
+    resource aws_ecr_repository lambda_ecr {
+        name = "lambda_container_registry"
+    }
+
     resource aws_lambda_function lambda {
         function_name = "my-lambda-function"
         role = aws_iam_role.lambda_iam.arn
         packge_type = "Image"
         timeout = <timeout value in seconds, max 900 (15 minutes), defaults to 3>
         memory_size = <Max memory in MB that the Lambda is expected to use, defaults to 128>
-        image_uri = <URI to the container image used by the lambda, defaults to `public.ecr.aws/covalent/covalent-lambda-executor:stable`>
+        image_uri = aws_ecr_repository.lambda_ecr.repository_url
     }
 
 For more information on how to create IAM roles and attach policies in AWS, refer to `IAM roles <https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create.html>`_.
 For more information on AWS S3, refer to `AWS S3 <https://aws.amazon.com/s3/>`_.
+
+.. note::
+
+    The lambda function created requires a docker image to execute the any tasks required by it. We distribute ready to use AWS Lambda executor docker images that user's can pull and push to their private ECR registries before dispatching workflows.
+
+    The base docker image can be obtained as follows
+
+    .. code:: bash
+
+        docker pull public.ecr.aws/covalent/covalent-lambda-executor:stable
+
+    Once the image has been obtained, user's can tag it with their registry information and upload to ECR as follows
+
+    .. code:: bash
+
+        aws ecr get-login-password --region <region> | docker login --username AWS --password-stdin <aws_account_id>.dkr.ecr.<region>.amazonaws.com
+        docker tag public.ecr.aws/covalent/covalent-lambda-executor:stable <aws_account_id>.dkr.ecr.<region>.amazonaws.com/<my-repository>:tag
+        docker push <aws_account_id>.dkr.ecr.<region>.amazonaws.com/<my-repository>:tag
diff --git a/doc/source/conf.py b/doc/source/conf.py
index d25cb965..10a4762e 100644
--- a/doc/source/conf.py
+++ b/doc/source/conf.py
@@ -1,4 +1,4 @@
-# Copyright 2021-2022 Agnostiq Inc.
+# Copyright 2021 Agnostiq Inc.
 #
 # This file is part of Covalent.
 #
@@ -25,7 +25,7 @@ import sys
 
 # Project information
 project = ""
-copyright = "2021-2022 Agnostiq Inc."
+copyright = "2021 Agnostiq Inc."
 author = "Agnostiq"
 
 html_static_path = ["_static"]
diff --git a/doc/source/covalent_theme/LICENSE b/doc/source/covalent_theme/LICENSE
index eeb82197..eafb0dab 100644
--- a/doc/source/covalent_theme/LICENSE
+++ b/doc/source/covalent_theme/LICENSE
@@ -1,5 +1,5 @@
 Original work by Washignton Teran <washingtonateran@gmail.com>
-Copyright (c) 2021-2022 Agnostiq Inc.
+Copyright (c) 2021 Agnostiq Inc.
 
 
 The above copyright notice and this permission notice shall be included in
diff --git a/doc/source/covalent_theme/footer.html b/doc/source/covalent_theme/footer.html
index 328a2b97..567a9438 100644
--- a/doc/source/covalent_theme/footer.html
+++ b/doc/source/covalent_theme/footer.html
@@ -5,7 +5,7 @@
     <!--Copyright-->
     <div class="footer-copyright text-md-right">
         <div class="container-fluid">
-           <small> © Copyright 2021-2022 Agnostiq - All rights reserved</small>
+           <small> © Copyright 2021 Agnostiq - All rights reserved</small>
         </div>
     </div>
   </footer>
diff --git a/doc/source/extensions/sphinx_execute_code.py b/doc/source/extensions/sphinx_execute_code.py
index 9dcdd478..80864a65 100644
--- a/doc/source/extensions/sphinx_execute_code.py
+++ b/doc/source/extensions/sphinx_execute_code.py
@@ -1,4 +1,4 @@
-# Copyright 2021-2022 Agnostiq Inc.
+# Copyright 2021 Agnostiq Inc.
 #
 # This file is part of Covalent.
 #
diff --git a/doc/source/getting_started/index.rst b/doc/source/getting_started/index.rst
index 8a581607..93cca7c3 100644
--- a/doc/source/getting_started/index.rst
+++ b/doc/source/getting_started/index.rst
@@ -2,47 +2,37 @@
 Getting Started
 ===============
 
-Covalent is a Pythonic workflow tool for data scientists, AI/ML researchers, and anyone who needs a way to run experiments on limited or expensive computing resources. Such resources can include quantum computers, HPC clusters, GPU arrays, and cloud services. Covalent manages workflows in heterogeneous environments that contain any or all of these advanced platforms.
+Covalent is developed using Python 3.8 on Linux and macOS.  See the :doc:`Compatibility <./compatibility>` page for further details on Python versions and operating systems which support Covalent. To set up Python on your computer, refer to the official `Python for Beginners <https://www.python.org/about/gettingstarted/>`_ page.
 
-Covalent enables you to:
-* Isolate operations that don't require advanced compute resources so you can run them on commonly available hardware;
-* Test individual functions or groups of functions on local hardware or inexpensive servers before committing them to advanced hardware;
-* Manage experiments and view results in a browser-based GUI;
-* Automate and manage workflows from a Jupyter notebook;
-* Parallelize independent computations to accelerate job completion;
-* Bring existing code into Covalent without extensive refactoring.
+Installation
+############
 
-Covalent is developed in Python 3.8 on Linux and macOS.  See the :doc:`Compatibility <./compatibility>` page for supported Python versions and operating systems. To set up Python on your computer, refer to the official `Python for Beginners <https://www.python.org/about/gettingstarted/>`_ page.
+Upgrading Covalent
+~~~~~~~~~~~~~~~~~~
 
-Installing Covalent
-###################
+If you are upgrading Covalent from the previous stable release, please refer to the :doc:`migration guide <./../version_migrations/index>` if you wish to preserve your data and/or avoid any issues that may arise when upgrading.
 
-.. note::
-
-   If you are upgrading Covalent from the previous stable release, refer to the :doc:`migration guide <./../version_migrations/index>` to preserve your data and avoid upgrade problems.
-
-You can install Covalent using the Python package manager, using Conda, or by building and installing from source.
+Installation Methods
+~~~~~~~~~~~~~~~~~~~~
 
 Pip Install
-~~~~~~~~~~~
-
-The easiest way to install Covalent is to use Pip, the Python package manager.
-
-.. note::
-
-   If you have previously used Covalent, uninstall the Covalent Dask plugin by running :code:`pip uninstall covalent-dask-plugin`. The plugin has been folded into Covalent and is no longer maintained separately.
+-----------
 
-To install using Pip:
+The easiest way to install Covalent is using the PyPI package manager:
 
 .. code:: bash
 
    pip install covalent
 
+.. note::
+
+   If you have used Covalent previously, make sure to uninstall the Covalent Dask plugin by running :code:`pip uninstall covalent-dask-plugin`. That plugin has been folded into Covalent and will no longer be maintained separately.
+
 
 Conda Install
-~~~~~~~~~~~~~
+-------------
 
-You can install Covalent as a package in a Conda environment:
+Users can also install Covalent as a package in a Conda environment:
 
 .. code:: bash
 
@@ -50,14 +40,12 @@ You can install Covalent as a package in a Conda environment:
 
 .. note::
 
-   Conda installation is currently only supported for Linux. Sometimes Conda can have trouble resolving packages. Use the flag :code:`--override-channels` to speed up installation.
-
-   On Mac, you can use Conda to manage your environment but must use Pip to install Covalent.
+   Installation via Conda is currently only supported for Linux. Sometimes Conda can have trouble resolving packages. Use the flag :code:`--override-channels` to speed things up.
 
 Install From Source
-~~~~~~~~~~~~~~~~~~~
+--------------------
 
-You can download and install Covalent from source:
+Covalent can also be downloaded and installed from source:
 
 .. code:: bash
 
@@ -67,47 +55,33 @@ You can download and install Covalent from source:
    # Build dashboard
    python setup.py webapp
 
-   # Either:
-   # 1. Install using pip (-e is for developer mode)
+   # Install using pip (-e for developer mode), or...
    pip install -e .
 
-   # - or -
-   # 2. Build and install using Conda (takes 10-15 mins)
+   # Build and install using Conda (10-15 mins)
    conda build .
    conda install -c local covalent
 
-To build the documentation locally:
+The documentation can also easily be built locally:
 
 .. code:: bash
 
    python setup.py docs
 
 
-Validating the Installation
-~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Validate the Installation
+~~~~~~~~~~~~~~~~~~~~~~~~~
 
-Covalent has been properly installed if the following returns without error:
+You can validate Covalent has been properly installed if the following returns without error:
 
 .. code:: bash
 
    python -c "import covalent"
 
-Starting the Server
-###################
+Start the Server
+#################
 
-Start the Covalent server:
-
-.. code:: console
-
-   $ covalent start
-   Covalent server has started at http://localhost:48008
-
-Managing the Server
-~~~~~~~~~~~~~~~~~~~
-
-Use the Covalent CLI tool to manage the Covalent server. You can start and stop the server, view its status, and see the server logs.
-
-View available subcommands with the --help option:
+Use the Covalent CLI tool to manage the Covalent server. The following commands will help you get started.
 
 .. code:: console
 
@@ -128,20 +102,26 @@ View available subcommands with the --help option:
    status   Query the status of the Covalent server.
    stop     Stop the Covalent server.
 
-Using the UI to View Workflows and Results
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Start the Covalent server:
 
-Navigate to the Covalent UI by entering the address, http://localhost:48008, into your web browser.  This is where dispatched jobs will appear.
+.. code:: console
+
+   $ covalent start
+   Covalent server has started at http://localhost:48008
+
+Optionally, confirm the server is running:
 
+.. code:: console
 
-Running a Workflow
-##################
+   $ covalent status
+   Covalent server is running at http://localhost:48008.
 
-Run this simple "Hello World" example to see Covalent in action.
+Now, navigate to the Covalent UI by entering the address into your web browser.  This is where dispatched jobs will appear.
 
-Before starting, ensure that you have installed Covalent, verified the installation, and started the Covalent server.
+Hello, Covalent!
+################
 
-Open a Jupyter notebook or Python console and create the following workflow:
+Let's look at a simple example to get started with Covalent. Before starting, ensure you have installed Covalent, verified the installation, and started the Covalent server. Next, open a Jupyter notebook or Python console and create a simple workflow:
 
 
 .. code:: python
@@ -157,7 +137,7 @@ Open a Jupyter notebook or Python console and create the following workflow:
    def excitement(a):
        return f"{a}!"
 
-   # Construct a workflow as "lattice"
+   # Construct a workflow of tasks
    @ct.lattice
    def simple_workflow(a, b):
        phrase = join_words(a, b)
@@ -182,7 +162,7 @@ Click on the dispatch ID to view the workflow graph:
    :align: center
 
 
-While the workflow is being processed by the dispatch server, you can terminate the Jupyter kernel or Python console process without losing access to the results. Make sure the Covalent server remains in the "running" state while you have running workflows.
+While the workflow is being processed by the dispatch server, you are free to terminate the Jupyter kernel or Python console process without losing access to the results. Make sure the Covalent server remains in the "running" state while you have running workflows.
 
 When the workflow has completed, you can start a new session and query the results:
 
@@ -190,11 +170,10 @@ When the workflow has completed, you can start a new session and query the resul
 
    import covalent as ct
 
-   # Copy the dispatch ID from the UI
-   dispatch_id = "12345678-1234-1234-1234-123456789abc"
+   dispatch_id = "8a7bfe54-d3c7-4ca1-861b-f55af6d5964a"
    result_string = ct.get_result(dispatch_id).result
 
-When you are done using Covalent to run workflows, stop the server:
+When you are done using Covalent, stop the server:
 
 .. code:: console
 
diff --git a/doc/source/index.rst b/doc/source/index.rst
index 33215e87..61c8ecc1 100644
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -31,60 +31,15 @@ COVALENT DOCUMENTATION
 
 |
 
-What is Covalent?
-#################
-
-Covalent is a Python-language – and [Pythonic(https://www.udacity.com/blog/2020/09/what-is-pythonic-style.html)] – workflow tool for computational scientists, AI/ML software engineers, and anyone who needs a way to run experiments on limited or expensive computing resources. Such resources can include quantum computers, HPC clusters, GPU arrays, and cloud services. Covalent manages workflows in heterogeneous environments that contain any or all of these advanced platforms.
-
-Covalent enables you to:
-
-Isolate operations that don't require advanced compute resources so you can run them on commonly available hardware.
-    Covalent dispatches tasks to any number of executors backed by heterogeneous hardware platforms. Run non-critical tasks on commodity hardware, using advanced hardware only for the heavy computations: Using a heterogeneous environment is almost as easy as running with a monolithic backend.
-
-Test individual functions or groups of functions on local hardware before committing them to advanced hardware.
-    Not only does Covalent run different workflow tasks on different platforms; but reassigning a task to a different platform is as easy as changing a single attribute in a function decorator. Test locally, then do the real work on big hardware.
-
-Iteratively run prototypes and exploratory research models and save results.
-    Covalent saves the data required to reproduce every run of every workflow, along with its results. Manage running workflows, view task dependencies, and examine results in Covalent's intuitive browser-based UI.
-
-Automate and manage workflows from a Jupyter notebook or any other interactive Python environment.
-    The Covalent scheduler runs locally or on a server. Run experiments as you normally would, swapping parameters and datasets, from Jupyter or any other interactive Python environment.
-
-Run experiments faster with parallel processing.
-    Covalent automatically parallelizes independent tasks to accelerate job completion.
-
-Ready to try it? Skip to [Getting Started](https://covalent.readthedocs.io/en/latest/getting_started/index.html)
-
-Covalent is designed and developed from the ground up for data experimentation and prototyping. Covalent features:
-
-Pure Python
-    Covalent automatically constructs complex workflows when you program with native Python functions. Make your code Covalent-ready simply by adding one-line decorators to your functions.
-
-A robust user interface
-    Covalent provides an intuitive and aesthetic browser-based UI to monitor and manage your workflows.
-
-Result management
-    Covalent manages the results of your workflows. Whenever you need to modify parts of your workflow, from inputs to components, Covalent stores and saves the run of every experiment in a reproducible format.
-
-Low overhead
-    Covalent is designed to be as lightweight as possible and is optimized for the most common use cases. Covalent's overhead is less than 0.1% of the total runtime for typical high-compute applications and often has a constant overhead of ~ 10-100μs – and this is constantly being optimized.
-
-Interactive
-    Unlike other workflow tools, Covalent is interactive. You can view, modify, and re-submit workflows directly within a Jupyter notebook.
-
-Ready to try it? Skip to [Getting Started](https://covalent.readthedocs.io/en/latest/getting_started/index.html)
-
-Is It Really That Easy?
-#######################
-
-Don't be fooled the simplicity of Covalent's decorator-based code syntax. Each decorator is a wrapper hiding sophisticated task management software that analyzes function input and output and enables the Covalent server to abstract the code from the backend implementation.
-
-You do have some responsibilities when you use a Covalent decorator. Your code should be task-oriented and Pythonic. Task-oriented, because the more you use small, independent, single-purpose tasks, the more efficiently Covalent can manage your workflow. Long, shambolic scripts are not a good candidate for Covalent scheduling – at least not without refactoring.
-
-Pythonic, because using Python best practices enables Covalent to automatically construct workflow graphs without having to specify graph edges explicitly in another language such as YAML.
-</div>
-
-For a more in-depth description of Covalent's features and how they work, refer to the [Concepts](https://covalent.readthedocs.io/en/latest/concepts/concepts.html) page in the documentation.
+Covalent is a Pythonic distributed workflow tool used to prototype and run high performance classical and quantum software. Covalent can be used to...
+
+- rapidly iterate prototypes and exploratory research models
+- construct complex workflows consisting of tasks written in a variety of languages
+- automate, manage, and share reproducible experiments
+- build data pipelines across distributed infrastructure
+- visualize data and task dependencies in an interactive user interface
+- run software in heterogeneous compute environments, including in hybrid-cloud and hybrid-quantum configurations
+- understand where time and money is spent across a project
 
 .. |
 


Test patch:
diff --git a/tests/covalent_dispatcher_tests/_cli/service_test.py b/tests/covalent_dispatcher_tests/_cli/service_test.py
index 2ef8461b..883aad22 100644
--- a/tests/covalent_dispatcher_tests/_cli/service_test.py
+++ b/tests/covalent_dispatcher_tests/_cli/service_test.py
@@ -224,10 +224,19 @@ def test_graceful_shutdown_stopped_server(mocker):
 
 
 @pytest.mark.parametrize(
-    "is_migration_pending, ignore_migrations",
-    [(True, True), (True, False), (False, False), (False, True)],
+    "is_migration_pending, ignore_migrations, current_revision",
+    [
+        (True, True, None),
+        (True, False, None),
+        (False, False, None),
+        (False, True, None),
+        (True, True, "112233"),
+        (True, False, "112233"),
+        (False, False, "112233"),
+        (False, True, "112233"),
+    ],
 )
-def test_start(mocker, monkeypatch, is_migration_pending, ignore_migrations):
+def test_start(mocker, monkeypatch, is_migration_pending, ignore_migrations, current_revision):
     """Test the start CLI command."""
 
     runner = CliRunner()
@@ -245,6 +254,7 @@ def test_start(mocker, monkeypatch, is_migration_pending, ignore_migrations):
     monkeypatch.setattr("covalent_dispatcher._cli.service.UI_LOGFILE", "mock")
 
     db_mock.is_migration_pending = is_migration_pending
+    db_mock.current_revision.return_value = current_revision
 
     cli_args = f"--port {port_val} -d"
 
@@ -253,10 +263,13 @@ def test_start(mocker, monkeypatch, is_migration_pending, ignore_migrations):
 
     res = runner.invoke(start, cli_args)
 
+    # if current_revision is None no migrations have been run
+    if current_revision is None and not ignore_migrations:
+        db_mock.current_revision.assert_called_once()
+
     if ignore_migrations or not is_migration_pending:
         graceful_start_mock.assert_called_once()
         assert set_config_mock.call_count == 6
-        # set_config_mock.assert_called_once()
     else:
         assert MIGRATION_COMMAND_MSG in res.output
         assert MIGRATION_WARNING_MSG in res.output
diff --git a/tests/covalent_dispatcher_tests/_service/app_test.py b/tests/covalent_dispatcher_tests/_service/app_test.py
index 1aeeafb8..26ffade2 100644
--- a/tests/covalent_dispatcher_tests/_service/app_test.py
+++ b/tests/covalent_dispatcher_tests/_service/app_test.py
@@ -147,7 +147,6 @@ def test_get_result_503(mocker, app, client, test_db_file, tmp_path):
 
 def test_get_result_dispatch_id_not_found(mocker, test_db_file, client, tmp_path):
 
-    mocker.patch.object(DispatchDB, "_get_data_store", test_db_file)
     mocker.patch("covalent_dispatcher._service.app._result_from", return_value={})
     mocker.patch("covalent_dispatcher._service.app.workflow_db", test_db_file)
     mocker.patch("covalent_dispatcher._service.app.Lattice", MockLattice)
diff --git a/tests/covalent_tests/file_transfer/strategies/s3_strategy_test.py b/tests/covalent_tests/file_transfer/strategies/s3_strategy_test.py
index 02644ea2..e7afdce4 100644
--- a/tests/covalent_tests/file_transfer/strategies/s3_strategy_test.py
+++ b/tests/covalent_tests/file_transfer/strategies/s3_strategy_test.py
@@ -1,3 +1,23 @@
+# Copyright 2021 Agnostiq Inc.
+#
+# This file is part of Covalent.
+#
+# Licensed under the GNU Affero General Public License 3.0 (the "License").
+# A copy of the License may be obtained with this software package or at
+#
+#      https://www.gnu.org/licenses/agpl-3.0.en.html
+#
+# Use of this file is prohibited except in compliance with the License. Any
+# modifications or derivative works of this file must retain this copyright
+# notice, and modified files must contain a notice indicating that they have
+# been altered from the originals.
+#
+# Covalent is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the License for more details.
+#
+# Relief from the License may be granted by purchasing a commercial license.
+
 import os
 import sys
 from unittest.mock import MagicMock
@@ -5,7 +25,7 @@ from unittest.mock import MagicMock
 import pytest
 from furl import furl
 
-from covalent._file_transfer import File
+from covalent._file_transfer import File, Folder
 from covalent._file_transfer.strategies.s3_strategy import S3
 
 
@@ -84,3 +104,61 @@ class TestS3Strategy:
 
         with pytest.raises(NotImplementedError):
             S3().cp(File(self.MOCK_REMOTE_FILEPATH), File(self.MOCK_LOCAL_FILEPATH))()
+
+
+def test_folder_download(mocker):
+    """Test the s3 file download when remote and local folders are provided."""
+    boto3_mock = MagicMock()
+    sys.modules["boto3"] = boto3_mock
+
+    boto3_client_mock = mocker.patch("boto3.client")
+
+    from_folder = Folder("s3://mock-bucket/")
+    to_folder = Folder("/User/tmp/")
+
+    bucket_name = furl(from_folder.uri).origin[5:]
+
+    boto3_client_mock().list_objects.return_value = {
+        "Contents": [
+            {"Key": "test.csv"},
+        ]
+    }
+
+    callable_func = S3().download(from_folder, to_folder)
+    callable_func()
+    boto3_client_mock().download_file.assert_called_once_with(
+        bucket_name, "test.csv", "/User/tmp/test.csv"
+    )
+
+
+def test_folder_upload(mocker):
+    """Test the s3 file upload method when remote and local folders are provided."""
+    boto3_mock = MagicMock()
+    sys.modules["boto3"] = boto3_mock
+
+    boto3_client_mock = mocker.patch("boto3.client")
+
+    os_mock = MagicMock()
+    sys.modules["os"] = os_mock
+
+    mocker.patch("os.walk", return_value=[["", "", ["test.csv"]]])
+
+    to_folder = Folder("s3://mock-bucket/")
+    from_folder = Folder("/User/tmp/")
+
+    bucket_name = furl(to_folder.uri).origin[5:]
+
+    boto3_client_mock().list_objects.return_value = {
+        "Contents": [
+            {"Key": "test.csv"},
+        ]
+    }
+
+    os_mock.path.relpath.return_value = "mock_path"
+    os_mock.path.join.return_value = "mock_join"
+
+    callable_func = S3().upload(from_folder, to_folder)
+    callable_func()
+    boto3_client_mock().upload_file.assert_called_once_with(
+        "/User/tmp/mock_join", bucket_name, "mock_join"
+    )
diff --git a/tests/covalent_tests/results_manager_tests/results_test.py b/tests/covalent_tests/results_manager_tests/results_test.py
index 9c0da777..538a3ecb 100644
--- a/tests/covalent_tests/results_manager_tests/results_test.py
+++ b/tests/covalent_tests/results_manager_tests/results_test.py
@@ -88,6 +88,29 @@ def result_1():
     return result
 
 
+@pytest.fixture
+def result_2():
+    @ct.electron(executor=le)
+    def task_1(x, y):
+        raise RuntimeError("error")
+
+    @ct.lattice(executor=le, workflow_executor=le, results_dir=TEMP_RESULTS_DIR)
+    def workflow_1(a, b):
+        """Docstring"""
+        res_1 = task_1(a, b)
+        return res_1
+
+    Path(f"{TEMP_RESULTS_DIR}/dispatch_1").mkdir(parents=True, exist_ok=True)
+    workflow_1.build_graph(a=1, b=2)
+    received_lattice = LatticeClass.deserialize_from_json(workflow_1.serialize_to_json())
+    result = Result(
+        lattice=received_lattice, results_dir=TEMP_RESULTS_DIR, dispatch_id="dispatch_1"
+    )
+    result.lattice.metadata["results_dir"] = TEMP_RESULTS_DIR
+    result._initialize_nodes()
+    return result
+
+
 def test_get_node_error(result_1):
     """Test result method to get the node error."""
     assert not result_1._get_node_error(node_id=0)
@@ -103,6 +126,12 @@ def test_get_all_node_results(result_1, mocker):
             assert data_row["node_name"] == ":parameter:1"
 
 
+def test_str_result(result_2, mocker):
+    """Test result __str__ method"""
+    s = str(result_2)
+    assert "task_1" in s
+
+
 def test_result_root_dispatch_id(result_1):
     """Test the `root_dispatch_id` property`"""
 
diff --git a/tests/functional_tests/local_executor_test.py b/tests/functional_tests/local_executor_test.py
new file mode 100644
index 00000000..a3361ea8
--- /dev/null
+++ b/tests/functional_tests/local_executor_test.py
@@ -0,0 +1,47 @@
+# Copyright 2021 Agnostiq Inc.
+#
+# This file is part of Covalent.
+#
+# Licensed under the GNU Affero General Public License 3.0 (the "License").
+# A copy of the License may be obtained with this software package or at
+#
+#      https://www.gnu.org/licenses/agpl-3.0.en.html
+#
+# Use of this file is prohibited except in compliance with the License. Any
+# modifications or derivative works of this file must retain this copyright
+# notice, and modified files must contain a notice indicating that they have
+# been altered from the originals.
+#
+# Covalent is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the License for more details.
+#
+# Relief from the License may be granted by purchasing a commercial license.
+
+
+import covalent as ct
+
+
+def test_local_executor_returns_stdout_stderr():
+    from covalent.executor import LocalExecutor
+
+    le = LocalExecutor()
+
+    @ct.electron(executor=le)
+    def task(x):
+        import sys
+
+        print("Hello")
+        print("Error", file=sys.stderr)
+        return x
+
+    @ct.lattice
+    def workflow(x):
+        return task(x)
+
+    dispatch_id = ct.dispatch(workflow)(5)
+    res = ct.get_result(dispatch_id, wait=True)
+    tg = res.lattice.transport_graph
+    assert tg.get_node_value(0, "stdout") == "Hello\n"
+    assert tg.get_node_value(0, "stderr") == "Error\n"
+    assert tg.get_node_value(0, "output").get_deserialized() == 5