Kaynağa Gözat

refactor write to excel tests

Toan Quach 1 yıl önce
ebeveyn
işleme
9b079b2b74

+ 17 - 14
taipy/core/data/excel.py

@@ -10,7 +10,6 @@
 # specific language governing permissions and limitations under the License.
 
 import os
-from collections import defaultdict
 from datetime import datetime, timedelta
 from os.path import isfile
 from typing import Any, Dict, List, Optional, Set, Tuple, Union
@@ -191,21 +190,17 @@ class ExcelDataNode(DataNode, _AbstractFileDataNode, _AbstractTabularDataNode):
             return self._read_as_numpy()
         return self._read_as()
 
-    def __sheet_name_to_list(self, properties):
-        if properties[self.__SHEET_NAME_PROPERTY]:
-            sheet_names = properties[self.__SHEET_NAME_PROPERTY]
-        else:
-            excel_file = load_workbook(properties[self.__PATH_KEY])
-            sheet_names = excel_file.sheetnames
-            excel_file.close()
-        return sheet_names if isinstance(sheet_names, (List, Set, Tuple)) else [sheet_names]
-
     def _read_as(self):
         excel_file = load_workbook(self._path)
         exposed_type = self.properties[self._EXPOSED_TYPE_PROPERTY]
-        work_books = defaultdict()
+        work_books = dict()
         sheet_names = excel_file.sheetnames
-        provided_sheet_names = self.__sheet_name_to_list(self.properties)
+
+        user_provided_sheet_names = self.properties.get(self.__SHEET_NAME_PROPERTY) or []
+        if not isinstance(user_provided_sheet_names, (List, Set, Tuple)):
+            user_provided_sheet_names = [user_provided_sheet_names]
+
+        provided_sheet_names = user_provided_sheet_names or sheet_names
 
         for sheet_name in provided_sheet_names:
             if sheet_name not in sheet_names:
@@ -251,6 +246,11 @@ class ExcelDataNode(DataNode, _AbstractFileDataNode, _AbstractTabularDataNode):
 
         if len(provided_sheet_names) == 1:
             return work_books[provided_sheet_names[0]]
+
+        # TODO: Decide to change or not?
+        # if user_provided_sheet_names == 1:
+        #     return work_books[user_provided_sheet_names]
+
         return work_books
 
     def _read_as_numpy(self):
@@ -336,14 +336,16 @@ class ExcelDataNode(DataNode, _AbstractFileDataNode, _AbstractTabularDataNode):
                 if columns:
                     data[key].columns = columns
 
-                df.to_excel(writer, key, index=False)
+                df.to_excel(writer, key, index=False, header=self.properties[self.__HAS_HEADER_PROPERTY] or None)
 
     def _write(self, data: Any):
         if isinstance(data, Dict):
             return self.__write_excel_with_multiple_sheets(data)
         else:
             data = self._convert_data_to_dataframe(self.properties[self._EXPOSED_TYPE_PROPERTY], data)
-            self.__write_excel_with_single_sheet(data.to_excel, self._path, index=False)
+            self.__write_excel_with_single_sheet(
+                data.to_excel, self._path, index=False, header=self.properties[self.__HAS_HEADER_PROPERTY] or None
+            )
 
     def write_with_column_names(self, data: Any, columns: List[str] = None, job_id: Optional[JobId] = None):
         """Write a set of columns.
@@ -353,6 +355,7 @@ class ExcelDataNode(DataNode, _AbstractFileDataNode, _AbstractTabularDataNode):
             columns (List[str]): The list of column names to write.
             job_id (JobId^): An optional identifier of the writer.
         """
+        # TODO: add tests for this and on csv
         if isinstance(data, Dict) and all(isinstance(x, (pd.DataFrame, np.ndarray)) for x in data.values()):
             self.__write_excel_with_multiple_sheets(data, columns=columns)
         else:

+ 0 - 341
tests/core/data/test_write_excel_data_node.py

