Repository: AgnostiqHQ/covalent
Pull request #1850 (2023-11-14 18:00:00)

Post mem improvement Qelectron fixes
<!--
⚠️ Your pull request title should be short detailed and understandable for all.
⚠️ Add a note to /CHANGELOG.md summarizing the changes.
⚠️ If your pull request fixes an open issue, please link to the issue.
⚠️ Ensure that your branch is up-to-date with the base branch.
-->

- [x] 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.


kessler-frost: It's working as expected, now need to fix the tests.
kessler-frost: - [ ] Remove the `qelectron_db_filename` variable if possible since it is always going to be "data.mdb", also since only the db path is required for `Database` class. Make sure it doesn't interfere when downloading the asset to object store - in which case we should let it be here.
- [ ] Don't load the `qelectron_db` unconditionally when doing `get_result`, i.e. add the key to the `DEFERRED_KEYS` variable so that it only gets loaded when it's actually needed
- [ ] Remove `value` from the `DEFERRED_KEYS` variable
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/AgnostiqHQ/covalent/pull/1850?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=AgnostiqHQ) Report
> Merging [#1850](https://app.codecov.io/gh/AgnostiqHQ/covalent/pull/1850?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=AgnostiqHQ) (3419145) into [develop](https://app.codecov.io/gh/AgnostiqHQ/covalent/commit/f2ad0f680e8c0ffc59ccd1e3a0c776e087192b33?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=AgnostiqHQ) (f2ad0f6) will **increase** coverage by `0.70%`.
> The diff coverage is `86.04%`.

<details><summary>Additional details and impacted files</summary>


[![Impacted file tree graph](https://app.codecov.io/gh/AgnostiqHQ/covalent/pull/1850/graphs/tree.svg?width=650&height=150&src=pr&token=YGHCB3DE4P&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=AgnostiqHQ)](https://app.codecov.io/gh/AgnostiqHQ/covalent/pull/1850?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=AgnostiqHQ)

```diff
@@             Coverage Diff             @@
##           develop    #1850      +/-   ##
===========================================
+ Coverage    84.01%   84.72%   +0.70%     
===========================================
  Files          290      176     -114     
  Lines        14125    10891    -3234     
  Branches       193        0     -193     
===========================================
- Hits         11867     9227    -2640     
+ Misses        2125     1664     -461     
+ Partials       133        0     -133     
```

| [Flag](https://app.codecov.io/gh/AgnostiqHQ/covalent/pull/1850/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=AgnostiqHQ) | Coverage Δ | |
|---|---|---|
| [Dispatcher](https://app.codecov.io/gh/AgnostiqHQ/covalent/pull/1850/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=AgnostiqHQ) | `92.33% <80.00%> (+0.11%)` | :arrow_up: |
| [Functional_Tests](https://app.codecov.io/gh/AgnostiqHQ/covalent/pull/1850/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=AgnostiqHQ) | `?` | |
| [SDK](https://app.codecov.io/gh/AgnostiqHQ/covalent/pull/1850/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=AgnostiqHQ) | `78.88% <86.84%> (+0.49%)` | :arrow_up: |
| [UI_Backend](https://app.codecov.io/gh/AgnostiqHQ/covalent/pull/1850/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=AgnostiqHQ) | `?` | |
| [UI_Frontend](https://app.codecov.io/gh/AgnostiqHQ/covalent/pull/1850/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=AgnostiqHQ) | `?` | |

</details>

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 22d9959a..0f08c981 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,8 +15,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - Removed unassigned variable names
 - Contributing guidelines steps for installing for the first time
 - Updated gitignore to ignore yarn files and folders for latest version of yarn
-- Fixed the bug that caused ValueError error when using KEYWORD_ONLY parameter in electron func 
+- Fixed the bug that caused ValueError error when using KEYWORD_ONLY parameter in electron func
 - Changed code at line 218 in covalent/_shared_files/utils.py
+- Fixed usage of deprecated pydantic validation methods
+- Fixed qelectron_db retrieval in result object
 
 ### Operations
 
@@ -36,12 +38,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - Changed license to Apache
 - Improved error handling in generate_docs.py
 - [Significant Changes] Migrated core server-side code to new data access layer.
+- Changed the way UI was accessing the qelectron database to access it directly from the mdb file in object store
 
 ### Added
 
 - Documentation and test cases for database triggers.
 - Added the `__pow__` method to the `Electron` class
 - New Runner and executor API to bypass server-side memory when running tasks.
+- Added qelectron_db as an asset to be transferred from executor's machine to covalent server
+- New methods to qelectron_utils, replacing the old ones
 
 ### Docs
 
@@ -51,6 +56,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 ### Tests
 
 - Temporarily skipping the sqlite and database trigger functional tests
+- Updated tests to accommodate the new qelectron fixes
+- Added new tests for the Database class and qelectron_utils
+
+### Removed
+
+- Removed no longer needed methods in qelectron_utils
+- Removed `dispatch-id` from generate_node_result method
 
 ## [0.229.0-rc.0] - 2023-09-22
 
diff --git a/covalent/_dispatcher_plugins/local.py b/covalent/_dispatcher_plugins/local.py
index 11d0c38c..f0df0f8a 100644
--- a/covalent/_dispatcher_plugins/local.py
+++ b/covalent/_dispatcher_plugins/local.py
@@ -56,7 +56,7 @@ def get_redispatch_request_body_v2(
     dispatcher_addr: str = None,
 ) -> ResultSchema:
     rm = get_result_manager(dispatch_id, dispatcher_addr=dispatcher_addr, wait=True)
-    manifest = ResultSchema.parse_obj(rm._manifest)
+    manifest = ResultSchema.model_validate(rm._manifest)
 
     # If no changes to inputs or electron, just retry the dispatch
     if not new_args and not new_kwargs and not replace_electrons:
@@ -170,6 +170,69 @@ class LocalDispatcher(BaseDispatcher):
 
         return wrapper
 
+    @staticmethod
+    def register(
+        orig_lattice: Lattice,
+        dispatcher_addr: str = None,
+    ) -> Callable:
+        """
+        Wrapping the dispatching functionality to allow input passing
+        and server address specification.
+
+        Afterwards, send the lattice to the dispatcher server and return
+        the assigned dispatch id.
+
+        Args:
+            orig_lattice: The lattice/workflow to send to the dispatcher server.
+            dispatcher_addr: The address of the dispatcher server.  If None then then defaults to the address set in Covalent's config.
+
+        Returns:
+            Wrapper function which takes the inputs of the workflow as arguments
+        """
+
+        if dispatcher_addr is None:
+            dispatcher_addr = format_server_url()
+
+        @wraps(orig_lattice)
+        def wrapper(*args, **kwargs) -> str:
+            """
+            Send the lattice to the dispatcher server and return
+            the assigned dispatch id.
+
+            Args:
+                *args: The inputs of the workflow.
+                **kwargs: The keyword arguments of the workflow.
+
+            Returns:
+                The dispatch id of the workflow.
+            """
+
+            if not isinstance(orig_lattice, Lattice):
+                message = f"Dispatcher expected a Lattice, received {type(orig_lattice)} instead."
+                app_log.error(message)
+                raise TypeError(message)
+
+            lattice = deepcopy(orig_lattice)
+
+            lattice.build_graph(*args, **kwargs)
+
+            with tempfile.TemporaryDirectory() as tmp_dir:
+                manifest = LocalDispatcher.prepare_manifest(lattice, tmp_dir)
+                LocalDispatcher.register_manifest(manifest, dispatcher_addr)
+
+                dispatch_id = manifest.metadata.dispatch_id
+
+                path = dispatch_cache_dir / f"{dispatch_id}"
+
+                with open(path, "w") as f:
+                    f.write(manifest.model_dump_json())
+
+                LocalDispatcher.upload_assets(manifest)
+
+                return dispatch_id
+
+        return wrapper
+
     @staticmethod
     def submit(
         orig_lattice: Lattice,
@@ -373,8 +436,6 @@ class LocalDispatcher(BaseDispatcher):
                 + str(get_config("dispatcher.port"))
             )
 
-        stop_triggers_url = f"{triggers_server_addr}/api/triggers/stop_observe"
-
         if isinstance(dispatch_ids, str):
             dispatch_ids = [dispatch_ids]
 
@@ -386,69 +447,6 @@ class LocalDispatcher(BaseDispatcher):
         for d_id in dispatch_ids:
             app_log.debug(d_id)
 
-    @staticmethod
-    def register(
-        orig_lattice: Lattice,
-        dispatcher_addr: str = None,
-    ) -> Callable:
-        """
-        Wrapping the dispatching functionality to allow input passing
-        and server address specification.
-
-        Afterwards, send the lattice to the dispatcher server and return
-        the assigned dispatch id.
-
-        Args:
-            orig_lattice: The lattice/workflow to send to the dispatcher server.
-            dispatcher_addr: The address of the dispatcher server.  If None then then defaults to the address set in Covalent's config.
-
-        Returns:
-            Wrapper function which takes the inputs of the workflow as arguments
-        """
-
-        if dispatcher_addr is None:
-            dispatcher_addr = format_server_url()
-
-        @wraps(orig_lattice)
-        def wrapper(*args, **kwargs) -> str:
-            """
-            Send the lattice to the dispatcher server and return
-            the assigned dispatch id.
-
-            Args:
-                *args: The inputs of the workflow.
-                **kwargs: The keyword arguments of the workflow.
-
-            Returns:
-                The dispatch id of the workflow.
-            """
-
-            if not isinstance(orig_lattice, Lattice):
-                message = f"Dispatcher expected a Lattice, received {type(orig_lattice)} instead."
-                app_log.error(message)
-                raise TypeError(message)
-
-            lattice = deepcopy(orig_lattice)
-
-            lattice.build_graph(*args, **kwargs)
-
-            with tempfile.TemporaryDirectory() as tmp_dir:
-                manifest = LocalDispatcher.prepare_manifest(lattice, tmp_dir)
-                LocalDispatcher.register_manifest(manifest, dispatcher_addr)
-
-                dispatch_id = manifest.metadata.dispatch_id
-
-                path = dispatch_cache_dir / f"{dispatch_id}"
-
-                with open(path, "w") as f:
-                    f.write(manifest.json())
-
-                LocalDispatcher.upload_assets(manifest)
-
-                return dispatch_id
-
-        return wrapper
-
     @staticmethod
     def register_redispatch(
         dispatch_id: str,
@@ -506,7 +504,7 @@ class LocalDispatcher(BaseDispatcher):
                 path = dispatch_cache_dir / f"{redispatch_id}"
 
                 with open(path, "w") as f:
-                    f.write(manifest.json())
+                    f.write(manifest.model_dump_json())
 
                 LocalDispatcher.upload_assets(manifest)
 
@@ -541,20 +539,16 @@ class LocalDispatcher(BaseDispatcher):
         if dispatcher_addr is None:
             dispatcher_addr = format_server_url()
 
-        if push_assets:
-            stripped = strip_local_uris(manifest)
-        else:
-            stripped = manifest
-
+        stripped = strip_local_uris(manifest) if push_assets else manifest
         endpoint = "/api/v2/dispatches"
 
         if parent_dispatch_id:
             endpoint = f"{endpoint}/{parent_dispatch_id}/subdispatches"
 
-        r = APIClient(dispatcher_addr).post(endpoint, data=stripped.json())
+        r = APIClient(dispatcher_addr).post(endpoint, data=stripped.model_dump_json())
         r.raise_for_status()
 
-        parsed_resp = ResultSchema.parse_obj(r.json())
+        parsed_resp = ResultSchema.model_validate(r.json())
 
         return merge_response_manifest(manifest, parsed_resp)
 
@@ -581,10 +575,12 @@ class LocalDispatcher(BaseDispatcher):
         endpoint = f"/api/v2/dispatches/{dispatch_id}/redispatches"
 
         params = {"reuse_previous_results": reuse_previous_results}
-        r = APIClient(dispatcher_addr).post(endpoint, data=stripped.json(), params=params)
+        r = APIClient(dispatcher_addr).post(
+            endpoint, data=stripped.model_dump_json(), params=params
+        )
         r.raise_for_status()
 
-        parsed_resp = ResultSchema.parse_obj(r.json())
+        parsed_resp = ResultSchema.model_validate(r.json())
 
         return merge_response_manifest(manifest, parsed_resp)
 
diff --git a/covalent/_results_manager/result.py b/covalent/_results_manager/result.py
index 6941de76..c9d42c2f 100644
--- a/covalent/_results_manager/result.py
+++ b/covalent/_results_manager/result.py
@@ -24,11 +24,9 @@ from .._shared_files import logger
 from .._shared_files.config import get_config
 from .._shared_files.context_managers import active_lattice_manager
 from .._shared_files.defaults import postprocess_prefix, prefix_separator, sublattice_prefix
-from .._shared_files.qelectron_utils import QE_DB_DIRNAME
 from .._shared_files.util_classes import RESULT_STATUS, Status
 from .._workflow.lattice import Lattice
 from .._workflow.transport import TransportableObject
-from ..quantum.qserver import database as qe_db
 
 if TYPE_CHECKING:
     from .._shared_files.util_classes import Status
@@ -211,10 +209,7 @@ Node Outputs
         Final result of current dispatch.
         """
 
-        if self._result is not None:
-            return self._result.get_deserialized()
-        else:
-            return None
+        return self._result.get_deserialized() if self._result is not None else None
 
     @property
     def inputs(self) -> dict:
@@ -275,7 +270,7 @@ Node Outputs
             "end_time": self.lattice.transport_graph.get_node_value(node_id, "end_time"),
             "status": self._get_node_status(node_id),
             "output": self._get_node_output(node_id),
-            "qelectron": self._get_node_qelectron_data(node_id),
+            "qelectron": self._get_node_qelectron_db_bytes(node_id),
             "error": self.lattice.transport_graph.get_node_value(node_id, "error"),
             "sublattice_result": self.lattice.transport_graph.get_node_value(
                 node_id, "sublattice_result"
@@ -387,26 +382,26 @@ Node Outputs
         """
         return self._lattice.transport_graph.get_node_value(node_id, "output")
 
-    def _get_node_qelectron_data(self, node_id: int) -> dict:
+    def _get_node_qelectron_db_bytes(self, node_id: int) -> dict:
         """
-        Return all QElectron data associated with a node.
+        Return the entire QRlectron DB in bytes, associated with a node.
+
+        In order to use this db, the user needs to save this in an data.mdb file and
+        then use the `Database` class, found in `covalent.quantum.qserver.database` to access the data,
+        i.e.: database = Database("dirpath/containing/data.mdb")
+            database.get_db_dict(dispatch_id="dispatch_id", node_id="node_id", direct_path=True)
 
         Args:
             node_id: The node id.
 
         Returns:
-            The QElectron data of said node. Will return None if no data exists.
+            The QElectron db of said node. Will return an empty byte string if it doesn't exist.
         """
+
         try:
-            # Checks existence of QElectron data.
-            self._lattice.transport_graph.get_node_value(node_id, "qelectron_data_exists")
+            return self._lattice.transport_graph.get_node_value(node_id, "qelectron_db")
         except KeyError:
-            return None
-
-        results_dir = get_config("dispatcher")["results_dir"]
-        db_dir = os.path.join(results_dir, self.dispatch_id, QE_DB_DIRNAME)
-
-        return qe_db.Database(db_dir).get_db(dispatch_id=self.dispatch_id, node_id=node_id)
+            return bytes()
 
     def _get_node_error(self, node_id: int) -> Union[None, str]:
         """
diff --git a/covalent/_results_manager/results_manager.py b/covalent/_results_manager/results_manager.py
index 65534687..458d746d 100644
--- a/covalent/_results_manager/results_manager.py
+++ b/covalent/_results_manager/results_manager.py
@@ -68,8 +68,8 @@ SDK_LAT_META_KEYS = {
 
 DEFERRED_KEYS = {
     "output",
-    "value",
     "result",
+    "qelectron_db",
 }
 
 
@@ -289,14 +289,14 @@ def _load_node_asset(manifest: dict, node_id: int, key: str):
 class ResultManager:
     def __init__(self, manifest: ResultSchema, results_dir: str):
         self.result_object = deserialize_result(manifest)
-        self._manifest = manifest.dict()
+        self._manifest = manifest.model_dump()
         self._results_dir = results_dir
 
     def save(self, path: Optional[str] = None):
         if not path:
             path = os.path.join(self._results_dir, "manifest.json")
         with open(path, "w") as f:
-            f.write(ResultSchema.parse_obj(self._manifest).json())
+            f.write(ResultSchema.model_validate(self._manifest).model_dump_json())
 
     @staticmethod
     def load(path: str, results_dir: str) -> "ResultManager":
@@ -345,7 +345,7 @@ class ResultManager:
             dispatch_id, wait, status_only=False, dispatcher_addr=dispatcher_addr
         )
 
-        manifest = ResultSchema.parse_obj(export["result_export"])
+        manifest = ResultSchema.model_validate(export["result_export"])
 
         # sort the nodes
         manifest.lattice.transport_graph.nodes.sort(key=lambda x: x.id)
@@ -379,6 +379,7 @@ def _get_result_multistage(
     workflow_output: bool = True,
     intermediate_outputs: bool = True,
     sublattice_results: bool = True,
+    qelectron_db: bool = False,
 ) -> Result:
     """
     Get the results of a dispatch from a file.
@@ -395,6 +396,7 @@ def _get_result_multistage(
         workflow_output: Whether to return the workflow output. Defaults to True.
         intermediate_outputs: Whether to return all intermediate outputs in the compute graph. Defaults to True.
         sublattice_results: Whether to recursively retrieve sublattice results. Default is True.
+        qelectron_db: Whether to load the bytes data of qelectron_db. Default is False.
 
     Returns:
         The Result object from the Covalent server
@@ -422,6 +424,11 @@ def _get_result_multistage(
                 rm.download_node_asset(node_id, "output")
                 rm.load_node_asset(node_id, "output")
 
+        if qelectron_db:
+            for node_id in tg._graph.nodes:
+                rm.download_node_asset(node_id, "qelectron_db")
+                rm.load_node_asset(node_id, "qelectron_db")
+
         # Fetch sublattice result objects recursively
         tg = rm.result_object.lattice.transport_graph
         for node_id in tg._graph.nodes:
@@ -436,6 +443,7 @@ def _get_result_multistage(
                     workflow_output=workflow_output,
                     intermediate_outputs=intermediate_outputs,
                     sublattice_results=sublattice_results,
+                    qelectron_db=qelectron_db,
                 )
                 tg.set_node_value(node_id, "sublattice_result", sub_result)
             else:
@@ -461,6 +469,7 @@ def get_result(
     workflow_output: bool = True,
     intermediate_outputs: bool = True,
     sublattice_results: bool = True,
+    qelectron_db: bool = False,
 ) -> Result:
     """
     Get the results of a dispatch.
@@ -476,6 +485,7 @@ def get_result(
         workflow_output: Whether to return the workflow output. Defaults to True.
         intermediate_outputs: Whether to return all intermediate outputs in the compute graph. Defaults to True.
         sublattice_results: Whether to recursively retrieve sublattice results. Default is True.
+        qelectron_db: Whether to load the bytes data of qelectron_db. Default is False.
 
     Returns:
         The Result object from the Covalent server
@@ -491,4 +501,5 @@ def get_result(
         workflow_output=workflow_output,
         intermediate_outputs=intermediate_outputs,
         sublattice_results=sublattice_results,
+        qelectron_db=qelectron_db,
     )
diff --git a/covalent/_serialize/common.py b/covalent/_serialize/common.py
index 14264075..c36f7d68 100644
--- a/covalent/_serialize/common.py
+++ b/covalent/_serialize/common.py
@@ -47,6 +47,7 @@ class AssetType(Enum):
     TRANSPORTABLE = 1  # Custom TO serialization
     JSONABLE = 2
     TEXT = 3  # Mainly for stdout, stderr, docstrings, etc.
+    BYTES = 4  # Just bytes, used for qelectron DB, and other binary data
 
 
 def serialize_asset(data: Any, data_type: AssetType) -> bytes:
@@ -70,6 +71,8 @@ def serialize_asset(data: Any, data_type: AssetType) -> bytes:
         return json.dumps(data).encode("utf-8")
     elif data_type == AssetType.TEXT:
         return data.encode("utf-8")
+    elif data_type == AssetType.BYTES:
+        return data
     else:
         raise TypeError(f"Unsupported data type {type(data)}")
 
@@ -95,6 +98,8 @@ def deserialize_asset(data: bytes, data_type: AssetType) -> Any:
         return json.loads(data.decode("utf-8"))
     elif data_type == AssetType.TEXT:
         return data.decode("utf-8")
+    elif data_type == AssetType.BYTES:
+        return data
     else:
         raise TypeError("Unsupported data type")
 
diff --git a/covalent/_serialize/electron.py b/covalent/_serialize/electron.py
index 5195fc64..3229875a 100644
--- a/covalent/_serialize/electron.py
+++ b/covalent/_serialize/electron.py
@@ -43,6 +43,7 @@ ASSET_TYPES = {
     "deps": AssetType.JSONABLE,
     "call_before": AssetType.JSONABLE,
     "call_after": AssetType.JSONABLE,
+    "qelectron_db": AssetType.BYTES,
     "stdout": AssetType.TEXT,
     "stderr": AssetType.TEXT,
     "error": AssetType.TEXT,
@@ -128,14 +129,6 @@ def _serialize_node_assets(node_attrs: dict, node_storage_path: str) -> Electron
         ASSET_FILENAME_MAP["output"],
     )
 
-    node_error = node_attrs.get("error", "")
-    error_asset = save_asset(
-        node_error,
-        ASSET_TYPES["error"],
-        node_storage_path,
-        ASSET_FILENAME_MAP["error"],
-    )
-
     node_stdout = node_attrs.get("stdout", "")
     stdout_asset = save_asset(
         node_stdout,
@@ -143,14 +136,31 @@ def _serialize_node_assets(node_attrs: dict, node_storage_path: str) -> Electron
         node_storage_path,
         ASSET_FILENAME_MAP["stdout"],
     )
+
     node_stderr = node_attrs.get("stderr", "")
     stderr_asset = save_asset(
-        node_error,
+        node_stderr,
         ASSET_TYPES["stderr"],
         node_storage_path,
         ASSET_FILENAME_MAP["stderr"],
     )
 
+    qelectron_db = node_attrs.get("qelectron_db", bytes())
+    qelectron_db_asset = save_asset(
+        qelectron_db,
+        ASSET_TYPES["qelectron_db"],
+        node_storage_path,
+        ASSET_FILENAME_MAP["qelectron_db"],
+    )
+
+    node_error = node_attrs.get("error", "")
+    error_asset = save_asset(
+        node_error,
+        ASSET_TYPES["error"],
+        node_storage_path,
+        ASSET_FILENAME_MAP["error"],
+    )
+
     deps = node_attrs["metadata"]["deps"]
     deps_asset = save_asset(
         deps, ASSET_TYPES["deps"], node_storage_path, ASSET_FILENAME_MAP["deps"]
@@ -177,12 +187,13 @@ def _serialize_node_assets(node_attrs: dict, node_storage_path: str) -> Electron
         function_string=function_string_asset,
         value=value_asset,
         output=output_asset,
-        deps=deps_asset,
-        call_before=call_before_asset,
-        call_after=call_after_asset,
         stdout=stdout_asset,
         stderr=stderr_asset,
+        qelectron_db=qelectron_db_asset,
         error=error_asset,
+        deps=deps_asset,
+        call_before=call_before_asset,
+        call_after=call_after_asset,
     )
 
 
@@ -191,14 +202,15 @@ def _deserialize_node_assets(ea: ElectronAssets) -> dict:
     function_string = load_asset(ea.function_string, ASSET_TYPES["function_string"])
     value = load_asset(ea.value, ASSET_TYPES["value"])
     output = load_asset(ea.output, ASSET_TYPES["output"])
-    deps = load_asset(ea.deps, ASSET_TYPES["deps"])
-    call_before = load_asset(ea.call_before, ASSET_TYPES["call_before"])
-    call_after = load_asset(ea.call_after, ASSET_TYPES["call_after"])
-
     stdout = load_asset(ea.stdout, ASSET_TYPES["stdout"])
     stderr = load_asset(ea.stderr, ASSET_TYPES["stderr"])
+    qelectron_db = load_asset(ea.qelectron_db, ASSET_TYPES["qelectron_db"])
     error = load_asset(ea.error, ASSET_TYPES["error"])
 
+    deps = load_asset(ea.deps, ASSET_TYPES["deps"])
+    call_before = load_asset(ea.call_before, ASSET_TYPES["call_before"])
+    call_after = load_asset(ea.call_after, ASSET_TYPES["call_after"])
+
     return {
         "function": function,
         "function_string": function_string,
@@ -206,6 +218,7 @@ def _deserialize_node_assets(ea: ElectronAssets) -> dict:
         "output": output,
         "stdout": stdout,
         "stderr": stderr,
+        "qelectron_db": qelectron_db,
         "error": error,
         "metadata": {
             "deps": deps,
@@ -233,8 +246,10 @@ def deserialize_node(e: ElectronSchema, metadata_only: bool = False) -> dict:
 
     asset_metadata = node_assets.pop("metadata")
 
+    if not metadata_only:
+        node_attrs.update(node_assets)
+
     # merge "metadata" attrs
-    node_attrs.update(node_assets)
     node_attrs["metadata"].update(asset_metadata)
 
     return {"id": e.id, "attrs": node_attrs}
diff --git a/covalent/_serialize/result.py b/covalent/_serialize/result.py
index c55fe130..29c2f817 100644
--- a/covalent/_serialize/result.py
+++ b/covalent/_serialize/result.py
@@ -110,7 +110,7 @@ def deserialize_result(res: ResultSchema) -> Result:
 
 def strip_local_uris(res: ResultSchema) -> ResultSchema:
     # Create a copy with the local uris removed for submission
-    manifest = res.copy(deep=True).dict()
+    manifest = res.model_copy(deep=True).model_dump()
 
     # Strip workflow asset uris:
     dispatch_assets = manifest["assets"]
@@ -131,7 +131,7 @@ def strip_local_uris(res: ResultSchema) -> ResultSchema:
         for _, asset in node_assets.items():
             asset["uri"] = ""
 
-    return ResultSchema.parse_obj(manifest)
+    return ResultSchema.model_validate(manifest)
 
 
 # Functions to postprocess response from dispatcher
diff --git a/covalent/_shared_files/qelectron_utils.py b/covalent/_shared_files/qelectron_utils.py
index 4b5e5874..1c0a8c6f 100644
--- a/covalent/_shared_files/qelectron_utils.py
+++ b/covalent/_shared_files/qelectron_utils.py
@@ -14,124 +14,27 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import base64
-import re
-from pathlib import Path
-from typing import Tuple
 
-from ..executor.utils.context import get_context
-from .config import get_config
-from .logger import app_log
-
-_QE_DB_DATA_MARKER = "<====QELECTRON_DB_DATA====>"
-_DATA_FILENAME = "data.mdb"
-
-QE_DB_DIRNAME = ".database"
-
-
-def print_qelectron_db() -> None:
-    """
-    Check for QElectron database file and dump it into stdout
-
-    Args(s)
-        None
-
-    Return(s)
-        None
-    """
-    context = get_context()
-    node_id, dispatch_id = context.node_id, context.dispatch_id
-
-    db_dir = Path(get_config("dispatcher")["qelectron_db_path"]).resolve()
-    task_subdir = db_dir / dispatch_id / f"node-{node_id}"
-    if not task_subdir.exists():
-        # qelectron database not found for dispatch_id/node
-        return
-
-    with open(task_subdir / _DATA_FILENAME, "rb") as data_mdb_file:
-        data_bytes = base64.b64encode(data_mdb_file.read())
-
-    output_string = "".join([_QE_DB_DATA_MARKER, data_bytes.decode(), _QE_DB_DATA_MARKER])
-    app_log.debug(f"Printing Qelectron data for node_id {node_id}")
-    print(output_string)
-
-
-def extract_qelectron_db(s: str) -> Tuple[str, bytes]:
-    """
-    Detect Qelectron data in `s` and process into dict if found
+from covalent.quantum.qserver.database import Database
 
-    Arg(s):
-        s: captured stdout string from a node in the transport graph
-
-    Return(s):
-        s_without_db: captured stdout string without Qelectron data
-        bytes_data: bytes representing the `data.mdb` file
-    """
-    # do nothing if string is empty or no database bytes found in the `s`
-    data_pattern = f".*{_QE_DB_DATA_MARKER}(.*){_QE_DB_DATA_MARKER}.*"
-    if not s or not (_matches := re.findall(data_pattern, s)):
-        app_log.debug("No Qelectron data detected")
-        return s, b""
-
-    # load qelectron data and convert back to bytes
-    app_log.debug("Detected Qelectron output data")
-    bytes_data = base64.b64decode(_matches.pop())
-
-    # remove decoded database bytes from `s`
-    s_without_db = remove_qelectron_db(s)
-
-    return s_without_db, bytes_data
-
-
-def remove_qelectron_db(output: str):
-    """
-    Replace the Qelectron DB string in `s` with the empty string.
-
-    Arg:
-        output: captured stdout string
-
-    Return:
-        the output string without QElectron database removed
-    """
-    output = re.sub(f"{_QE_DB_DATA_MARKER}.*{_QE_DB_DATA_MARKER}", "", output)
-    return output.strip()
+from .logger import app_log
 
 
-def write_qelectron_db(
-    dispatch_id: str,
-    node_id: int,
-    bytes_data: bytes,
-) -> None:
+def get_qelectron_db_path(dispatch_id: str, task_id: int):
     """
-    Reproduces the Qelectron database inside the results_dir sub-directory for
-    given dispatch and node IDs.
-
-    That is, creates the tree
-
-    covalent
-    └── data
-        └── <dispatch-id>
-            └── .database
-                └── <dispatch-id>  # redundant hierarchy here to mimic QServer DB
-                    └── <node-id>
-                        └── data.mdb
+    Return the path to the Qelectron database for a given dispatch_id and task_id.
 
-    inside the `results_dir/dispatch_id`.
+    WARNING: SHOULD ONLY BE USED FROM THE SAME MACHINE
+    AS WHERE THE USER'S TASK FUNCTION IS BEING RUN.
     """
-    results_dir = Path(get_config("dispatcher")["results_dir"]).resolve()
 
-    # create the database directory if it does not exist
-    qelectron_db_dir = results_dir / dispatch_id / QE_DB_DIRNAME
-    if not qelectron_db_dir.exists():
-        qelectron_db_dir.mkdir()
+    database = Database()
 
-    # create node subdirectory if it does not exist
-    node_dir = qelectron_db_dir / dispatch_id / f"node-{node_id}"
-    if not node_dir.exists():
-        node_dir.mkdir(parents=True)
+    db_path = database.get_db_path(dispatch_id=dispatch_id, node_id=task_id)
 
-    # write 'data.mdb' file
-    data_mdb_path = node_dir / _DATA_FILENAME
-    app_log.debug(f"Writing Qelectron database file {str(data_mdb_path)}")
-    with open(data_mdb_path, "wb") as data_mdb_file:
-        data_mdb_file.write(bytes_data)
+    if db_path.exists():
+        app_log.debug(f"Found qelectron DB for task {task_id}")
+        return db_path
+    else:
+        app_log.debug(f"Qelectron database not found for task {task_id}")
+        return None
diff --git a/covalent/_shared_files/schemas/electron.py b/covalent/_shared_files/schemas/electron.py
index e2b06e57..3635724e 100644
--- a/covalent/_shared_files/schemas/electron.py
+++ b/covalent/_shared_files/schemas/electron.py
@@ -30,7 +30,7 @@ ELECTRON_METADATA_KEYS = {
     "start_time",
     "end_time",
     "status",
-    # electron metadata
+    # user dependent metadata
     "executor",
     "executor_data",
     "qelectron_data_exists",
@@ -44,7 +44,8 @@ ELECTRON_ASSET_KEYS = {
     "error",
     "stdout",
     "stderr",
-    # electron metadata
+    "qelectron_db",
+    # user dependent assets
     "deps",
     "call_before",
     "call_after",
@@ -55,6 +56,9 @@ ELECTRON_FUNCTION_STRING_FILENAME = "function_string.txt"
 ELECTRON_VALUE_FILENAME = "value.tobj"
 ELECTRON_STDOUT_FILENAME = "stdout.log"
 ELECTRON_STDERR_FILENAME = "stderr.log"
+ELECTRON_QELECTRON_DB_FILENAME = (
+    "data.mdb"  # This NEEDS to be data.mdb so that the qelectron's Database class can find it
+)
 ELECTRON_ERROR_FILENAME = "error.log"
 ELECTRON_RESULTS_FILENAME = "results.tobj"
 ELECTRON_DEPS_FILENAME = "deps.json"
@@ -68,12 +72,13 @@ ASSET_FILENAME_MAP = {
     "function_string": ELECTRON_FUNCTION_STRING_FILENAME,
     "value": ELECTRON_VALUE_FILENAME,
     "output": ELECTRON_RESULTS_FILENAME,
-    "deps": ELECTRON_DEPS_FILENAME,
-    "call_before": ELECTRON_CALL_BEFORE_FILENAME,
-    "call_after": ELECTRON_CALL_AFTER_FILENAME,
     "stdout": ELECTRON_STDOUT_FILENAME,
     "stderr": ELECTRON_STDERR_FILENAME,
+    "qelectron_db": ELECTRON_QELECTRON_DB_FILENAME,
     "error": ELECTRON_ERROR_FILENAME,
+    "deps": ELECTRON_DEPS_FILENAME,
+    "call_before": ELECTRON_CALL_BEFORE_FILENAME,
+    "call_after": ELECTRON_CALL_AFTER_FILENAME,
 }
 
 
@@ -85,8 +90,9 @@ class ElectronAssets(BaseModel):
     error: Optional[AssetSchema] = None
     stdout: Optional[AssetSchema] = None
     stderr: Optional[AssetSchema] = None
+    qelectron_db: AssetSchema
 
-    # electron_metadata attached by the user
+    # user dependent assets
     deps: AssetSchema
     call_before: AssetSchema
     call_after: AssetSchema
diff --git a/covalent/_shared_files/schemas/lattice.py b/covalent/_shared_files/schemas/lattice.py
index ffc38654..1bc7f4da 100644
--- a/covalent/_shared_files/schemas/lattice.py
+++ b/covalent/_shared_files/schemas/lattice.py
@@ -27,7 +27,7 @@ LATTICE_METADATA_KEYS = {
     "__name__",
     "python_version",
     "covalent_version",
-    # metadata
+    # user dependent metadata
     "executor",
     "workflow_executor",
     "executor_data",
@@ -43,7 +43,7 @@ LATTICE_ASSET_KEYS = {
     "named_kwargs",
     "cova_imports",
     "lattice_imports",
-    # metadata
+    # user dependent assets
     "deps",
     "call_before",
     "call_after",
diff --git a/covalent/executor/base.py b/covalent/executor/base.py
index 562af7b2..1e7670ad 100644
--- a/covalent/executor/base.py
+++ b/covalent/executor/base.py
@@ -37,7 +37,6 @@ from covalent.executor.utils import Signals
 
 from .._shared_files import TaskRuntimeError, logger
 from .._shared_files.context_managers import active_dispatch_info_manager
-from .._shared_files.qelectron_utils import remove_qelectron_db
 from .._shared_files.util_classes import RESULT_STATUS, DispatchInfo, Status
 from .schemas import TaskUpdate
 
@@ -298,7 +297,7 @@ class BaseExecutor(_AbstractBaseExecutor):
                 filename.touch(exist_ok=True)
 
                 with open(filepath, "a") as f:
-                    f.write(remove_qelectron_db(ss))
+                    f.write(ss)
 
     async def _execute(
         self,
@@ -682,7 +681,7 @@ class AsyncBaseExecutor(_AbstractBaseExecutor):
                 filename.touch(exist_ok=True)
 
                 async with aiofiles.open(filepath, "a") as f:
-                    await f.write(remove_qelectron_db(ss))
+                    await f.write(ss)
 
     async def _execute(
         self,
diff --git a/covalent/executor/executor_plugins/dask.py b/covalent/executor/executor_plugins/dask.py
index d23abc5e..73b719b9 100644
--- a/covalent/executor/executor_plugins/dask.py
+++ b/covalent/executor/executor_plugins/dask.py
@@ -249,7 +249,10 @@ class DaskExecutor(AsyncBaseExecutor):
             result_uri = os.path.join(self.cache_dir, f"result_{dispatch_id}-{node_id}.pkl")
             stdout_uri = os.path.join(self.cache_dir, f"stdout_{dispatch_id}-{node_id}.txt")
             stderr_uri = os.path.join(self.cache_dir, f"stderr_{dispatch_id}-{node_id}.txt")
-            output_uris.append((result_uri, stdout_uri, stderr_uri))
+            qelectron_db_uri = os.path.join(
+                self.cache_dir, f"qelectron_db_{dispatch_id}-{node_id}.mdb"
+            )
+            output_uris.append((result_uri, stdout_uri, stderr_uri, qelectron_db_uri))
 
         server_url = format_server_url()
 
@@ -259,8 +262,8 @@ class DaskExecutor(AsyncBaseExecutor):
 
         future = dask_client.submit(
             run_task_from_uris_alt,
-            list(map(lambda t: t.dict(), task_specs)),
-            resources.dict(),
+            list(map(lambda t: t.model_dump(), task_specs)),
+            resources.model_dump(),
             output_uris,
             self.cache_dir,
             task_group_metadata,
@@ -295,7 +298,7 @@ class DaskExecutor(AsyncBaseExecutor):
         if not data:
             terminal_status = RESULT_STATUS.CANCELLED
         else:
-            received = ReceiveModel.parse_obj(data)
+            received = ReceiveModel.model_validate(data)
             terminal_status = Status(received.status.value)
 
         for task_id in task_ids:
@@ -306,6 +309,7 @@ class DaskExecutor(AsyncBaseExecutor):
                 output_uri = ""
                 stdout_uri = ""
                 stderr_uri = ""
+                qelectron_db_uri = ""
 
             else:
                 result_path = os.path.join(self.cache_dir, f"result-{dispatch_id}:{task_id}.json")
@@ -315,6 +319,7 @@ class DaskExecutor(AsyncBaseExecutor):
                     output_uri = result_summary["output_uri"]
                     stdout_uri = result_summary["stdout_uri"]
                     stderr_uri = result_summary["stderr_uri"]
+                    qelectron_db_uri = result_summary["qelectron_db_uri"]
                     exception_raised = result_summary["exception_occurred"]
 
                 terminal_status = (
@@ -335,6 +340,9 @@ class DaskExecutor(AsyncBaseExecutor):
                     "stderr": {
                         "remote_uri": stderr_uri,
                     },
+                    "qelectron_db": {
+                        "remote_uri": qelectron_db_uri,
+                    },
                 },
             }
 
diff --git a/covalent/executor/executor_plugins/local.py b/covalent/executor/executor_plugins/local.py
index aaba7a04..5cf2659b 100644
--- a/covalent/executor/executor_plugins/local.py
+++ b/covalent/executor/executor_plugins/local.py
@@ -169,15 +169,18 @@ class LocalExecutor(BaseExecutor):
             result_uri = os.path.join(self.cache_dir, f"result_{dispatch_id}-{node_id}.pkl")
             stdout_uri = os.path.join(self.cache_dir, f"stdout_{dispatch_id}-{node_id}.txt")
             stderr_uri = os.path.join(self.cache_dir, f"stderr_{dispatch_id}-{node_id}.txt")
-            output_uris.append((result_uri, stdout_uri, stderr_uri))
+            qelectron_db_uri = os.path.join(
+                self.cache_dir, f"qelectron_db_{dispatch_id}-{node_id}.mdb"
+            )
+            output_uris.append((result_uri, stdout_uri, stderr_uri, qelectron_db_uri))
 
         server_url = format_server_url()
 
         app_log.debug(f"Running task group {dispatch_id}:{task_ids}")
         future = proc_pool.submit(
             run_task_from_uris,
-            list(map(lambda t: t.dict(), task_specs)),
-            resources.dict(),
+            list(map(lambda t: t.model_dump(), task_specs)),
+            resources.model_dump(),
             output_uris,
             self.cache_dir,
             task_group_metadata,
@@ -210,7 +213,7 @@ class LocalExecutor(BaseExecutor):
             if not data:
                 terminal_status = RESULT_STATUS.CANCELLED
             else:
-                received = ReceiveModel.parse_obj(data)
+                received = ReceiveModel.model_validate(data)
                 terminal_status = Status(received.status.value)
 
             task_result = {
@@ -227,6 +230,9 @@ class LocalExecutor(BaseExecutor):
                     "stderr": {
                         "remote_uri": "",
                     },
+                    "qelectron_db": {
+                        "remote_uri": "",
+                    },
                 },
             }
 
diff --git a/covalent/executor/utils/context.py b/covalent/executor/utils/context.py
index 2bd2a177..dd1d2c05 100644
--- a/covalent/executor/utils/context.py
+++ b/covalent/executor/utils/context.py
@@ -20,8 +20,8 @@ from pydantic import BaseModel
 
 
 class Context(BaseModel):
-    node_id: int = None
     dispatch_id: str = None
+    task_id: int = None
 
 
 def get_context():
@@ -29,10 +29,10 @@ def get_context():
 
 
 @contextmanager
-def set_context(node_id: int, dispatch_id: str):
+def set_context(dispatch_id: int, task_id: str):
     global current_context
     global unset_context
-    current_context = Context(node_id=node_id, dispatch_id=dispatch_id)
+    current_context = Context(dispatch_id=dispatch_id, task_id=task_id)
     yield
     current_context = unset_context
 
diff --git a/covalent/executor/utils/wrappers.py b/covalent/executor/utils/wrappers.py
index b2199b0d..15f406b5 100644
--- a/covalent/executor/utils/wrappers.py
+++ b/covalent/executor/utils/wrappers.py
@@ -29,10 +29,12 @@ from typing import Any, Callable, Dict, List, Tuple
 
 import requests
 
+from covalent._shared_files.qelectron_utils import get_qelectron_db_path
 from covalent._workflow.depsbash import DepsBash
 from covalent._workflow.depscall import RESERVED_RETVAL_KEY__FILES, DepsCall
 from covalent._workflow.depspip import DepsPip
 from covalent._workflow.transport import TransportableObject
+from covalent.executor.utils import set_context
 from covalent.executor.utils.serialize import deserialize_node_asset, serialize_node_asset
 
 
@@ -162,6 +164,14 @@ def run_task_from_uris(
     task_group_metadata: dict,
     server_url: str,
 ):
+    """
+    Run the task from URIs.
+
+    This is appropriate for executors which can access the Covalent
+    server url directly. Exampl: LocalExecutor.
+
+    """
+
     prefix = "file://"
     prefix_len = len(prefix)
 
@@ -175,7 +185,10 @@ def run_task_from_uris(
     os.environ["COVALENT_DISPATCHER_URL"] = server_url
 
     for i, task in enumerate(task_specs):
-        result_uri, stdout_uri, stderr_uri = output_uris[i]
+        result_uri, stdout_uri, stderr_uri, qelectron_db_uri = output_uris[i]
+
+        # Setting these to empty bytes in case the task fails
+        qelectron_db_bytes = bytes()
 
         with open(stdout_uri, "w") as stdout, open(stderr_uri, "w") as stderr:
             with redirect_stdout(stdout), redirect_stderr(stderr):
@@ -229,13 +242,20 @@ def run_task_from_uris(
                     )
                     exception_occurred = False
 
-                    transportable_output = wrapper_fn(
-                        serialized_fn, call_before, call_after, *ser_args, **ser_kwargs
-                    )
+                    with set_context(dispatch_id, task_id):
+                        transportable_output = wrapper_fn(
+                            serialized_fn, call_before, call_after, *ser_args, **ser_kwargs
+                        )
+
                     ser_output = serialize_node_asset(transportable_output, "output")
                     with open(result_uri, "wb") as f:
                         f.write(ser_output)
 
+                    qelectron_db_path = get_qelectron_db_path(dispatch_id, task_id)
+                    if qelectron_db_path is not None:
+                        with open(qelectron_db_path / "data.mdb", "rb") as f:
+                            qelectron_db_bytes = f.read()
+
                     outputs[task_id] = result_uri
 
                     result_summary = {
@@ -243,6 +263,7 @@ def run_task_from_uris(
                         "output_uri": result_uri,
                         "stdout_uri": stdout_uri,
                         "stderr_uri": stderr_uri,
+                        "qelectron_db_uri": qelectron_db_uri,
                         "exception_occurred": exception_occurred,
                     }
 
@@ -256,6 +277,7 @@ def run_task_from_uris(
                         "output_uri": result_uri,
                         "stdout_uri": stdout_uri,
                         "stderr_uri": stderr_uri,
+                        "qelectron_db_uri": qelectron_db_uri,
                         "exception_occurred": exception_occurred,
                     }
 
@@ -282,6 +304,10 @@ def run_task_from_uris(
                             headers = {"Content-Length": os.path.getsize(stderr_uri)}
                             requests.put(upload_url, data=f)
 
+                    if qelectron_db_bytes:
+                        upload_url = f"{server_url}/api/v2/dispatches/{dispatch_id}/electrons/{task_id}/assets/qelectron_db"
+                        requests.put(upload_url, data=qelectron_db_bytes)
+
                     result_path = os.path.join(results_dir, f"result-{dispatch_id}:{task_id}.json")
 
                     with open(result_path, "w") as f:
@@ -304,6 +330,7 @@ def run_task_from_uris(
                 "output_uri": "",
                 "stdout_uri": "",
                 "stderr_uri": "",
+                "qelectron_db_uri": "",
                 "exception_occurred": True,
             }
 
@@ -327,13 +354,16 @@ def run_task_from_uris_alt(
     task_group_metadata: dict,
     server_url: str,
 ):
-    """Alternate form of run_task_from_uris for sync executors.
+    """
+    Alternate form of run_task_from_uris for sync executors.
 
     This is appropriate for backends that cannot reach the Covalent
     server. Covalent will push input assets to the executor's
     persistent storage before invoking `Executor.send()` and pull output
     artifacts after `Executor.receive()`.
 
+    Example: DaskExecutor.
+
     """
 
     prefix = "file://"
@@ -349,7 +379,10 @@ def run_task_from_uris_alt(
     os.environ["COVALENT_DISPATCHER_URL"] = server_url
 
     for i, task in enumerate(task_specs):
-        result_uri, stdout_uri, stderr_uri = output_uris[i]
+        result_uri, stdout_uri, stderr_uri, qelectron_db_uri = output_uris[i]
+
+        # Setting these to empty bytes in case the task fails
+        qelectron_db_bytes = bytes()
 
         with open(stdout_uri, "w") as stdout, open(stderr_uri, "w") as stderr:
             with redirect_stdout(stdout), redirect_stderr(stderr):
@@ -410,17 +443,30 @@ def run_task_from_uris_alt(
                     call_before, call_after = _gather_deps(
                         deps_json, call_before_json, call_after_json
                     )
+
                     exception_occurred = False
 
-                    transportable_output = wrapper_fn(
-                        serialized_fn, call_before, call_after, *ser_args, **ser_kwargs
-                    )
+                    # Run the task function
+                    with set_context(dispatch_id, task_id):
+                        transportable_output = wrapper_fn(
+                            serialized_fn, call_before, call_after, *ser_args, **ser_kwargs
+                        )
+
                     ser_output = serialize_node_asset(transportable_output, "output")
 
                     # Save output
                     with open(result_uri, "wb") as f:
                         f.write(ser_output)
 
+                    # Save QElectron DB
+                    qelectron_db_path = get_qelectron_db_path(dispatch_id, task_id)
+                    if qelectron_db_path is not None:
+                        with open(qelectron_db_path / "data.mdb", "rb") as f:
+                            qelectron_db_bytes = f.read()
+
+                    with open(qelectron_db_uri, "wb") as f:
+                        f.write(qelectron_db_bytes)
+
                     resources["inputs"][task_id] = result_uri
 
                     result_summary = {
@@ -428,6 +474,7 @@ def run_task_from_uris_alt(
                         "output_uri": result_uri,
                         "stdout_uri": stdout_uri,
                         "stderr_uri": stderr_uri,
+                        "qelectron_db_uri": qelectron_db_uri,
                         "exception_occurred": exception_occurred,
                     }
 
@@ -441,6 +488,7 @@ def run_task_from_uris_alt(
                         "output_uri": result_uri,
                         "stdout_uri": stdout_uri,
                         "stderr_uri": stderr_uri,
+                        "qelectron_db_uri": qelectron_db_uri,
                         "exception_occurred": exception_occurred,
                     }
 
@@ -451,7 +499,7 @@ def run_task_from_uris_alt(
                     result_path = os.path.join(results_dir, f"result-{dispatch_id}:{task_id}.json")
 
                     # Write the summary file containing the URIs for
-                    # the serialized result, stdout, and stderr
+                    # the serialized result, stdout, stderr, and qelectron_db
                     with open(result_path, "w") as f:
                         json.dump(result_summary, f)
 
@@ -464,6 +512,7 @@ def run_task_from_uris_alt(
                 "output_uri": "",
                 "stdout_uri": "",
                 "stderr_uri": "",
+                "qelectron_db_uri": "",
                 "exception_occurred": True,
             }
 
diff --git a/covalent/quantum/qserver/core.py b/covalent/quantum/qserver/core.py
index 340f4c64..f8efe09d 100644
--- a/covalent/quantum/qserver/core.py
+++ b/covalent/quantum/qserver/core.py
@@ -247,7 +247,7 @@ class QServer:
             circuit_id = get_circuit_id(batch_id, i)
             key_value_pairs[0].append(circuit_id)
             circuit_info = CircuitInfo(
-                electron_node_id=context.node_id,
+                electron_node_id=context.task_id,
                 dispatch_id=context.dispatch_id,
                 circuit_name=qelectron_info.name,
                 circuit_description=qelectron_info.description,
@@ -295,7 +295,7 @@ class QServer:
         # ]
 
         self._database.set(
-            *key_value_pairs, dispatch_id=context.dispatch_id, node_id=context.node_id
+            *key_value_pairs, dispatch_id=context.dispatch_id, node_id=context.task_id
         )
 
         return batch_id
@@ -378,7 +378,7 @@ class QServer:
             # [[exec_4], [exec_2, {2: future_3}], [exec_3, {4: future_5}]]
 
         self._database.set(
-            *key_value_pairs, dispatch_id=context.dispatch_id, node_id=context.node_id
+            *key_value_pairs, dispatch_id=context.dispatch_id, node_id=context.task_id
         )
 
         # An example `results_dict` will look like:
diff --git a/covalent/quantum/qserver/database.py b/covalent/quantum/qserver/database.py
index eb2becef..59d98b09 100644
--- a/covalent/quantum/qserver/database.py
+++ b/covalent/quantum/qserver/database.py
@@ -29,6 +29,9 @@ def set_serialization_strategy(strategy_name):
 
 
 class Database:
+    # TODO: Should probably revisit the path mechanism later as it
+    #       can be implemented in a more elegant way.
+
     # dash-separated names result in fallback strategy with try/except loops,
     # other valid strategy names uses the one strategy every time
     # see `covalent_qelectron/quantum_server/serialize.py`
@@ -46,7 +49,11 @@ class Database:
         else:
             self.db_dir = Path(get_config("dispatcher")["qelectron_db_path"])
 
-    def _get_db_path(self, dispatch_id, node_id, *, mkdir=False):
+    def get_db_path(self, dispatch_id=None, node_id=None, *, mkdir=False, direct_path=False):
+        if direct_path:
+            # If the .mdb file is directly located in the db_dir
+            return self.db_dir.resolve().absolute()
+
         dispatch_id = "default-dispatch" if dispatch_id is None else dispatch_id
         node_id = "default-node" if node_id is None else node_id
         db_path = self.db_dir.joinpath(dispatch_id, f"node-{node_id}")
@@ -55,8 +62,8 @@ class Database:
 
         return db_path.resolve().absolute()
 
-    def _open(self, dispatch_id, node_id, mkdir=False):
-        db_path = self._get_db_path(dispatch_id, node_id, mkdir=mkdir)
+    def _open(self, dispatch_id, node_id, mkdir=False, direct_path=False):
+        db_path = self.get_db_path(dispatch_id, node_id, mkdir=mkdir, direct_path=direct_path)
 
         if not db_path.exists():
             raise FileNotFoundError(f"Missing database directory {db_path}.")
@@ -65,8 +72,8 @@ class Database:
             file=str(db_path), flag="c", strategy_name=self.strategy_name
         )
 
-    def set(self, keys, values, *, dispatch_id, node_id):
-        with self._open(dispatch_id, node_id, mkdir=True) as db:
+    def set(self, keys, values, *, dispatch_id, node_id, direct_path=False):
+        with self._open(dispatch_id, node_id, mkdir=True, direct_path=direct_path) as db:
             for i, circuit_id in enumerate(keys):
                 stored_val: dict = db.get(circuit_id, None)
                 if stored_val is None:
@@ -77,17 +84,17 @@ class Database:
 
             db.update(dict(zip(keys, values)))
 
-    def get_circuit_ids(self, *, dispatch_id, node_id):
-        with self._open(dispatch_id, node_id) as db:
+    def get_circuit_ids(self, *, dispatch_id, node_id, direct_path=False):
+        with self._open(dispatch_id, node_id, direct_path=direct_path) as db:
             return list(db.keys())
 
-    def get_circuit_info(self, circuit_id, *, dispatch_id, node_id):
-        with self._open(dispatch_id, node_id) as db:
+    def get_circuit_info(self, circuit_id, *, dispatch_id, node_id, direct_path=False):
+        with self._open(dispatch_id, node_id, direct_path=direct_path) as db:
             return CircuitInfo(**db.get(circuit_id, None))
 
-    def get_db(self, *, dispatch_id, node_id):
+    def get_db_dict(self, *, dispatch_id, node_id, direct_path=False):
         db_copy = {}
-        with self._open(dispatch_id, node_id) as db:
+        with self._open(dispatch_id, node_id, direct_path=direct_path) as db:
             for key, value in db.items():
                 db_copy[key] = value
 
diff --git a/covalent_dispatcher/_core/data_manager.py b/covalent_dispatcher/_core/data_manager.py
index c41c48c8..a03a3df6 100644
--- a/covalent_dispatcher/_core/data_manager.py
+++ b/covalent_dispatcher/_core/data_manager.py
@@ -28,7 +28,6 @@ from pydantic import ValidationError
 from covalent._dispatcher_plugins.local import LocalDispatcher
 from covalent._results_manager import Result
 from covalent._shared_files import logger
-from covalent._shared_files.qelectron_utils import extract_qelectron_db, write_qelectron_db
 from covalent._shared_files.schemas.result import ResultSchema
 from covalent._shared_files.util_classes import RESULT_STATUS
 from covalent._workflow.lattice import Lattice
@@ -45,9 +44,7 @@ app_log = logger.app_log
 log_stack_info = logger.log_stack_info
 
 
-# TODO: Remove dispatch_id from the signature once qelectron_db becomes an Asset (PR #1690)
 def generate_node_result(
-    dispatch_id: str,
     node_id: int,
     node_name: str = None,
     start_time=None,
@@ -57,12 +54,12 @@ def generate_node_result(
     error=None,
     stdout=None,
     stderr=None,
+    qelectron_data_exists=None,
 ):
     """
     Helper routine to prepare the node result
 
     Arg(s)
-        dispatch_id: ID of the dispatched workflow
         node_id: ID of the node in the trasport graph
         node_name: Name of the node
         start_time: Start time of the node
@@ -72,16 +69,11 @@ def generate_node_result(
         error: Error from the node
         stdout: STDOUT of a node
         stderr: STDERR generated during node execution
+        qelectron_data_exists: Whether the qelectron data exists
 
     Return(s)
         Dictionary of the inputs
     """
-    clean_stdout, bytes_data = extract_qelectron_db(stdout)
-    qelectron_data_exists = bool(bytes_data)
-
-    if qelectron_data_exists:
-        app_log.debug(f"Reproducing Qelectron database for node {node_id}")
-        write_qelectron_db(dispatch_id, node_id, bytes_data)
 
     return {
         "node_id": node_id,
@@ -91,9 +83,9 @@ def generate_node_result(
         "status": status,
         "output": output,
         "error": error,
-        "stdout": clean_stdout,
+        "stdout": stdout,
         "stderr": stderr,
-        "qelectron_data_exists": qelectron_data_exists,
+        "qelectron_data_exists": qelectron_data_exists,  # TODO: This field is now defunct, see PR #1850
     }
 
 
@@ -227,7 +219,6 @@ async def _update_parent_electron(dispatch_id: str):
         dispatch_id, node_id = resolve_electron_id(parent_eid)
         status = dispatch_attrs["status"]
         node_result = generate_node_result(
-            dispatch_id=dispatch_id,
             node_id=node_id,
             end_time=dispatch_attrs["end_time"],
             status=status,
diff --git a/covalent_dispatcher/_core/runner.py b/covalent_dispatcher/_core/runner.py
index 08b990cd..b38cc105 100644
--- a/covalent_dispatcher/_core/runner.py
+++ b/covalent_dispatcher/_core/runner.py
@@ -19,7 +19,6 @@ Defines the core functionality of the legacy runner
 """
 
 import asyncio
-import importlib
 import traceback
 from datetime import datetime, timezone
 from functools import partial
@@ -29,8 +28,6 @@ from covalent._shared_files import logger
 from covalent._shared_files.config import get_config
 from covalent._shared_files.util_classes import RESULT_STATUS
 from covalent._workflow import DepsBash, DepsCall, DepsPip
-from covalent._workflow.transport import TransportableObject
-from covalent.executor.utils import set_context
 from covalent.executor.utils.wrappers import wrapper_fn
 
 from . import data_manager as datasvc
@@ -91,7 +88,6 @@ async def _run_abstract_task(
     except Exception as ex:
         app_log.error(f"Exception when trying to resolve inputs or deps: {ex}")
         node_result = datasvc.generate_node_result(
-            dispatch_id=dispatch_id,
             node_id=node_id,
             start_time=timestamp,
             end_time=timestamp,
@@ -101,7 +97,6 @@ async def _run_abstract_task(
         return node_result
 
     node_result = datasvc.generate_node_result(
-        dispatch_id=dispatch_id,
         node_name=node_name,
         node_id=node_id,
         start_time=timestamp,
@@ -168,7 +163,6 @@ async def _run_task(
         app_log.debug(tb)
         error_msg = tb if debug_mode else str(ex)
         node_result = datasvc.generate_node_result(
-            dispatch_id=dispatch_id,
             node_id=node_id,
             end_time=datetime.now(timezone.utc),
             status=RESULT_STATUS.FAILED,
@@ -180,34 +174,7 @@ async def _run_task(
     try:
         app_log.debug(f"Executing task {node_name}")
 
-        def qelectron_compatible_wrapper(node_id, dispatch_id, ser_user_fn, *args, **kwargs):
-            user_fn = ser_user_fn.get_deserialized()
-
-            try:
-                mod_qe_utils = importlib.import_module("covalent._shared_files.qelectron_utils")
-
-                with set_context(node_id, dispatch_id):
-                    res = user_fn(*args, **kwargs)
-                    mod_qe_utils.print_qelectron_db()
-
-                return res
-            except ModuleNotFoundError:
-                return user_fn(*args, **kwargs)
-
-        serialized_callable = TransportableObject(
-            partial(qelectron_compatible_wrapper, node_id, dispatch_id, serialized_callable)
-        )
-
         assembled_callable = partial(wrapper_fn, serialized_callable, call_before, call_after)
-        execute_callable = partial(
-            executor.execute,
-            function=assembled_callable,
-            args=inputs["args"],
-            kwargs=inputs["kwargs"],
-            dispatch_id=dispatch_id,
-            results_dir=results_dir,
-            node_id=node_id,
-        )
 
         # Start listening for messages from the plugin
         asyncio.create_task(executor_proxy.watch(dispatch_id, node_id, executor))
@@ -222,7 +189,6 @@ async def _run_task(
         )
 
         node_result = datasvc.generate_node_result(
-            dispatch_id=dispatch_id,
             node_id=node_id,
             node_name=node_name,
             end_time=datetime.now(timezone.utc),
@@ -238,7 +204,6 @@ async def _run_task(
         app_log.debug(tb)
         error_msg = tb if debug_mode else str(ex)
         node_result = datasvc.generate_node_result(
-            dispatch_id=dispatch_id,
             node_id=node_id,
             node_name=node_name,
             end_time=datetime.now(timezone.utc),
diff --git a/covalent_dispatcher/_core/runner_ng.py b/covalent_dispatcher/_core/runner_ng.py
index f4157929..b3988de8 100644
--- a/covalent_dispatcher/_core/runner_ng.py
+++ b/covalent_dispatcher/_core/runner_ng.py
@@ -131,7 +131,6 @@ async def _submit_abstract_task_group(
         ts = datetime.now(timezone.utc)
         node_results = [
             datamgr.generate_node_result(
-                dispatch_id=dispatch_id,
                 node_id=task_id,
                 start_time=ts,
                 status=RESULT_STATUS.RUNNING,
@@ -164,7 +163,6 @@ async def _submit_abstract_task_group(
 
         node_results = [
             datamgr.generate_node_result(
-                dispatch_id=dispatch_id,
                 node_id=task_id,
                 end_time=datetime.now(timezone.utc),
                 status=RESULT_STATUS.CANCELLED,
@@ -183,7 +181,6 @@ async def _submit_abstract_task_group(
 
         node_results = [
             datamgr.generate_node_result(
-                dispatch_id=dispatch_id,
                 node_id=task_id,
                 end_time=datetime.now(timezone.utc),
                 status=RESULT_STATUS.FAILED,
@@ -235,7 +232,6 @@ async def _get_task_result(task_group_metadata: Dict, data: Any):
             await am.download_assets_for_node(dispatch_id, task_id, task_result.assets)
 
             node_result = datamgr.generate_node_result(
-                dispatch_id=dispatch_id,
                 node_id=task_id,
                 end_time=datetime.now(timezone.utc),
                 status=status,
@@ -251,7 +247,6 @@ async def _get_task_result(task_group_metadata: Dict, data: Any):
 
         node_results = [
             datamgr.generate_node_result(
-                dispatch_id=dispatch_id,
                 node_id=node_id,
                 end_time=ts,
                 status=RESULT_STATUS.FAILED,
@@ -338,7 +333,6 @@ async def run_abstract_task_group(
         send_retval = None
         node_results = [
             datamgr.generate_node_result(
-                dispatch_id=dispatch_id,
                 node_id=node_id,
                 start_time=ts,
                 end_time=ts,
@@ -394,7 +388,6 @@ async def _listen_for_job_events():
                 ts = datetime.now(timezone.utc)
                 for task_id in task_ids:
                     node_result = datamgr.generate_node_result(
-                        dispatch_id=dispatch_id,
                         node_id=task_id,
                         end_time=ts,
                         status=RESULT_STATUS.FAILED,
diff --git a/covalent_dispatcher/_dal/importers/electron.py b/covalent_dispatcher/_dal/importers/electron.py
index e429231c..60b1a3e0 100644
--- a/covalent_dispatcher/_dal/importers/electron.py
+++ b/covalent_dispatcher/_dal/importers/electron.py
@@ -103,7 +103,7 @@ def _get_electron_meta(
 
     legacy_kwargs = {
         "storage_type": ELECTRON_STORAGE_TYPE,
-        "storage_path": str(node_storage_path),
+        "storage_path": node_storage_path,
         "function_filename": ELECTRON_FUNCTION_FILENAME,
         "function_string_filename": ELECTRON_FUNCTION_STRING_FILENAME,
         "results_filename": ELECTRON_RESULTS_FILENAME,
diff --git a/covalent_dispatcher/_dal/importers/tg.py b/covalent_dispatcher/_dal/importers/tg.py
index 28039965..468abdad 100644
--- a/covalent_dispatcher/_dal/importers/tg.py
+++ b/covalent_dispatcher/_dal/importers/tg.py
@@ -105,9 +105,9 @@ def import_transport_graph(
     delta = (et - st).total_seconds()
     app_log.debug(f"Inserting {n_records} electron records took {delta} seconds")
 
-    n_records = 0
-    for asset_records_by_key in electron_asset_links.values():
-        n_records += len(asset_records_by_key)
+    n_records = sum(
+        len(asset_records_by_key) for asset_records_by_key in electron_asset_links.values()
+    )
 
     st = datetime.now()
     session.flush()
diff --git a/covalent_dispatcher/_db/upsert.py b/covalent_dispatcher/_db/upsert.py
index 9e242402..64bcf237 100644
--- a/covalent_dispatcher/_db/upsert.py
+++ b/covalent_dispatcher/_db/upsert.py
@@ -47,6 +47,7 @@ ELECTRON_FUNCTION_STRING_FILENAME = ELECTRON_FILENAMES["function_string"]
 ELECTRON_VALUE_FILENAME = ELECTRON_FILENAMES["value"]
 ELECTRON_STDOUT_FILENAME = ELECTRON_FILENAMES["stdout"]
 ELECTRON_STDERR_FILENAME = ELECTRON_FILENAMES["stderr"]
+ELECTRON_QELECTRON_DB_FILENAME = ELECTRON_FILENAMES["qelectron_db"]
 ELECTRON_ERROR_FILENAME = ELECTRON_FILENAMES["error"]
 ELECTRON_RESULTS_FILENAME = ELECTRON_FILENAMES["output"]
 ELECTRON_DEPS_FILENAME = ELECTRON_FILENAMES["deps"]
@@ -277,6 +278,11 @@ def _electron_data(
             except KeyError:
                 node_qelectron_data_exists = False
 
+            try:
+                node_qelectron_data = tg.get_node_value(node_id, "qelectron_db")
+            except KeyError:
+                node_qelectron_data = bytes()
+
             executor = tg.get_node_value(node_id, "metadata")["executor"]
             started_at = tg.get_node_value(node_key=node_id, value_key="start_time")
             completed_at = tg.get_node_value(node_key=node_id, value_key="end_time")
@@ -300,6 +306,7 @@ def _electron_data(
                 ),
                 ("stdout", ELECTRON_STDOUT_FILENAME, node_stdout),
                 ("stderr", ELECTRON_STDERR_FILENAME, node_stderr),
+                ("qelectron_db", ELECTRON_QELECTRON_DB_FILENAME, node_qelectron_data),
                 ("error", ELECTRON_ERROR_FILENAME, node_error),
                 ("output", ELECTRON_RESULTS_FILENAME, node_output),
             ]:
diff --git a/covalent_dispatcher/_object_store/local.py b/covalent_dispatcher/_object_store/local.py
index 36f25722..5e8d0861 100644
--- a/covalent_dispatcher/_object_store/local.py
+++ b/covalent_dispatcher/_object_store/local.py
@@ -34,7 +34,7 @@ ALGORITHM = "sha1"
 
 WORKFLOW_ASSET_FILENAME_MAP = result.ASSET_FILENAME_MAP.copy()
 WORKFLOW_ASSET_FILENAME_MAP.update(lattice.ASSET_FILENAME_MAP)
-ELECTRON_ASSET_FILENAME_MAP = electron.ASSET_FILENAME_MAP
+ELECTRON_ASSET_FILENAME_MAP = electron.ASSET_FILENAME_MAP.copy()
 
 
 # Moved from write_result_to_db.py
@@ -128,6 +128,10 @@ class LocalProvider(BaseProvider):
         elif filename.endswith(".json"):
             with open(Path(storage_path) / filename, "w") as f:
                 json.dump(data, f)
+
+        elif filename.endswith(".mdb"):
+            with open(Path(storage_path) / filename, "wb") as f:
+                f.write(data)
         else:
             raise InvalidFileExtension("The file extension is not supported.")
 
@@ -153,6 +157,10 @@ class LocalProvider(BaseProvider):
             with open(Path(storage_path) / filename, "r") as f:
                 data = json.load(f)
 
+        elif filename.endswith(".mdb"):
+            with open(Path(storage_path) / filename, "rb") as f:
+                data = f.read()
+
         return data
 
 
diff --git a/covalent_dispatcher/_service/models.py b/covalent_dispatcher/_service/models.py
index 95c71efe..de9a6db3 100644
--- a/covalent_dispatcher/_service/models.py
+++ b/covalent_dispatcher/_service/models.py
@@ -98,6 +98,7 @@ class ElectronAssetKey(str, Enum):
     error = "error"
     stdout = "stdout"
     stderr = "stderr"
+    qelectron_db = "qelectron_db"
     call_before = "call_before"
     call_after = "call_after"
 
diff --git a/covalent_ui/api/v1/data_layer/electron_dal.py b/covalent_ui/api/v1/data_layer/electron_dal.py
index 3dbd5dc6..4a470714 100644
--- a/covalent_ui/api/v1/data_layer/electron_dal.py
+++ b/covalent_ui/api/v1/data_layer/electron_dal.py
@@ -28,7 +28,6 @@ from sqlalchemy.sql import func
 from covalent._results_manager.results_manager import get_result
 from covalent._shared_files import logger
 from covalent._shared_files.config import get_config
-from covalent._shared_files.qelectron_utils import QE_DB_DIRNAME
 from covalent.quantum.qserver.database import Database
 from covalent_dispatcher._core.execution import _get_task_inputs as get_task_inputs
 from covalent_ui.api.v1.data_layer.lattice_dal import Lattices
@@ -89,8 +88,8 @@ class Electrons:
             if not validated:
                 return jobs_response
             try:
-                jobs = _qelectron_get_db(str(dispatch_id), electron_id)
-                if not jobs:
+                jobs = self._get_qelectron_db_dict(str(dispatch_id), electron_id)
+                if len(jobs) == 0:
                     jobs_response.data = []
                     jobs_response.msg = f"Job details for {dispatch_id} dispatch with {electron_id} node do not exist."
                     return jobs_response
@@ -140,7 +139,9 @@ class Electrons:
             if not validated:
                 return job_detail_response
             try:
-                jobs = _qelectron_get_db(dispatch_id=str(dispatch_id), node_id=electron_id)
+                jobs = self._get_qelectron_db_dict(
+                    dispatch_id=str(dispatch_id), node_id=electron_id
+                )
                 selected_job = jobs[job_id]
             except Exception as exc:
                 app_log.debug(f"Unable to process get jobs \n {exc}")
@@ -288,18 +289,15 @@ class Electrons:
         )
         return data
 
-    def get_total_quantum_calls(self, dispatch_id, node_id, is_qa_electron: bool):
-        if not is_qa_electron:
-            return None
-        qdb_path = _path_to_qelectron_db(dispatch_id=str(dispatch_id))
-        return len(
-            Database(qdb_path).get_circuit_ids(dispatch_id=str(dispatch_id), node_id=node_id)
-        )
+    def get_total_quantum_calls(self, dispatch_id, node_id):
+        qdb = self._get_qelectron_db_dict(dispatch_id=str(dispatch_id), node_id=node_id)
+        return None if len(qdb) == 0 else len(qdb)
 
-    def get_avg_quantum_calls(self, dispatch_id, node_id, is_qa_electron: bool):
-        if not is_qa_electron:
+    def get_avg_quantum_calls(self, dispatch_id, node_id):
+        jobs = self._get_qelectron_db_dict(dispatch_id=str(dispatch_id), node_id=node_id)
+        if len(jobs) == 0:
             return None
-        jobs = _qelectron_get_db(dispatch_id=str(dispatch_id), node_id=node_id)
+
         time = [jobs[value]["execution_time"] for value in jobs]
         return sum(time) / len(time)
 
@@ -323,21 +321,14 @@ class Electrons:
         )
         return validate_data(inputs)
 
+    def _get_qelectron_db_dict(self, dispatch_id: str, node_id: int) -> dict:
+        """Return the QElectron DB for a given node."""
 
-def _path_to_qelectron_db(dispatch_id: str) -> Path:
-    """Construct path to the QElectron database in Covalent's results directory."""
-
-    # This is NOT the QServer's data but rather the qdb stored on Covalent's server.
-    qdb_path = RESULTS_DIR / dispatch_id / QE_DB_DIRNAME
-    qdb_path = qdb_path.resolve().absolute()
-
-    if not qdb_path.exists():
-        app_log.error(f"Expected QElectron database at {qdb_path}.")
-
-    return qdb_path
+        electron = self.get_electrons_id(dispatch_id, node_id)
 
+        database = Database(electron.storage_path)
+        qelectron_db_dict = database.get_db_dict(
+            dispatch_id=dispatch_id, node_id=node_id, direct_path=True
+        )
 
-def _qelectron_get_db(dispatch_id: str, node_id: int) -> dict:
-    """Return the QElectron jobs dictionary for a given node."""
-    qdb_path = _path_to_qelectron_db(dispatch_id)
-    return Database(qdb_path).get_db(dispatch_id=dispatch_id, node_id=node_id)
+        return qelectron_db_dict
diff --git a/covalent_ui/api/v1/database/schema/electron.py b/covalent_ui/api/v1/database/schema/electron.py
index 7baf68f8..63524091 100644
--- a/covalent_ui/api/v1/database/schema/electron.py
+++ b/covalent_ui/api/v1/database/schema/electron.py
@@ -111,7 +111,7 @@ class Electron(Base):
     # Name of the file containing the functions that are called before electron execution
     call_before_filename = Column(Text)
 
-    # Name of the file containing the functions that are called before electron execution
+    # Name of the file containing the functions that are called after electron execution
     call_after_filename = Column(Text)
 
     # Name of the file containing standard error generated by the task
diff --git a/covalent_ui/api/v1/routes/end_points/electron_routes.py b/covalent_ui/api/v1/routes/end_points/electron_routes.py
index 4742c5e6..616f1c80 100644
--- a/covalent_ui/api/v1/routes/end_points/electron_routes.py
+++ b/covalent_ui/api/v1/routes/end_points/electron_routes.py
@@ -68,16 +68,18 @@ def get_electron_details(dispatch_id: uuid.UUID, electron_id: int):
                     }
                 ],
             )
+
         qelectron = {
             "total_quantum_calls": electron.get_total_quantum_calls(
-                dispatch_id, result["transport_graph_node_id"], result["qelectron_data_exists"]
+                dispatch_id,
+                result["transport_graph_node_id"],
             ),
             "avg_quantum_calls": electron.get_avg_quantum_calls(
                 dispatch_id=dispatch_id,
-                is_qa_electron=result["qelectron_data_exists"],
                 node_id=result["transport_graph_node_id"],
             ),
         }
+
         return ElectronResponse(
             id=result["id"],
             node_id=result["transport_graph_node_id"],
@@ -90,8 +92,8 @@ def get_electron_details(dispatch_id: uuid.UUID, electron_id: int):
             ended_at=result["completed_at"],
             runtime=result["runtime"],
             description="",
-            qelectron_data_exists=bool(result["qelectron_data_exists"]),
-            qelectron=qelectron if bool(result["qelectron_data_exists"]) else None,
+            qelectron_data_exists=qelectron["total_quantum_calls"] is not None,
+            qelectron=qelectron if qelectron["total_quantum_calls"] is not None else None,
         )
 
 
@@ -235,6 +237,11 @@ def get_electron_file(dispatch_id: uuid.UUID, electron_id: int, name: ElectronFi
             stderr_response = handler.read_from_text(result["stderr_filename"])
             response = stderr_response + error_response
             return ElectronFileResponse(data=response)
+        elif name == "qelectron_db":
+            # Since in case of bytes 2 same bytes objects are returned by the handler
+            # so we are taking only the first one
+            response = handler.read_from_serialized(result["qelectron_db_filename"])[0]
+            return ElectronFileResponse(data=response)
         else:
             return ElectronFileResponse(data=None)
 
@@ -248,14 +255,14 @@ def get_electron_jobs(
     count: Optional[int] = None,
     offset: Optional[int] = Query(0),
 ) -> List[Job]:
-    """Get Electron Jobs List
+    """Get Qelectron Jobs List
 
     Args:
-        dispatch_id: To fetch electron data with dispatch id
-        electron_id: To fetch electron data with the provided electron id.
+        dispatch_id: To fetch qelectron data with dispatch id
+        electron_id: To fetch qelectron data with the provided electron id.
 
     Returns:
-        Returns the list of electron jobs
+        Returns the list of qelectron jobs
     """
     with Session(db.engine) as session:
         electron = Electrons(session)


Test patch:
diff --git a/tests/covalent_dispatcher_tests/_object_store/local_test.py b/tests/covalent_dispatcher_tests/_object_store/local_test.py
index 9f869c6d..0823d360 100644
--- a/tests/covalent_dispatcher_tests/_object_store/local_test.py
+++ b/tests/covalent_dispatcher_tests/_object_store/local_test.py
@@ -62,3 +62,7 @@ def test_store_and_load_file():
         data = None
         local_store.store_file(storage_path=temp_dir, filename="pickle.txt", data=data)
         assert local_store.load_file(storage_path=temp_dir, filename="pickle.txt") == ""
+
+        data = b"test"
+        local_store.store_file(storage_path=temp_dir, filename="pickle.mdb", data=data)
+        assert local_store.load_file(storage_path=temp_dir, filename="pickle.mdb") == data
diff --git a/tests/covalent_tests/executor/executor_plugins/dask_test.py b/tests/covalent_tests/executor/executor_plugins/dask_test.py
index c28bc52f..c9b5c088 100644
--- a/tests/covalent_tests/executor/executor_plugins/dask_test.py
+++ b/tests/covalent_tests/executor/executor_plugins/dask_test.py
@@ -460,13 +460,16 @@ def test_run_task_from_uris_alt():
     result_file = tempfile.NamedTemporaryFile()
     stdout_file = tempfile.NamedTemporaryFile()
     stderr_file = tempfile.NamedTemporaryFile()
+    qelectron_db_file = tempfile.NamedTemporaryFile()
 
     results_dir = tempfile.TemporaryDirectory()
 
     run_task_from_uris_alt(
         task_specs=[task_spec.dict()],
         resources=resources.dict(),
-        output_uris=[(result_file.name, stdout_file.name, stderr_file.name)],
+        output_uris=[
+            (result_file.name, stdout_file.name, stderr_file.name, qelectron_db_file.name)
+        ],
         results_dir=results_dir.name,
         task_group_metadata=task_group_metadata,
         server_url="http://localhost:48008",
@@ -567,13 +570,16 @@ def test_run_task_from_uris_alt_exception():
     result_file = tempfile.NamedTemporaryFile()
     stdout_file = tempfile.NamedTemporaryFile()
     stderr_file = tempfile.NamedTemporaryFile()
+    qelectron_db_file = tempfile.NamedTemporaryFile()
 
     results_dir = tempfile.TemporaryDirectory()
 
     run_task_from_uris_alt(
-        task_specs=[task_spec.dict()],
-        resources=resources.dict(),
-        output_uris=[(result_file.name, stdout_file.name, stderr_file.name)],
+        task_specs=[task_spec.model_dump()],
+        resources=resources.model_dump(),
+        output_uris=[
+            (result_file.name, stdout_file.name, stderr_file.name, qelectron_db_file.name)
+        ],
         results_dir=results_dir.name,
         task_group_metadata=task_group_metadata,
         server_url="http://localhost:48008",
diff --git a/tests/covalent_tests/executor/executor_plugins/local_test.py b/tests/covalent_tests/executor/executor_plugins/local_test.py
index c5c34ae5..331e4457 100644
--- a/tests/covalent_tests/executor/executor_plugins/local_test.py
+++ b/tests/covalent_tests/executor/executor_plugins/local_test.py
@@ -343,13 +343,16 @@ def test_run_task_from_uris(mocker):
     result_file = tempfile.NamedTemporaryFile()
     stdout_file = tempfile.NamedTemporaryFile()
     stderr_file = tempfile.NamedTemporaryFile()
+    qelectron_db_file = tempfile.NamedTemporaryFile()
 
     results_dir = tempfile.TemporaryDirectory()
 
     run_task_from_uris(
         task_specs=[task_spec.dict()],
         resources={},
-        output_uris=[(result_file.name, stdout_file.name, stderr_file.name)],
+        output_uris=[
+            (result_file.name, stdout_file.name, stderr_file.name, qelectron_db_file.name)
+        ],
         results_dir=results_dir.name,
         task_group_metadata=task_group_metadata,
         server_url=server_url,
@@ -472,13 +475,16 @@ def test_run_task_from_uris_exception(mocker):
     result_file = tempfile.NamedTemporaryFile()
     stdout_file = tempfile.NamedTemporaryFile()
     stderr_file = tempfile.NamedTemporaryFile()
+    qelectron_db_file = tempfile.NamedTemporaryFile()
 
     results_dir = tempfile.TemporaryDirectory()
 
     run_task_from_uris(
         task_specs=[task_spec.dict()],
         resources={},
-        output_uris=[(result_file.name, stdout_file.name, stderr_file.name)],
+        output_uris=[
+            (result_file.name, stdout_file.name, stderr_file.name, qelectron_db_file.name)
+        ],
         results_dir=results_dir.name,
         task_group_metadata=task_group_metadata,
         server_url=server_url,
@@ -543,7 +549,7 @@ test_cases = [
             deps={"deps": "mock_deps_uri"},
         ),
         "task_group_metadata": {"dispatch_id": "1", "node_ids": ["1"], "task_group_id": "1"},
-        "expected_output_uris": [("mock_path", "mock_path", "mock_path")],
+        "expected_output_uris": [("mock_path", "mock_path", "mock_path", "mock_path")],
         "expected_server_url": "mock_server_url",
         "expected_future_cancelled": False,
     },
@@ -565,7 +571,7 @@ test_cases = [
             deps={"deps": "mock_deps_uri"},
         ),
         "task_group_metadata": {"dispatch_id": "1", "node_ids": ["1"], "task_group_id": "1"},
-        "expected_output_uris": [("mock_path", "mock_path", "mock_path")],
+        "expected_output_uris": [("mock_path", "mock_path", "mock_path", "mock_path")],
         "expected_server_url": "mock_server_url",
         "expected_future_cancelled": True,
     },
diff --git a/tests/covalent_tests/quantum/qserver/__init__.py b/tests/covalent_tests/quantum/qserver/__init__.py
new file mode 100644
index 00000000..21d7eaa5
--- /dev/null
+++ b/tests/covalent_tests/quantum/qserver/__init__.py
@@ -0,0 +1,15 @@
+# Copyright 2023 Agnostiq Inc.
+#
+# This file is part of Covalent.
+#
+# Licensed under the Apache License 2.0 (the "License"). A copy of the
+# License may be obtained with this software package or at
+#
+#     https://www.apache.org/licenses/LICENSE-2.0
+#
+# Use of this file is prohibited except in compliance with the License.
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
diff --git a/tests/covalent_tests/quantum/qserver/database_test.py b/tests/covalent_tests/quantum/qserver/database_test.py
new file mode 100644
index 00000000..4bc7976b
--- /dev/null
+++ b/tests/covalent_tests/quantum/qserver/database_test.py
@@ -0,0 +1,211 @@
+# Copyright 2023 Agnostiq Inc.
+#
+# This file is part of Covalent.
+#
+# Licensed under the Apache License 2.0 (the "License"). A copy of the
+# License may be obtained with this software package or at
+#
+#     https://www.apache.org/licenses/LICENSE-2.0
+#
+# Use of this file is prohibited except in compliance with the License.
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+""" Tests for qelectron's Database class """
+
+
+import tempfile
+
+import pytest
+
+from covalent.quantum.qserver.database import Database
+
+
+@pytest.fixture
+def temp_dir():
+    with tempfile.TemporaryDirectory() as temp_dir:
+        yield temp_dir
+
+
+@pytest.fixture
+def db(temp_dir):
+    return Database(db_dir=temp_dir)
+
+
+@pytest.mark.parametrize("direct_path", [True, False])
+@pytest.mark.parametrize("mkdir", [True, False])
+def test_get_db_path(mocker, db, direct_path, mkdir):
+    """Test the function used to get the path to the database."""
+
+    db.db_dir = mocker.Mock()
+
+    db_path = db.get_db_path(direct_path=direct_path, mkdir=mkdir)
+
+    if direct_path:
+        assert db_path == db.db_dir.resolve.return_value.absolute.return_value
+
+    else:
+        db.db_dir.joinpath.assert_called_once_with("default-dispatch", "node-default-node")
+        assert (
+            db_path == db.db_dir.joinpath.return_value.resolve.return_value.absolute.return_value
+        )
+
+        if mkdir:
+            db.db_dir.joinpath.return_value.mkdir.assert_called_once_with(
+                parents=True, exist_ok=True
+            )
+        else:
+            db.db_dir.joinpath.return_value.mkdir.assert_not_called()
+
+
+@pytest.mark.parametrize("direct_path", [True, False])
+@pytest.mark.parametrize("mkdir", [True, False])
+@pytest.mark.parametrize("db_path_exists", [True, False])
+def test_open(mocker, db, direct_path, mkdir, db_path_exists):
+    """Test the function used to open a database."""
+
+    mock_dispatch_id = "mock-dispatch-id"
+    mock_node_id = "mock-node-id"
+
+    mock_get_db_path = mocker.patch.object(db, "get_db_path")
+    mock_get_db_path.return_value = mocker.Mock()
+    mock_get_db_path.return_value.exists.return_value = db_path_exists
+
+    mock_json_lmdb = mocker.patch("covalent.quantum.qserver.database.JsonLmdb")
+
+    if not db_path_exists:
+        with pytest.raises(FileNotFoundError):
+            db._open(
+                dispatch_id=mock_dispatch_id,
+                node_id=mock_node_id,
+                direct_path=direct_path,
+                mkdir=mkdir,
+            )
+            mock_get_db_path.return_value.exists.assert_called_once_with()
+            assert mock_json_lmdb.open_with_strategy.call_count == 0
+        return
+
+    db._open(
+        dispatch_id=mock_dispatch_id, node_id=mock_node_id, direct_path=direct_path, mkdir=mkdir
+    )
+
+    mock_get_db_path.assert_called_once_with(
+        mock_dispatch_id, mock_node_id, direct_path=direct_path, mkdir=mkdir
+    )
+
+    mock_json_lmdb.open_with_strategy.assert_called_once_with(
+        file=str(mock_get_db_path.return_value),
+        flag="c",
+        strategy_name=Database.serialization_strategy,
+    )
+
+
+@pytest.mark.parametrize("direct_path", [True, False])
+def test_set(mocker, db, direct_path):
+    """Test the function used to set data in the database."""
+
+    mock_open = mocker.patch.object(db, "_open")
+    mock_open.return_value.__enter__.return_value = mocker.Mock()
+
+    mock_keys = ["key1", "key2"]
+    mock_values = ["value1", "value2"]
+    mock_dispatch_id = "mock-dispatch-id"
+    mock_node_id = "mock-node-id"
+
+    db.set(
+        keys=mock_keys,
+        values=mock_values,
+        dispatch_id=mock_dispatch_id,
+        node_id=mock_node_id,
+        direct_path=direct_path,
+    )
+
+    mock_open.assert_called_once_with(
+        mock_dispatch_id, mock_node_id, direct_path=direct_path, mkdir=True
+    )
+
+    mock_open.return_value.__enter__.return_value.update.assert_called_once_with(
+        dict(zip(mock_keys, mock_values))
+    )
+
+
+@pytest.mark.parametrize("direct_path", [True, False])
+def test_get_circuit_ids(mocker, db, direct_path):
+    """Test the function used to get circuit IDs from the database."""
+
+    mock_open = mocker.patch.object(db, "_open")
+    mock_open.return_value.__enter__.return_value = mocker.Mock()
+    mock_open.return_value.__enter__.return_value.keys.return_value = ["key1", "key2"]
+
+    mock_dispatch_id = "mock-dispatch-id"
+    mock_node_id = "mock-node-id"
+
+    circuit_ids = db.get_circuit_ids(
+        dispatch_id=mock_dispatch_id, node_id=mock_node_id, direct_path=direct_path
+    )
+
+    mock_open.assert_called_once_with(mock_dispatch_id, mock_node_id, direct_path=direct_path)
+
+    mock_open.return_value.__enter__.return_value.keys.assert_called_once_with()
+
+    assert circuit_ids == list(mock_open.return_value.__enter__.return_value.keys.return_value)
+
+
+@pytest.mark.parametrize("direct_path", [True, False])
+def test_get_circuit_info(mocker, db, direct_path):
+    """Test the function used to get circuit info from the database."""
+
+    mock_circuit_info_class = mocker.patch("covalent.quantum.qserver.database.CircuitInfo")
+
+    mock_open = mocker.patch.object(db, "_open")
+    mock_open.return_value.__enter__.return_value = mocker.Mock()
+    mock_open.return_value.__enter__.return_value.get.return_value = {"key1": "value1"}
+
+    mock_dispatch_id = "mock-dispatch-id"
+    mock_node_id = "mock-node-id"
+    mock_circuit_id = "mock-circuit-id"
+
+    db.get_circuit_info(
+        circuit_id=mock_circuit_id,
+        dispatch_id=mock_dispatch_id,
+        node_id=mock_node_id,
+        direct_path=direct_path,
+    )
+
+    mock_open.assert_called_once_with(mock_dispatch_id, mock_node_id, direct_path=direct_path)
+
+    mock_open.return_value.__enter__.return_value.get.assert_called_once_with(
+        mock_circuit_id, None
+    )
+
+    mock_circuit_info_class.assert_called_once_with(
+        **mock_open.return_value.__enter__.return_value.get.return_value
+    )
+
+
+@pytest.mark.parametrize("direct_path", [True, False])
+def test_get_db_dict(mocker, db, direct_path):
+    """Test the function used to get the database as a dictionary."""
+
+    mock_open = mocker.patch.object(db, "_open")
+    mock_open.return_value.__enter__.return_value = mocker.Mock()
+    mock_open.return_value.__enter__.return_value.items.return_value = [
+        ("key1", "value1"),
+        ("key2", "value2"),
+    ]
+
+    mock_dispatch_id = "mock-dispatch-id"
+    mock_node_id = "mock-node-id"
+
+    db_dict = db.get_db_dict(
+        dispatch_id=mock_dispatch_id, node_id=mock_node_id, direct_path=direct_path
+    )
+
+    mock_open.assert_called_once_with(mock_dispatch_id, mock_node_id, direct_path=direct_path)
+
+    mock_open.return_value.__enter__.return_value.items.assert_called_once_with()
+
+    assert db_dict == dict(mock_open.return_value.__enter__.return_value.items.return_value)
diff --git a/tests/covalent_tests/shared_files/qelectron_utils_test.py b/tests/covalent_tests/shared_files/qelectron_utils_test.py
new file mode 100644
index 00000000..6c213e21
--- /dev/null
+++ b/tests/covalent_tests/shared_files/qelectron_utils_test.py
@@ -0,0 +1,49 @@
+# Copyright 2023 Agnostiq Inc.
+#
+# This file is part of Covalent.
+#
+# Licensed under the Apache License 2.0 (the "License"). A copy of the
+# License may be obtained with this software package or at
+#
+#     https://www.apache.org/licenses/LICENSE-2.0
+#
+# Use of this file is prohibited except in compliance with the License.
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import pytest
+
+from covalent._shared_files.qelectron_utils import get_qelectron_db_path
+
+
+@pytest.mark.parametrize(
+    "db_exists",
+    [
+        True,
+        False,
+    ],
+)
+def test_get_qelectron_db_path(mocker, db_exists):
+    """Test the get_qelectron_db_path function."""
+
+    mock_database = mocker.patch("covalent._shared_files.qelectron_utils.Database")
+    mock_database.return_value.get_db_path.return_value.exists.return_value = db_exists
+
+    dispatch_id = "mock_dispatch_id"
+    task_id = 0
+
+    db_path = get_qelectron_db_path(dispatch_id, 0)
+
+    mock_database.return_value.get_db_path.assert_called_once_with(
+        dispatch_id=dispatch_id, node_id=task_id
+    )
+    mock_database.return_value.get_db_path.return_value.exists.assert_called_once()
+
+    if db_exists:
+        assert db_path is not None
+    else:
+        assert db_path is None