@@ -1,341 +0,0 @@
-# Copyright 2021-2024 Avaiga Private Limited
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-#        http://www.apache.org/licenses/LICENSE-2.0
-#
-# 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 os
-import pathlib
-
-import numpy as np
-import pandas as pd
-import pytest
-from pandas.testing import assert_frame_equal
-
-from taipy.config.common.scope import Scope
-from taipy.core.data.excel import ExcelDataNode
-from taipy.core.exceptions.exceptions import SheetNameLengthMismatch
-
-
-@pytest.fixture(scope="function", autouse=True)
-def cleanup():
-    yield
-    path = os.path.join(pathlib.Path(__file__).parent.resolve(), "data_sample/temp.xlsx")
-    if os.path.exists(path):
-        os.remove(path)
-
-
-class MyCustomObject:
-    def __init__(self, id, integer, text):
-        self.id = id
-        self.integer = integer
-        self.text = text
-
-
-class MyCustomObject1:
-    def __init__(self, id, integer, text):
-        self.id = id
-        self.integer = integer
-        self.text = text
-
-
-class MyCustomObject2:
-    def __init__(self, id, integer, text):
-        self.id = id
-        self.integer = integer
-        self.text = text
-
-
-@pytest.mark.parametrize(
-    "content,columns",
-    [
-        ([{"a": 11, "b": 22, "c": 33}, {"a": 44, "b": 55, "c": 66}], None),
-        ([[11, 22, 33], [44, 55, 66]], None),
-        ([[11, 22, 33], [44, 55, 66]], ["e", "f", "g"]),
-    ],
-)
-def test_write(excel_file, default_data_frame, content, columns):
-    excel_dn = ExcelDataNode("foo", Scope.SCENARIO, properties={"path": excel_file, "sheet_name": "Sheet1"})
-    assert np.array_equal(excel_dn.read().values, default_data_frame.values)
-    if not columns:
-        excel_dn.write(content)
-        df = pd.DataFrame(content)
-    else:
-        excel_dn.write_with_column_names(content, columns)
-        df = pd.DataFrame(content, columns=columns)
-
-    assert np.array_equal(excel_dn.read().values, df.values)
-
-    excel_dn.write(None)
-    assert len(excel_dn.read()) == 0
-
-
-@pytest.mark.parametrize(
-    "content,sheet_name",
-    [
-        ([{"a": 11, "b": 22, "c": 33}, {"a": 44, "b": 55, "c": 66}], "sheet_name"),
-        ([[11, 22, 33], [44, 55, 66]], ["sheet_name"]),
-    ],
-)
-def test_write_with_sheet_name(excel_file_with_sheet_name, default_data_frame, content, sheet_name):
-    excel_dn = ExcelDataNode(
-        "foo", Scope.SCENARIO, properties={"path": excel_file_with_sheet_name, "sheet_name": sheet_name}
-    )
-    df = pd.DataFrame(content)
-
-    if isinstance(sheet_name, str):
-        assert np.array_equal(excel_dn.read().values, default_data_frame.values)
-    else:
-        assert np.array_equal(excel_dn.read()["sheet_name"].values, default_data_frame.values)
-
-    excel_dn.write(content)
-    if isinstance(sheet_name, str):
-        assert np.array_equal(excel_dn.read().values, df.values)
-    else:
-        assert np.array_equal(excel_dn.read()["sheet_name"].values, df.values)
-
-    sheet_names = pd.ExcelFile(excel_file_with_sheet_name).sheet_names
-    expected_sheet_name = sheet_name[0] if isinstance(sheet_name, list) else sheet_name
-
-    assert sheet_names[0] == expected_sheet_name
-
-    excel_dn.write(None)
-    if isinstance(sheet_name, str):
-        assert len(excel_dn.read()) == 0
-    else:
-        assert len(excel_dn.read()) == 1
-
-
-@pytest.mark.parametrize(
-    "content,sheet_name",
-    [
-        ([[11, 22, 33], [44, 55, 66]], ["sheet_name_1", "sheet_name_2"]),
-    ],
-)
-def test_raise_write_with_sheet_name_length_mismatch(
-    excel_file_with_sheet_name, default_data_frame, content, sheet_name
-):
-    excel_dn = ExcelDataNode(
-        "foo", Scope.SCENARIO, properties={"path": excel_file_with_sheet_name, "sheet_name": sheet_name}
-    )
-    with pytest.raises(SheetNameLengthMismatch):
-        excel_dn.write(content)
-
-
-@pytest.mark.parametrize(
-    "content",
-    [
-        ([{"a": 11, "b": 22, "c": 33}, {"a": 44, "b": 55, "c": 66}]),
-    ],
-)
-def test_write_without_sheet_name(excel_file_with_sheet_name, default_data_frame, content):
-    excel_dn = ExcelDataNode("foo", Scope.SCENARIO, properties={"path": excel_file_with_sheet_name})
-    default_data_frame = {"sheet_name": default_data_frame}
-    df = {"Sheet1": pd.DataFrame(content)}
-
-    assert np.array_equal(excel_dn.read()["sheet_name"].values, default_data_frame["sheet_name"].values)
-
-    excel_dn.write(content)
-    assert np.array_equal(excel_dn.read()["Sheet1"].values, df["Sheet1"].values)
-
-    sheet_names = pd.ExcelFile(excel_file_with_sheet_name).sheet_names
-    expected_sheet_name = "Sheet1"
-
-    assert sheet_names[0] == expected_sheet_name
-
-    excel_dn.write(None)
-    assert len(excel_dn.read()) == 1
-
-
-@pytest.mark.parametrize(
-    "content,columns,sheet_name",
-    [
-        ([[11, 22, 33], [44, 55, 66]], ["e", "f", "g"], "sheet_name"),
-        ([[11, 22, 33], [44, 55, 66]], ["e", "f", "g"], ["sheet_name"]),
-    ],
-)
-def test_write_with_column_and_sheet_name(excel_file_with_sheet_name, default_data_frame, content, columns, sheet_name):
-    excel_dn = ExcelDataNode(
-        "foo", Scope.SCENARIO, properties={"path": excel_file_with_sheet_name, "sheet_name": sheet_name}
-    )
-    df = pd.DataFrame(content)
-
-    if isinstance(sheet_name, str):
-        assert np.array_equal(excel_dn.read().values, default_data_frame.values)
-    else:
-        assert np.array_equal(excel_dn.read()["sheet_name"].values, default_data_frame.values)
-
-    excel_dn.write_with_column_names(content, columns)
-
-    if isinstance(sheet_name, str):
-        assert np.array_equal(excel_dn.read().values, df.values)
-    else:
-        assert np.array_equal(excel_dn.read()["sheet_name"].values, df.values)
-
-    sheet_names = pd.ExcelFile(excel_file_with_sheet_name).sheet_names
-    expected_sheet_name = sheet_name[0] if isinstance(sheet_name, list) else sheet_name
-
-    assert sheet_names[0] == expected_sheet_name
-
-    excel_dn.write(None)
-    if isinstance(sheet_name, str):
-        assert len(excel_dn.read()) == 0
-    else:
-        assert len(excel_dn.read()) == 1
-
-
-@pytest.mark.parametrize(
-    "content,columns",
-    [
-        ([{"a": 11, "b": 22, "c": 33}, {"a": 44, "b": 55, "c": 66}], None),
-        ([[11, 22, 33], [44, 55, 66]], None),
-        ([[11, 22, 33], [44, 55, 66]], ["e", "f", "g"]),
-    ],
-)
-def test_write_multi_sheet(excel_file_with_multi_sheet, default_multi_sheet_data_frame, content, columns):
-    sheet_names = ["Sheet1", "Sheet2"]
-
-    excel_dn = ExcelDataNode(
-        "foo",
-        Scope.SCENARIO,
-        properties={"path": excel_file_with_multi_sheet, "sheet_name": sheet_names},
-    )
-
-    for sheet_name in sheet_names:
-        assert np.array_equal(excel_dn.read()[sheet_name].values, default_multi_sheet_data_frame[sheet_name].values)
-
-    multi_sheet_content = {sheet_name: pd.DataFrame(content) for sheet_name in sheet_names}
-
-    excel_dn.write(multi_sheet_content)
-
-    for sheet_name in sheet_names:
-        assert np.array_equal(excel_dn.read()[sheet_name].values, multi_sheet_content[sheet_name].values)
-
-
-def test_write_multi_sheet_numpy(excel_file_with_multi_sheet):
-    sheet_names = ["Sheet1", "Sheet2"]
-
-    excel_dn = ExcelDataNode(
-        "foo",
-        Scope.SCENARIO,
-        properties={"path": excel_file_with_multi_sheet, "sheet_name": sheet_names, "exposed_type": "numpy"},
-    )
-
-    sheets_data = [[11, 22, 33], [44, 55, 66]]
-    data = {sheet_name: pd.DataFrame(sheet_data).to_numpy() for sheet_name, sheet_data in zip(sheet_names, sheets_data)}
-    excel_dn.write(data)
-    read_data = excel_dn.read()
-    assert all(np.array_equal(data[sheet_name], read_data[sheet_name]) for sheet_name in sheet_names)
-
-
-@pytest.mark.parametrize(
-    "content",
-    [
-        ([{"a": 11, "b": 22, "c": 33}, {"a": 44, "b": 55, "c": 66}]),
-        (pd.DataFrame([{"a": 11, "b": 22, "c": 33}, {"a": 44, "b": 55, "c": 66}])),
-        ([[11, 22, 33], [44, 55, 66]]),
-    ],
-)
-def test_append_pandas_with_sheetname(excel_file, default_data_frame, content):
-    dn = ExcelDataNode("foo", Scope.SCENARIO, properties={"path": excel_file, "sheet_name": "Sheet1"})
-    assert_frame_equal(dn.read(), default_data_frame)
-
-    dn.append(content)
-    assert_frame_equal(
-        dn.read(),
-        pd.concat([default_data_frame, pd.DataFrame(content, columns=["a", "b", "c"])]).reset_index(drop=True),
-    )
-
-
-@pytest.mark.parametrize(
-    "content",
-    [
-        ([{"a": 11, "b": 22, "c": 33}, {"a": 44, "b": 55, "c": 66}]),
-        (pd.DataFrame([{"a": 11, "b": 22, "c": 33}, {"a": 44, "b": 55, "c": 66}])),
-        ([[11, 22, 33], [44, 55, 66]]),
-    ],
-)
-def test_append_pandas_without_sheetname(excel_file, default_data_frame, content):
-    dn = ExcelDataNode("foo", Scope.SCENARIO, properties={"path": excel_file})
-    assert_frame_equal(dn.read()["Sheet1"], default_data_frame)
-
-    dn.append(content)
-    assert_frame_equal(
-        dn.read()["Sheet1"],
-        pd.concat([default_data_frame, pd.DataFrame(content, columns=["a", "b", "c"])]).reset_index(drop=True),
-    )
-
-
-@pytest.mark.parametrize(
-    "content",
-    [
-        (
-            {
-                "Sheet1": pd.DataFrame([{"a": 11, "b": 22, "c": 33}]),
-                "Sheet2": pd.DataFrame([{"a": 44, "b": 55, "c": 66}]),
-            }
-        ),
-        (
-            {
-                "Sheet1": pd.DataFrame({"a": [11, 44], "b": [22, 55], "c": [33, 66]}),
-                "Sheet2": pd.DataFrame([{"a": 77, "b": 88, "c": 99}]),
-            }
-        ),
-        ({"Sheet1": np.array([[11, 22, 33], [44, 55, 66]]), "Sheet2": np.array([[77, 88, 99]])}),
-    ],
-)
-def test_append_pandas_multisheet(excel_file_with_multi_sheet, default_multi_sheet_data_frame, content):
-    dn = ExcelDataNode(
-        "foo", Scope.SCENARIO, properties={"path": excel_file_with_multi_sheet, "sheet_name": ["Sheet1", "Sheet2"]}
-    )
-    assert_frame_equal(dn.read()["Sheet1"], default_multi_sheet_data_frame["Sheet1"])
-    assert_frame_equal(dn.read()["Sheet2"], default_multi_sheet_data_frame["Sheet2"])
-
-    dn.append(content)
-
-    assert_frame_equal(
-        dn.read()["Sheet1"],
-        pd.concat(
-            [default_multi_sheet_data_frame["Sheet1"], pd.DataFrame(content["Sheet1"], columns=["a", "b", "c"])]
-        ).reset_index(drop=True),
-    )
-    assert_frame_equal(
-        dn.read()["Sheet2"],
-        pd.concat(
-            [default_multi_sheet_data_frame["Sheet2"], pd.DataFrame(content["Sheet2"], columns=["a", "b", "c"])]
-        ).reset_index(drop=True),
-    )
-
-
-@pytest.mark.parametrize(
-    "content",
-    [
-        ({"Sheet1": pd.DataFrame([{"a": 11, "b": 22, "c": 33}])}),
-        (pd.DataFrame({"a": [11, 44], "b": [22, 55], "c": [33, 66]})),
-        ([[11, 22, 33], [44, 55, 66]]),
-    ],
-)
-def test_append_only_first_sheet_of_a_multisheet_file(
-    excel_file_with_multi_sheet, default_multi_sheet_data_frame, content
-):
-    dn = ExcelDataNode(
-        "foo", Scope.SCENARIO, properties={"path": excel_file_with_multi_sheet, "sheet_name": ["Sheet1", "Sheet2"]}
-    )
-    assert_frame_equal(dn.read()["Sheet1"], default_multi_sheet_data_frame["Sheet1"])
-    assert_frame_equal(dn.read()["Sheet2"], default_multi_sheet_data_frame["Sheet2"])
-
-    dn.append(content)
-
-    appended_content = content["Sheet1"] if isinstance(content, dict) else content
-    assert_frame_equal(
-        dn.read()["Sheet1"],
-        pd.concat(
-            [default_multi_sheet_data_frame["Sheet1"], pd.DataFrame(appended_content, columns=["a", "b", "c"])]
-        ).reset_index(drop=True),
-    )
-    assert_frame_equal(dn.read()["Sheet2"], default_multi_sheet_data_frame["Sheet2"])

+ 387 - 0
tests/core/data/test_write_multiple_sheet_excel_data_node.py

@@ -0,0 +1,387 @@
+# Copyright 2021-2024 Avaiga Private Limited
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+#        http://www.apache.org/licenses/LICENSE-2.0
+#
+# 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 dataclasses
+import os
+import pathlib
+
+import numpy as np
+import pandas as pd
+import pytest
+from pandas.testing import assert_frame_equal
+
+from taipy.config.common.scope import Scope
+from taipy.core.data.excel import ExcelDataNode
+
+
+@pytest.fixture(scope="function")
+def tmp_excel_file():
+    return os.path.join(pathlib.Path(__file__).parent.resolve(), "data_sample/temp.xlsx")
+
+
+@pytest.fixture(scope="function", autouse=True)
+def cleanup(tmp_excel_file):
+    yield
+    if os.path.exists(tmp_excel_file):
+        os.remove(tmp_excel_file)
+
+
+@dataclasses.dataclass
+class MyCustomObject:
+    id: int
+    integer: int
+    text: str
+
+    def __eq__(self, val) -> bool:
+        return self.id == val.id and self.integer == val.integer and self.text == val.text
+
+    def __post_init__(self):
+        for field in dataclasses.fields(self):
+            value = getattr(self, field.name)
+            if not isinstance(value, field.type):
+                setattr(self, field.name, field.type(value))
+
+
+sheet_names = ["Sheet1", "Sheet2"]
+
+
+def test_write_with_header_multiple_sheet_pandas(tmp_excel_file):
+    # With sheet name
+    excel_dn = ExcelDataNode("foo", Scope.SCENARIO, properties={"path": tmp_excel_file, "sheet_name": sheet_names})
+
+    df_1 = pd.DataFrame([{"a": 1, "b": 2, "c": 3}])
+    df_2 = pd.DataFrame([{"a": 4, "b": 5, "c": 6}])
+    sheet_data = {"Sheet1": df_1, "Sheet2": df_2}
+
+    excel_dn.write(sheet_data)
+    excel_dn_data = excel_dn.read()
+    assert len(excel_dn_data) == len(sheet_data) == 2
+    for sheet_name in sheet_data.keys():
+        assert pd.DataFrame.equals(excel_dn_data[sheet_name], sheet_data[sheet_name])
+
+    sheet_data = {"Sheet1": df_1[["a"]], "Sheet2": df_2[["a"]]}
+
+    excel_dn.write(sheet_data)
+    excel_dn_data = excel_dn.read()
+    assert len(excel_dn_data) == len(sheet_data) == 2
+    for sheet_name in sheet_data.keys():
+        assert pd.DataFrame.equals(excel_dn_data[sheet_name], sheet_data[sheet_name])
+
+    sheet_data = {"Sheet1": pd.Series([1, 2, 3]), "Sheet2": pd.Series([4, 5, 6])}
+    excel_dn.write(sheet_data)
+    excel_dn_data = excel_dn.read()
+    assert len(excel_dn_data) == len(sheet_data) == 2
+    for sheet_name in sheet_data.keys():
+        assert np.array_equal(excel_dn_data[sheet_name].to_numpy(), pd.DataFrame(sheet_data[sheet_name]).to_numpy())
+
+    # Without sheet name
+    excel_dn = ExcelDataNode("foo", Scope.SCENARIO, properties={"path": tmp_excel_file})
+
+    df_1 = pd.DataFrame([{"a": 1, "b": 2, "c": 3}])
+    df_2 = pd.DataFrame([{"a": 4, "b": 5, "c": 6}])
+    sheet_data = {"Sheet1": df_1, "Sheet2": df_2}
+
+    excel_dn.write(sheet_data)
+    excel_dn_data = excel_dn.read()
+    assert len(excel_dn_data) == len(sheet_data) == 2
+    for sheet_name in sheet_data.keys():
+        assert pd.DataFrame.equals(excel_dn_data[sheet_name], sheet_data[sheet_name])
+
+    sheet_data = {"Sheet1": df_1[["a"]], "Sheet2": df_2[["a"]]}
+
+    excel_dn.write(sheet_data)
+    excel_dn_data = excel_dn.read()
+    assert len(excel_dn_data) == len(sheet_data) == 2
+    for sheet_name in sheet_data.keys():
+        assert pd.DataFrame.equals(excel_dn_data[sheet_name], sheet_data[sheet_name])
+
+    sheet_data = {"Sheet1": pd.Series([1, 2, 3]), "Sheet2": pd.Series([4, 5, 6])}
+    excel_dn.write(sheet_data)
+    excel_dn_data = excel_dn.read()
+    assert len(excel_dn_data) == len(sheet_data) == 2
+    for sheet_name in sheet_data.keys():
+        assert np.array_equal(excel_dn_data[sheet_name].to_numpy(), pd.DataFrame(sheet_data[sheet_name]).to_numpy())
+
+
+def test_write_with_header_multiple_sheet_numpy(tmp_excel_file):
+    # With sheet name
+    excel_dn = ExcelDataNode(
+        "foo", Scope.SCENARIO, properties={"path": tmp_excel_file, "sheet_name": sheet_names, "exposed_type": "numpy"}
+    )
+
+    arr_1 = np.array([[1], [2], [3]])
+    arr_2 = np.array([[4], [5], [6]])
+    sheet_data = {"Sheet1": arr_1, "Sheet2": arr_2}
+
+    excel_dn.write(sheet_data)
+    excel_dn_data = excel_dn.read()
+    assert len(excel_dn_data) == len(sheet_data) == 2
+    for sheet_name in sheet_data.keys():
+        assert np.array_equal(excel_dn_data[sheet_name], sheet_data[sheet_name])
+
+    arr_1 = arr_1[0:1]
+    arr_2 = arr_2[0:1]
+    sheet_data = {"Sheet1": arr_1, "Sheet2": arr_2}
+
+    excel_dn.write(sheet_data)
+    excel_dn_data = excel_dn.read()
+    assert len(excel_dn_data) == len(sheet_data) == 2
+    for sheet_name in sheet_data.keys():
+        assert np.array_equal(excel_dn_data[sheet_name], sheet_data[sheet_name])
+
+    # Without sheet name
+    excel_dn = ExcelDataNode("foo", Scope.SCENARIO, properties={"path": tmp_excel_file, "exposed_type": "numpy"})
+
+    arr_1 = np.array([[1], [2], [3]])
+    arr_2 = np.array([[4], [5], [6]])
+    sheet_data = {"Sheet1": arr_1, "Sheet2": arr_2}
+
+    excel_dn.write(sheet_data)
+    excel_dn_data = excel_dn.read()
+    assert len(excel_dn_data) == len(sheet_data) == 2
+    for sheet_name in sheet_data.keys():
+        assert np.array_equal(excel_dn_data[sheet_name], sheet_data[sheet_name])
+
+    arr_1 = arr_1[0:1]
+    arr_2 = arr_2[0:1]
+    sheet_data = {"Sheet1": arr_1, "Sheet2": arr_2}
+
+    excel_dn.write(sheet_data)
+    excel_dn_data = excel_dn.read()
+    assert len(excel_dn_data) == len(sheet_data) == 2
+    for sheet_name in sheet_data.keys():
+        assert np.array_equal(excel_dn_data[sheet_name], sheet_data[sheet_name])
+
+
+def test_write_with_header_multiple_sheet_custom_exposed_type(tmp_excel_file):
+    # With sheet name
+    excel_dn = ExcelDataNode(
+        "foo",
+        Scope.SCENARIO,
+        properties={"path": tmp_excel_file, "sheet_name": sheet_names, "exposed_type": MyCustomObject},
+    )
+
+    row_1 = [MyCustomObject(0, 1, "hi"), MyCustomObject(1, 2, "world"), MyCustomObject(2, 3, "text")]
+    row_2 = [MyCustomObject(0, 4, "hello"), MyCustomObject(1, 5, "abc"), MyCustomObject(2, 6, ".")]
+    sheet_data = {"Sheet1": row_1, "Sheet2": row_2}
+
+    excel_dn.write(sheet_data)
+    excel_dn_data = excel_dn.read()
+    assert len(excel_dn_data) == len(sheet_data) == 2
+    for sheet_name in sheet_data.keys():
+        assert all(actual == expected for actual, expected in zip(excel_dn_data[sheet_name], sheet_data[sheet_name]))
+
+    # Without sheet name
+    excel_dn = ExcelDataNode("foo", Scope.SCENARIO, properties={"path": tmp_excel_file, "exposed_type": MyCustomObject})
+
+    row_1 = [MyCustomObject(0, 1, "hi"), MyCustomObject(1, 2, "world"), MyCustomObject(2, 3, "text")]
+    row_2 = [MyCustomObject(0, 4, "hello"), MyCustomObject(1, 5, "abc"), MyCustomObject(2, 6, ".")]
+    sheet_data = {"Sheet1": row_1, "Sheet2": row_2}
+
+    excel_dn.write(sheet_data)
+    excel_dn_data = excel_dn.read()
+    assert len(excel_dn_data) == len(sheet_data) == 2
+    for sheet_name in sheet_data.keys():
+        assert all(actual == expected for actual, expected in zip(excel_dn_data[sheet_name], sheet_data[sheet_name]))
+
+
+def test_write_without_header_multiple_sheet_pandas(tmp_excel_file):
+    # With sheet name
+    excel_dn = ExcelDataNode(
+        "foo", Scope.SCENARIO, properties={"path": tmp_excel_file, "sheet_name": sheet_names, "has_header": False}
+    )
+
+    df_1 = pd.DataFrame([*zip([1, 2, 3])])
+    df_2 = pd.DataFrame([*zip([4, 5, 6])])
+    sheet_data = {"Sheet1": df_1, "Sheet2": df_2}
+
+    excel_dn.write(sheet_data)
+    excel_dn_data = excel_dn.read()
+    assert len(excel_dn_data) == len(sheet_data) == 2
+    for sheet_name in sheet_data.keys():
+        assert pd.DataFrame.equals(excel_dn_data[sheet_name], sheet_data[sheet_name])
+
+    sheet_data = {"Sheet1": df_1[[0]], "Sheet2": df_2[[0]]}
+
+    excel_dn.write(sheet_data)
+    excel_dn_data = excel_dn.read()
+    assert len(excel_dn_data) == len(sheet_data) == 2
+    for sheet_name in sheet_data.keys():
+        assert pd.DataFrame.equals(excel_dn_data[sheet_name], sheet_data[sheet_name])
+
+    sheet_data = {"Sheet1": pd.Series([1, 2, 3]), "Sheet2": pd.Series([4, 5, 6])}
+    excel_dn.write(sheet_data)
+    excel_dn_data = excel_dn.read()
+    assert len(excel_dn_data) == len(sheet_data) == 2
+    for sheet_name in sheet_data.keys():
+        assert np.array_equal(excel_dn_data[sheet_name].to_numpy(), pd.DataFrame(sheet_data[sheet_name]).to_numpy())
+
+    # Without sheet name
+    excel_dn = ExcelDataNode("foo", Scope.SCENARIO, properties={"path": tmp_excel_file, "has_header": False})
+
+    df_1 = pd.DataFrame([*zip([1, 2, 3])])
+    df_2 = pd.DataFrame([*zip([4, 5, 6])])
+    sheet_data = {"Sheet1": df_1, "Sheet2": df_2}
+
+    excel_dn.write(sheet_data)
+    excel_dn_data = excel_dn.read()
+    assert len(excel_dn_data) == len(sheet_data) == 2
+    for sheet_name in sheet_data.keys():
+        assert pd.DataFrame.equals(excel_dn_data[sheet_name], sheet_data[sheet_name])
+
+    sheet_data = {"Sheet1": df_1[[0]], "Sheet2": df_2[[0]]}
+
+    excel_dn.write(sheet_data)
+    excel_dn_data = excel_dn.read()
+    assert len(excel_dn_data) == len(sheet_data) == 2
+    for sheet_name in sheet_data.keys():
+        assert pd.DataFrame.equals(excel_dn_data[sheet_name], sheet_data[sheet_name])
+
+    sheet_data = {"Sheet1": pd.Series([1, 2, 3]), "Sheet2": pd.Series([4, 5, 6])}
+    excel_dn.write(sheet_data)
+    excel_dn_data = excel_dn.read()
+    assert len(excel_dn_data) == len(sheet_data) == 2
+    for sheet_name in sheet_data.keys():
+        assert np.array_equal(excel_dn_data[sheet_name].to_numpy(), pd.DataFrame(sheet_data[sheet_name]).to_numpy())
+
+
+def test_write_without_header_multiple_sheet_numpy(tmp_excel_file):
+    # With sheet name
+    excel_dn = ExcelDataNode(
+        "foo",
+        Scope.SCENARIO,
+        properties={"path": tmp_excel_file, "sheet_name": sheet_names, "exposed_type": "numpy", "has_header": False},
+    )
+
+    arr_1 = np.array([[1], [2], [3]])
+    arr_2 = np.array([[4], [5], [6]])
+    sheet_data = {"Sheet1": arr_1, "Sheet2": arr_2}
+
+    excel_dn.write(sheet_data)
+    excel_dn_data = excel_dn.read()
+    assert len(excel_dn_data) == len(sheet_data) == 2
+    for sheet_name in sheet_data.keys():
+        assert np.array_equal(excel_dn_data[sheet_name], sheet_data[sheet_name])
+
+    arr_1 = arr_1[0:1]
+    arr_2 = arr_2[0:1]
+    sheet_data = {"Sheet1": arr_1, "Sheet2": arr_2}
+
+    excel_dn.write(sheet_data)
+    excel_dn_data = excel_dn.read()
+    assert len(excel_dn_data) == len(sheet_data) == 2
+    for sheet_name in sheet_data.keys():
+        assert np.array_equal(excel_dn_data[sheet_name], sheet_data[sheet_name])
+
+    # Without sheet name
+    excel_dn = ExcelDataNode(
+        "foo", Scope.SCENARIO, properties={"path": tmp_excel_file, "exposed_type": "numpy", "has_header": False}
+    )
+
+    arr_1 = np.array([[1], [2], [3]])
+    arr_2 = np.array([[4], [5], [6]])
+    sheet_data = {"Sheet1": arr_1, "Sheet2": arr_2}
+
+    excel_dn.write(sheet_data)
+    excel_dn_data = excel_dn.read()
+    assert len(excel_dn_data) == len(sheet_data) == 2
+    for sheet_name in sheet_data.keys():
+        assert np.array_equal(excel_dn_data[sheet_name], sheet_data[sheet_name])
+
+    arr_1 = arr_1[0:1]
+    arr_2 = arr_2[0:1]
+    sheet_data = {"Sheet1": arr_1, "Sheet2": arr_2}
+
+    excel_dn.write(sheet_data)
+    excel_dn_data = excel_dn.read()
+    assert len(excel_dn_data) == len(sheet_data) == 2
+    for sheet_name in sheet_data.keys():
+        assert np.array_equal(excel_dn_data[sheet_name], sheet_data[sheet_name])
+
+
+def test_write_without_header_multiple_sheet_custom_exposed_type(tmp_excel_file):
+    # With sheet name
+    excel_dn = ExcelDataNode(
+        "foo",
+        Scope.SCENARIO,
+        properties={
+            "path": tmp_excel_file,
+            "sheet_name": sheet_names,
+            "exposed_type": MyCustomObject,
+            "has_header": False,
+        },
+    )
+
+    row_1 = [MyCustomObject(0, 1, "hi"), MyCustomObject(1, 2, "world"), MyCustomObject(2, 3, "text")]
+    row_2 = [MyCustomObject(0, 4, "hello"), MyCustomObject(1, 5, "abc"), MyCustomObject(2, 6, ".")]
+    sheet_data = {"Sheet1": row_1, "Sheet2": row_2}
+
+    excel_dn.write(sheet_data)
+    excel_dn_data = excel_dn.read()
+    assert len(excel_dn_data) == len(sheet_data) == 2
+    for sheet_name in sheet_data.keys():
+        assert all(actual == expected for actual, expected in zip(excel_dn_data[sheet_name], sheet_data[sheet_name]))
+
+    # Without sheet name
+    excel_dn = ExcelDataNode(
+        "foo", Scope.SCENARIO, properties={"path": tmp_excel_file, "exposed_type": MyCustomObject, "has_header": False}
+    )
+
+    row_1 = [MyCustomObject(0, 1, "hi"), MyCustomObject(1, 2, "world"), MyCustomObject(2, 3, "text")]
+    row_2 = [MyCustomObject(0, 4, "hello"), MyCustomObject(1, 5, "abc"), MyCustomObject(2, 6, ".")]
+    sheet_data = {"Sheet1": row_1, "Sheet2": row_2}
+
+    excel_dn.write(sheet_data)
+    excel_dn_data = excel_dn.read()
+    assert len(excel_dn_data) == len(sheet_data) == 2
+    for sheet_name in sheet_data.keys():
+        assert all(actual == expected for actual, expected in zip(excel_dn_data[sheet_name], sheet_data[sheet_name]))
+
+
+@pytest.mark.parametrize(
+    "content",
+    [
+        (
+            {
+                "Sheet1": pd.DataFrame([{"a": 11, "b": 22, "c": 33}]),
+                "Sheet2": pd.DataFrame([{"a": 44, "b": 55, "c": 66}]),
+            }
+        ),
+        (
+            {
+                "Sheet1": pd.DataFrame({"a": [11, 44], "b": [22, 55], "c": [33, 66]}),
+                "Sheet2": pd.DataFrame([{"a": 77, "b": 88, "c": 99}]),
+            }
+        ),
+        ({"Sheet1": np.array([[11, 22, 33], [44, 55, 66]]), "Sheet2": np.array([[77, 88, 99]])}),
+    ],
+)
+def test_append_pandas_multisheet(excel_file_with_multi_sheet, default_multi_sheet_data_frame, content):
+    dn = ExcelDataNode(
+        "foo", Scope.SCENARIO, properties={"path": excel_file_with_multi_sheet, "sheet_name": ["Sheet1", "Sheet2"]}
+    )
+    assert_frame_equal(dn.read()["Sheet1"], default_multi_sheet_data_frame["Sheet1"])
+    assert_frame_equal(dn.read()["Sheet2"], default_multi_sheet_data_frame["Sheet2"])
+
+    dn.append(content)
+
+    assert_frame_equal(
+        dn.read()["Sheet1"],
+        pd.concat(
+            [default_multi_sheet_data_frame["Sheet1"], pd.DataFrame(content["Sheet1"], columns=["a", "b", "c"])]
+        ).reset_index(drop=True),
+    )
+    assert_frame_equal(
+        dn.read()["Sheet2"],
+        pd.concat(
+            [default_multi_sheet_data_frame["Sheet2"], pd.DataFrame(content["Sheet2"], columns=["a", "b", "c"])]
+        ).reset_index(drop=True),
+    )

+ 431 - 0
tests/core/data/test_write_single_sheet_excel_data_node.py

@@ -0,0 +1,431 @@
+# Copyright 2021-2024 Avaiga Private Limited
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+#        http://www.apache.org/licenses/LICENSE-2.0
+#
+# 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 dataclasses
+import os
+import pathlib
+
+import numpy as np
+import pandas as pd
+import pytest
+from pandas.testing import assert_frame_equal
+
+from taipy.config.common.scope import Scope
+from taipy.core.data.excel import ExcelDataNode
+from taipy.core.exceptions.exceptions import SheetNameLengthMismatch
+
+
+@pytest.fixture(scope="function")
+def tmp_excel_file():
+    return os.path.join(pathlib.Path(__file__).parent.resolve(), "data_sample/temp.xlsx")
+
+
+@pytest.fixture(scope="function", autouse=True)
+def cleanup(tmp_excel_file):
+    yield
+    if os.path.exists(tmp_excel_file):
+        os.remove(tmp_excel_file)
+
+
+@dataclasses.dataclass
+class MyCustomObject:
+    id: int
+    integer: int
+    text: str
+
+    def __eq__(self, val) -> bool:
+        return self.id == val.id and self.integer == val.integer and self.text == val.text
+
+    def __post_init__(self):
+        for field in dataclasses.fields(self):
+            value = getattr(self, field.name)
+            if not isinstance(value, field.type):
+                setattr(self, field.name, field.type(value))
+
+
+def test_write_with_header_single_sheet_pandas(tmp_excel_file):
+    # With sheet name
+    excel_dn = ExcelDataNode("foo", Scope.SCENARIO, properties={"path": tmp_excel_file, "sheet_name": "Sheet1"})
+
+    df = pd.DataFrame([{"a": 1, "b": 2, "c": 3}, {"a": 4, "b": 5, "c": 6}])
+
+    excel_dn.write(df)
+    assert pd.DataFrame.equals(excel_dn.read(), df)
+
+    excel_dn.write(None)
+    assert len(excel_dn.read()) == 0
+
+    excel_dn.write(df["a"])
+    assert pd.DataFrame.equals(excel_dn.read(), df[["a"]])
+
+    series = pd.Series([1, 2, 3])
+    excel_dn.write(series)
+    assert np.array_equal(excel_dn.read().to_numpy(), pd.DataFrame(series).to_numpy())
+
+    excel_dn.write(None)
+    assert excel_dn.read().empty
+
+    # Without sheet name
+    excel_dn = ExcelDataNode("foo", Scope.SCENARIO, properties={"path": tmp_excel_file})
+
+    df = pd.DataFrame([{"a": 1, "b": 2, "c": 3}, {"a": 4, "b": 5, "c": 6}])
+
+    excel_dn.write(df)
+    excel_data = excel_dn.read()
+    assert isinstance(excel_data, dict) and "Sheet1" in excel_data.keys()
+    assert pd.DataFrame.equals(excel_data["Sheet1"], df)
+
+    excel_dn.write(None)
+    excel_data = excel_dn.read()
+    assert isinstance(excel_data, dict) and "Sheet1" in excel_data.keys()
+    assert len(excel_dn.read()["Sheet1"]) == 0
+
+    excel_dn.write(df["a"])
+    excel_data = excel_dn.read()
+    assert isinstance(excel_data, dict) and "Sheet1" in excel_data.keys()
+    assert pd.DataFrame.equals(excel_dn.read()["Sheet1"], df[["a"]])
+
+    series = pd.Series([1, 2, 3])
+    excel_dn.write(series)
+    excel_data = excel_dn.read()
+    assert isinstance(excel_data, dict) and "Sheet1" in excel_data.keys()
+    assert np.array_equal(excel_dn.read()["Sheet1"].to_numpy(), pd.DataFrame(series).to_numpy())
+
+    excel_dn.write(None)
+    excel_data = excel_dn.read()
+    assert isinstance(excel_data, dict) and "Sheet1" in excel_data.keys()
+    assert excel_dn.read()["Sheet1"].empty
+
+
+def test_write_without_header_single_sheet_pandas(tmp_excel_file):
+    # With sheet name
+    excel_dn = ExcelDataNode(
+        "foo", Scope.SCENARIO, properties={"path": tmp_excel_file, "sheet_name": "Sheet1", "has_header": False}
+    )
+
+    df = pd.DataFrame([*zip([1, 2, 3], [4, 5, 6])])
+
+    excel_dn.write(df)
+    assert pd.DataFrame.equals(excel_dn.read(), df)
+
+    excel_dn.write(None)
+    assert len(excel_dn.read()) == 0
+
+    excel_dn.write(df[0])
+    assert pd.DataFrame.equals(excel_dn.read(), df[[0]])
+
+    series = pd.Series([1, 2, 3])
+    excel_dn.write(series)
+    assert np.array_equal(excel_dn.read().to_numpy(), pd.DataFrame(series).to_numpy())
+
+    excel_dn.write(None)
+    assert excel_dn.read().empty
+
+    # Without sheet name
+    excel_dn = ExcelDataNode("foo", Scope.SCENARIO, properties={"path": tmp_excel_file, "has_header": False})
+
+    df = pd.DataFrame([*zip([1, 2, 3], [4, 5, 6])])
+
+    excel_dn.write(df)
+    excel_data = excel_dn.read()
+    assert isinstance(excel_data, dict) and "Sheet1" in excel_data.keys()
+    assert pd.DataFrame.equals(excel_data["Sheet1"], df)
+
+    excel_dn.write(None)
+    excel_data = excel_dn.read()
+    assert isinstance(excel_data, dict) and "Sheet1" in excel_data.keys()
+    assert len(excel_dn.read()["Sheet1"]) == 0
+
+    excel_dn.write(df[0])
+    excel_data = excel_dn.read()
+    assert isinstance(excel_data, dict) and "Sheet1" in excel_data.keys()
+    assert pd.DataFrame.equals(excel_dn.read()["Sheet1"], df[[0]])
+
+    series = pd.Series([1, 2, 3])
+    excel_dn.write(series)
+    excel_data = excel_dn.read()
+    assert isinstance(excel_data, dict) and "Sheet1" in excel_data.keys()
+    assert np.array_equal(excel_dn.read()["Sheet1"].to_numpy(), pd.DataFrame(series).to_numpy())
+
+    excel_dn.write(None)
+    excel_data = excel_dn.read()
+    assert isinstance(excel_data, dict) and "Sheet1" in excel_data.keys()
+    assert excel_dn.read()["Sheet1"].empty
+
+
+def test_write_with_header_single_sheet_numpy(tmp_excel_file):
+    # With sheet name
+    excel_dn = ExcelDataNode(
+        "foo", Scope.SCENARIO, properties={"path": tmp_excel_file, "sheet_name": "Sheet1", "exposed_type": "numpy"}
+    )
+
+    arr = np.array([[1], [2], [3], [4], [5]])
+    excel_dn.write(arr)
+    assert np.array_equal(excel_dn.read(), arr)
+
+    arr = arr[0:3]
+    excel_dn.write(arr)
+    assert np.array_equal(excel_dn.read(), arr)
+
+    excel_dn.write(None)
+    assert excel_dn.read().size == 0
+
+    # Without sheet name
+    excel_dn = ExcelDataNode("foo", Scope.SCENARIO, properties={"path": tmp_excel_file, "exposed_type": "numpy"})
+
+    arr = np.array([[1], [2], [3], [4], [5]])
+    excel_dn.write(arr)
+    excel_data = excel_dn.read()
+    assert isinstance(excel_data, dict) and "Sheet1" in excel_data.keys()
+    assert np.array_equal(excel_dn.read()["Sheet1"], arr)
+
+    arr = arr[0:3]
+    excel_dn.write(arr)
+    excel_data = excel_dn.read()
+    assert isinstance(excel_data, dict) and "Sheet1" in excel_data.keys()
+    assert np.array_equal(excel_dn.read()["Sheet1"], arr)
+
+    excel_dn.write(None)
+    excel_data = excel_dn.read()
+    assert isinstance(excel_data, dict) and "Sheet1" in excel_data.keys()
+    assert excel_dn.read()["Sheet1"].size == 0
+
+
+def test_write_without_header_single_sheet_numpy(tmp_excel_file):
+    # With sheet name
+    excel_dn = ExcelDataNode(
+        "foo",
+        Scope.SCENARIO,
+        properties={"path": tmp_excel_file, "sheet_name": "Sheet1", "exposed_type": "numpy", "has_header": False},
+    )
+
+    arr = np.array([[1], [2], [3], [4], [5]])
+    excel_dn.write(arr)
+    assert np.array_equal(excel_dn.read(), arr)
+
+    arr = arr[0:3]
+    excel_dn.write(arr)
+    assert np.array_equal(excel_dn.read(), arr)
+
+    excel_dn.write(None)
+    assert excel_dn.read().size == 0
+
+    # Without sheet name
+    excel_dn = ExcelDataNode(
+        "foo", Scope.SCENARIO, properties={"path": tmp_excel_file, "exposed_type": "numpy", "has_header": False}
+    )
+
+    arr = np.array([[1], [2], [3], [4], [5]])
+    excel_dn.write(arr)
+    excel_data = excel_dn.read()
+    assert isinstance(excel_data, dict) and "Sheet1" in excel_data.keys()
+    assert np.array_equal(excel_dn.read()["Sheet1"], arr)
+
+    arr = arr[0:3]
+    excel_dn.write(arr)
+    excel_data = excel_dn.read()
+    assert isinstance(excel_data, dict) and "Sheet1" in excel_data.keys()
+    assert np.array_equal(excel_dn.read()["Sheet1"], arr)
+
+    excel_dn.write(None)
+    excel_data = excel_dn.read()
+    assert isinstance(excel_data, dict) and "Sheet1" in excel_data.keys()
+    assert excel_dn.read()["Sheet1"].size == 0
+
+
+def test_write_with_header_single_sheet_custom_exposed_type(tmp_excel_file):
+    # With sheet name
+    excel_dn = ExcelDataNode(
+        "foo",
+        Scope.SCENARIO,
+        properties={"path": tmp_excel_file, "sheet_name": "Sheet1", "exposed_type": MyCustomObject},
+    )
+
+    data = [MyCustomObject(0, 1, "hi"), MyCustomObject(1, 2, "world"), MyCustomObject(2, 3, "text")]
+    excel_dn.write(data)
+    assert all(actual == expected for actual, expected in zip(excel_dn.read(), data))
+
+    excel_dn.write(None)
+    assert excel_dn.read() == []
+
+    # TODO: fix this part
+    # Without sheet name
+    # excel_dn = ExcelDataNode(
+    #     "foo", Scope.SCENARIO,
+    #     properties={"path": tmp_excel_file, "exposed_type": MyCustomObject}
+    # )
+
+    # data = [MyCustomObject(0, 1, "hi"), MyCustomObject(1, 2, "world"), MyCustomObject(2, 3, "text")]
+    # excel_dn.write(data)
+    # excel_data = excel_dn.read()
+    # assert isinstance(excel_data, dict) and "Sheet1" in excel_data.keys()
+    # assert all(actual == expected for actual, expected in zip(excel_dn.read()["Sheet1"], data))
+
+    # excel_dn.write(None)
+    # excel_data = excel_dn.read()
+    # assert isinstance(excel_data, dict) and "Sheet1" in excel_data.keys()
+    # assert excel_dn.read()["Sheet1"] == []
+
+
+def test_write_without_header_single_sheet_custom_exposed_type(tmp_excel_file):
+    # With sheet name
+    excel_dn = ExcelDataNode(
+        "foo",
+        Scope.SCENARIO,
+        properties={
+            "path": tmp_excel_file,
+            "sheet_name": "Sheet1",
+            "exposed_type": MyCustomObject,
+            "has_header": False,
+        },
+    )
+
+    data = [MyCustomObject(0, 1, "hi"), MyCustomObject(1, 2, "world"), MyCustomObject(2, 3, "text")]
+    excel_dn.write(data)
+    assert all(actual == expected for actual, expected in zip(excel_dn.read(), data))
+
+    excel_dn.write(None)
+    assert excel_dn.read() == []
+
+    # TODO: fix this part
+    # Without sheet name
+    # excel_dn = ExcelDataNode(
+    #     "foo",
+    #     Scope.SCENARIO,
+    #     properties={"path": tmp_excel_file, "exposed_type": MyCustomObject, "has_header": False}
+    # )
+
+    # data = [MyCustomObject(0, 1, "hi"), MyCustomObject(1, 2, "world"), MyCustomObject(2, 3, "text")]
+    # excel_dn.write(data)
+    # excel_data = excel_dn.read()
+    # assert isinstance(excel_data, dict) and "Sheet1" in excel_data.keys()
+    # assert all(actual == expected for actual, expected in zip(excel_dn.read()["Sheet1"], data))
+
+    # excel_dn.write(None)
+    # excel_data = excel_dn.read()
+    # assert isinstance(excel_data, dict) and "Sheet1" in excel_data.keys()
+    # assert excel_dn.read()["Sheet1"] == []
+
+
+def test_raise_write_with_sheet_name_length_mismatch(excel_file_with_sheet_name):
+    excel_dn = ExcelDataNode(
+        "foo",
+        Scope.SCENARIO,
+        properties={"path": excel_file_with_sheet_name, "sheet_name": ["sheet_name_1", "sheet_name_2"]},
+    )
+    # TODO: only raised in single sheet case?
+    with pytest.raises(SheetNameLengthMismatch):
+        excel_dn.write([])
+
+
+@pytest.mark.parametrize(
+    "content,columns,sheet_name",
+    [
+        ([[11, 22, 33], [44, 55, 66]], ["e", "f", "g"], "sheet_name"),
+        ([[11, 22, 33], [44, 55, 66]], ["e", "f", "g"], ["sheet_name"]),
+    ],
+)
+def test_write_with_column_and_sheet_name(excel_file_with_sheet_name, default_data_frame, content, columns, sheet_name):
+    # TODO: fix this test??
+    excel_dn = ExcelDataNode(
+        "foo", Scope.SCENARIO, properties={"path": excel_file_with_sheet_name, "sheet_name": sheet_name}
+    )
+    df = pd.DataFrame(content)
+
+    if isinstance(sheet_name, str):
+        assert np.array_equal(excel_dn.read().values, default_data_frame.values)
+    else:
+        assert np.array_equal(excel_dn.read()["sheet_name"].values, default_data_frame.values)
+
+    excel_dn.write_with_column_names(content, columns)
+
+    if isinstance(sheet_name, str):
+        assert np.array_equal(excel_dn.read().values, df.values)
+    else:
+        assert np.array_equal(excel_dn.read()["sheet_name"].values, df.values)
+
+    sheet_names = pd.ExcelFile(excel_file_with_sheet_name).sheet_names
+    expected_sheet_name = sheet_name[0] if isinstance(sheet_name, list) else sheet_name
+
+    assert sheet_names[0] == expected_sheet_name
+
+    excel_dn.write(None)
+    if isinstance(sheet_name, str):
+        assert len(excel_dn.read()) == 0
+    else:
+        assert len(excel_dn.read()) == 1
+
+
+@pytest.mark.parametrize(
+    "content",
+    [
+        ([{"a": 11, "b": 22, "c": 33}, {"a": 44, "b": 55, "c": 66}]),
+        (pd.DataFrame([{"a": 11, "b": 22, "c": 33}, {"a": 44, "b": 55, "c": 66}])),
+        ([[11, 22, 33], [44, 55, 66]]),
+    ],
+)
+def test_append_pandas_with_sheetname(excel_file, default_data_frame, content):
+    dn = ExcelDataNode("foo", Scope.SCENARIO, properties={"path": excel_file, "sheet_name": "Sheet1"})
+    assert_frame_equal(dn.read(), default_data_frame)
+
+    dn.append(content)
+    assert_frame_equal(
+        dn.read(),
+        pd.concat([default_data_frame, pd.DataFrame(content, columns=["a", "b", "c"])]).reset_index(drop=True),
+    )
+
+
+@pytest.mark.parametrize(
+    "content",
+    [
+        ([{"a": 11, "b": 22, "c": 33}, {"a": 44, "b": 55, "c": 66}]),
+        (pd.DataFrame([{"a": 11, "b": 22, "c": 33}, {"a": 44, "b": 55, "c": 66}])),
+        ([[11, 22, 33], [44, 55, 66]]),
+    ],
+)
+def test_append_pandas_without_sheetname(excel_file, default_data_frame, content):
+    dn = ExcelDataNode("foo", Scope.SCENARIO, properties={"path": excel_file})
+    assert_frame_equal(dn.read()["Sheet1"], default_data_frame)
+
+    dn.append(content)
+    assert_frame_equal(
+        dn.read()["Sheet1"],
+        pd.concat([default_data_frame, pd.DataFrame(content, columns=["a", "b", "c"])]).reset_index(drop=True),
+    )
+
+
+@pytest.mark.parametrize(
+    "content",
+    [
+        ({"Sheet1": pd.DataFrame([{"a": 11, "b": 22, "c": 33}])}),
+        (pd.DataFrame({"a": [11, 44], "b": [22, 55], "c": [33, 66]})),
+        ([[11, 22, 33], [44, 55, 66]]),
+    ],
+)
+def test_append_only_first_sheet_of_a_multisheet_file(
+    excel_file_with_multi_sheet, default_multi_sheet_data_frame, content
+):
+    dn = ExcelDataNode(
+        "foo", Scope.SCENARIO, properties={"path": excel_file_with_multi_sheet, "sheet_name": ["Sheet1", "Sheet2"]}
+    )
+    assert_frame_equal(dn.read()["Sheet1"], default_multi_sheet_data_frame["Sheet1"])
+    assert_frame_equal(dn.read()["Sheet2"], default_multi_sheet_data_frame["Sheet2"])
+
+    dn.append(content)
+
+    appended_content = content["Sheet1"] if isinstance(content, dict) else content
+    assert_frame_equal(
+        dn.read()["Sheet1"],
+        pd.concat(
+            [default_multi_sheet_data_frame["Sheet1"], pd.DataFrame(appended_content, columns=["a", "b", "c"])]
+        ).reset_index(drop=True),
+    )
+    assert_frame_equal(dn.read()["Sheet2"], default_multi_sheet_data_frame["Sheet2"])