test_excel_data_node.py 59 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395
  1. # Copyright 2021-2024 Avaiga Private Limited
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
  4. # the License. You may obtain a copy of the License at
  5. #
  6. # http://www.apache.org/licenses/LICENSE-2.0
  7. #
  8. # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
  9. # an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
  10. # specific language governing permissions and limitations under the License.
  11. import os
  12. import pathlib
  13. from datetime import datetime
  14. from time import sleep
  15. from typing import Dict
  16. import numpy as np
  17. import pandas as pd
  18. import pytest
  19. from pandas.testing import assert_frame_equal
  20. from taipy.config.common.scope import Scope
  21. from taipy.config.config import Config
  22. from taipy.core.data._data_manager import _DataManager
  23. from taipy.core.data.data_node_id import DataNodeId
  24. from taipy.core.data.excel import ExcelDataNode
  25. from taipy.core.data.operator import JoinOperator, Operator
  26. from taipy.core.exceptions.exceptions import (
  27. ExposedTypeLengthMismatch,
  28. InvalidExposedType,
  29. NoData,
  30. NonExistingExcelSheet,
  31. SheetNameLengthMismatch,
  32. )
  33. @pytest.fixture(scope="function", autouse=True)
  34. def cleanup():
  35. yield
  36. path = os.path.join(pathlib.Path(__file__).parent.resolve(), "data_sample/temp.xlsx")
  37. if os.path.exists(path):
  38. os.remove(path)
  39. class MyCustomObject:
  40. def __init__(self, id, integer, text):
  41. self.id = id
  42. self.integer = integer
  43. self.text = text
  44. class MyCustomObject1:
  45. def __init__(self, id, integer, text):
  46. self.id = id
  47. self.integer = integer
  48. self.text = text
  49. class MyCustomObject2:
  50. def __init__(self, id, integer, text):
  51. self.id = id
  52. self.integer = integer
  53. self.text = text
  54. class TestExcelDataNode:
  55. def test_new_excel_data_node_with_existing_file_is_ready_for_reading(self):
  56. not_ready_dn_cfg = Config.configure_data_node("not_ready_data_node_config_id", "excel", path="NOT_EXISTING.csv")
  57. path = os.path.join(pathlib.Path(__file__).parent.resolve(), "data_sample/example.xlsx")
  58. ready_dn_cfg = Config.configure_data_node("ready_data_node_config_id", "excel", path=path)
  59. dns = _DataManager._bulk_get_or_create([not_ready_dn_cfg, ready_dn_cfg])
  60. assert not dns[not_ready_dn_cfg].is_ready_for_reading
  61. assert dns[ready_dn_cfg].is_ready_for_reading
  62. def test_create(self):
  63. path = "data/node/path"
  64. sheet_names = ["sheet_name_1", "sheet_name_2"]
  65. dn = ExcelDataNode(
  66. "foo_bar",
  67. Scope.SCENARIO,
  68. properties={"path": path, "has_header": False, "sheet_name": sheet_names, "name": "super name"},
  69. )
  70. assert isinstance(dn, ExcelDataNode)
  71. assert dn.storage_type() == "excel"
  72. assert dn.config_id == "foo_bar"
  73. assert dn.name == "super name"
  74. assert dn.scope == Scope.SCENARIO
  75. assert dn.id is not None
  76. assert dn.owner_id is None
  77. assert dn.parent_ids == set()
  78. assert dn.last_edit_date is None
  79. assert dn.job_ids == []
  80. assert not dn.is_ready_for_reading
  81. assert dn.path == path
  82. assert dn.has_header is False
  83. assert dn.sheet_name == sheet_names
  84. def test_get_user_properties(self, excel_file):
  85. dn_1 = ExcelDataNode("dn_1", Scope.SCENARIO, properties={"path": "data/node/path"})
  86. assert dn_1._get_user_properties() == {}
  87. dn_2 = ExcelDataNode(
  88. "dn_2",
  89. Scope.SCENARIO,
  90. properties={
  91. "exposed_type": "numpy",
  92. "default_data": "foo",
  93. "default_path": excel_file,
  94. "has_header": False,
  95. "sheet_name": ["sheet_name_1", "sheet_name_2"],
  96. "foo": "bar",
  97. },
  98. )
  99. # exposed_type, default_data, default_path, path, has_header are filtered out
  100. assert dn_2._get_user_properties() == {"foo": "bar"}
  101. def test_modin_deprecated_in_favor_of_pandas(self):
  102. path = os.path.join(pathlib.Path(__file__).parent.resolve(), "data_sample/example.xlsx")
  103. # Create ExcelDataNode with modin exposed_type
  104. props = {"path": path, "sheet_name": "Sheet1", "exposed_type": "modin"}
  105. modin_dn = ExcelDataNode("bar", Scope.SCENARIO, properties=props)
  106. assert modin_dn.properties["exposed_type"] == "pandas"
  107. data_modin = modin_dn.read()
  108. assert isinstance(data_modin, pd.DataFrame)
  109. def test_read_with_header(self):
  110. with pytest.raises(NoData):
  111. not_existing_excel = ExcelDataNode("foo", Scope.SCENARIO, properties={"path": "WRONG.xlsx"})
  112. assert not_existing_excel.read() is None
  113. not_existing_excel.read_or_raise()
  114. empty_excel_path = os.path.join(pathlib.Path(__file__).parent.resolve(), "data_sample/empty.xlsx")
  115. empty_excel = ExcelDataNode(
  116. "foo",
  117. Scope.SCENARIO,
  118. properties={"path": empty_excel_path, "exposed_type": MyCustomObject, "has_header": True},
  119. )
  120. assert len(empty_excel.read()) == 0
  121. path = os.path.join(pathlib.Path(__file__).parent.resolve(), "data_sample/example.xlsx")
  122. # Create ExcelDataNode without exposed_type (Default is pandas.DataFrame)
  123. excel_data_node_as_pandas = ExcelDataNode(
  124. "bar", Scope.SCENARIO, properties={"path": path, "sheet_name": "Sheet1"}
  125. )
  126. data_pandas = excel_data_node_as_pandas.read()
  127. assert isinstance(data_pandas, pd.DataFrame)
  128. assert len(data_pandas) == 5
  129. assert np.array_equal(data_pandas.to_numpy(), pd.read_excel(path).to_numpy())
  130. # Create ExcelDataNode with numpy exposed_type
  131. excel_data_node_as_numpy = ExcelDataNode(
  132. "bar", Scope.SCENARIO, properties={"path": path, "exposed_type": "numpy", "sheet_name": "Sheet1"}
  133. )
  134. data_numpy = excel_data_node_as_numpy.read()
  135. assert isinstance(data_numpy, np.ndarray)
  136. assert len(data_numpy) == 5
  137. assert np.array_equal(data_numpy, pd.read_excel(path).to_numpy())
  138. # Create the same ExcelDataNode but with custom exposed_type
  139. non_existing_sheet_name_custom = ExcelDataNode(
  140. "bar",
  141. Scope.SCENARIO,
  142. properties={"path": path, "sheet_name": "abc", "exposed_type": MyCustomObject},
  143. )
  144. with pytest.raises(NonExistingExcelSheet):
  145. non_existing_sheet_name_custom.read()
  146. excel_data_node_as_custom_object = ExcelDataNode(
  147. "bar",
  148. Scope.SCENARIO,
  149. properties={"path": path, "exposed_type": MyCustomObject, "sheet_name": "Sheet1"},
  150. )
  151. data_custom = excel_data_node_as_custom_object.read()
  152. assert isinstance(data_custom, list)
  153. assert len(data_custom) == 5
  154. for (_, row_pandas), row_custom in zip(data_pandas.iterrows(), data_custom):
  155. assert isinstance(row_custom, MyCustomObject)
  156. assert row_pandas["id"] == row_custom.id
  157. assert row_pandas["integer"] == row_custom.integer
  158. assert row_pandas["text"] == row_custom.text
  159. def test_read_without_header(self):
  160. not_existing_excel = ExcelDataNode(
  161. "foo", Scope.SCENARIO, properties={"path": "WRONG.xlsx", "has_header": False}
  162. )
  163. with pytest.raises(NoData):
  164. assert not_existing_excel.read() is None
  165. not_existing_excel.read_or_raise()
  166. path = os.path.join(pathlib.Path(__file__).parent.resolve(), "data_sample/example.xlsx")
  167. # Create ExcelDataNode without exposed_type (Default is pandas.DataFrame)
  168. excel_data_node_as_pandas = ExcelDataNode(
  169. "bar", Scope.SCENARIO, properties={"path": path, "has_header": False, "sheet_name": "Sheet1"}
  170. )
  171. data_pandas = excel_data_node_as_pandas.read()
  172. assert isinstance(data_pandas, pd.DataFrame)
  173. assert len(data_pandas) == 6
  174. assert np.array_equal(data_pandas.to_numpy(), pd.read_excel(path, header=None).to_numpy())
  175. # Create ExcelDataNode with numpy exposed_type
  176. excel_data_node_as_numpy = ExcelDataNode(
  177. "bar",
  178. Scope.SCENARIO,
  179. properties={"path": path, "has_header": False, "exposed_type": "numpy", "sheet_name": "Sheet1"},
  180. )
  181. data_numpy = excel_data_node_as_numpy.read()
  182. assert isinstance(data_numpy, np.ndarray)
  183. assert len(data_numpy) == 6
  184. assert np.array_equal(data_numpy, pd.read_excel(path, header=None).to_numpy())
  185. # Create the same ExcelDataNode but with custom exposed_type
  186. non_existing_sheet_name_custom = ExcelDataNode(
  187. "bar",
  188. Scope.SCENARIO,
  189. properties={"path": path, "has_header": False, "sheet_name": "abc", "exposed_type": MyCustomObject},
  190. )
  191. with pytest.raises(NonExistingExcelSheet):
  192. non_existing_sheet_name_custom.read()
  193. excel_data_node_as_custom_object = ExcelDataNode(
  194. "bar",
  195. Scope.SCENARIO,
  196. properties={
  197. "path": path,
  198. "has_header": False,
  199. "exposed_type": MyCustomObject,
  200. "sheet_name": "Sheet1",
  201. },
  202. )
  203. data_custom = excel_data_node_as_custom_object.read()
  204. assert isinstance(data_custom, list)
  205. assert len(data_custom) == 6
  206. for (_, row_pandas), row_custom in zip(data_pandas.iterrows(), data_custom):
  207. assert isinstance(row_custom, MyCustomObject)
  208. assert row_pandas[0] == row_custom.id
  209. assert row_pandas[1] == row_custom.integer
  210. assert row_pandas[2] == row_custom.text
  211. @pytest.mark.parametrize(
  212. "content,columns",
  213. [
  214. ([{"a": 11, "b": 22, "c": 33}, {"a": 44, "b": 55, "c": 66}], None),
  215. ([[11, 22, 33], [44, 55, 66]], None),
  216. ([[11, 22, 33], [44, 55, 66]], ["e", "f", "g"]),
  217. ],
  218. )
  219. def test_write(self, excel_file, default_data_frame, content, columns):
  220. excel_dn = ExcelDataNode("foo", Scope.SCENARIO, properties={"path": excel_file, "sheet_name": "Sheet1"})
  221. assert np.array_equal(excel_dn.read().values, default_data_frame.values)
  222. if not columns:
  223. excel_dn.write(content)
  224. df = pd.DataFrame(content)
  225. else:
  226. excel_dn.write_with_column_names(content, columns)
  227. df = pd.DataFrame(content, columns=columns)
  228. assert np.array_equal(excel_dn.read().values, df.values)
  229. excel_dn.write(None)
  230. assert len(excel_dn.read()) == 0
  231. @pytest.mark.parametrize(
  232. "content,sheet_name",
  233. [
  234. ([{"a": 11, "b": 22, "c": 33}, {"a": 44, "b": 55, "c": 66}], "sheet_name"),
  235. ([[11, 22, 33], [44, 55, 66]], ["sheet_name"]),
  236. ],
  237. )
  238. def test_write_with_sheet_name(self, excel_file_with_sheet_name, default_data_frame, content, sheet_name):
  239. excel_dn = ExcelDataNode(
  240. "foo", Scope.SCENARIO, properties={"path": excel_file_with_sheet_name, "sheet_name": sheet_name}
  241. )
  242. df = pd.DataFrame(content)
  243. if isinstance(sheet_name, str):
  244. assert np.array_equal(excel_dn.read().values, default_data_frame.values)
  245. else:
  246. assert np.array_equal(excel_dn.read()["sheet_name"].values, default_data_frame.values)
  247. excel_dn.write(content)
  248. if isinstance(sheet_name, str):
  249. assert np.array_equal(excel_dn.read().values, df.values)
  250. else:
  251. assert np.array_equal(excel_dn.read()["sheet_name"].values, df.values)
  252. sheet_names = pd.ExcelFile(excel_file_with_sheet_name).sheet_names
  253. expected_sheet_name = sheet_name[0] if isinstance(sheet_name, list) else sheet_name
  254. assert sheet_names[0] == expected_sheet_name
  255. excel_dn.write(None)
  256. if isinstance(sheet_name, str):
  257. assert len(excel_dn.read()) == 0
  258. else:
  259. assert len(excel_dn.read()) == 1
  260. @pytest.mark.parametrize(
  261. "content,sheet_name",
  262. [
  263. ([[11, 22, 33], [44, 55, 66]], ["sheet_name_1", "sheet_name_2"]),
  264. ],
  265. )
  266. def test_raise_write_with_sheet_name_length_mismatch(
  267. self, excel_file_with_sheet_name, default_data_frame, content, sheet_name
  268. ):
  269. excel_dn = ExcelDataNode(
  270. "foo", Scope.SCENARIO, properties={"path": excel_file_with_sheet_name, "sheet_name": sheet_name}
  271. )
  272. with pytest.raises(SheetNameLengthMismatch):
  273. excel_dn.write(content)
  274. @pytest.mark.parametrize(
  275. "content",
  276. [
  277. ([{"a": 11, "b": 22, "c": 33}, {"a": 44, "b": 55, "c": 66}]),
  278. ],
  279. )
  280. def test_write_without_sheet_name(self, excel_file_with_sheet_name, default_data_frame, content):
  281. excel_dn = ExcelDataNode("foo", Scope.SCENARIO, properties={"path": excel_file_with_sheet_name})
  282. default_data_frame = {"sheet_name": default_data_frame}
  283. df = {"Sheet1": pd.DataFrame(content)}
  284. assert np.array_equal(excel_dn.read()["sheet_name"].values, default_data_frame["sheet_name"].values)
  285. excel_dn.write(content)
  286. assert np.array_equal(excel_dn.read()["Sheet1"].values, df["Sheet1"].values)
  287. sheet_names = pd.ExcelFile(excel_file_with_sheet_name).sheet_names
  288. expected_sheet_name = "Sheet1"
  289. assert sheet_names[0] == expected_sheet_name
  290. excel_dn.write(None)
  291. assert len(excel_dn.read()) == 1
  292. @pytest.mark.parametrize(
  293. "content,columns,sheet_name",
  294. [
  295. ([[11, 22, 33], [44, 55, 66]], ["e", "f", "g"], "sheet_name"),
  296. ([[11, 22, 33], [44, 55, 66]], ["e", "f", "g"], ["sheet_name"]),
  297. ],
  298. )
  299. def test_write_with_column_and_sheet_name(
  300. self, excel_file_with_sheet_name, default_data_frame, content, columns, sheet_name
  301. ):
  302. excel_dn = ExcelDataNode(
  303. "foo", Scope.SCENARIO, properties={"path": excel_file_with_sheet_name, "sheet_name": sheet_name}
  304. )
  305. df = pd.DataFrame(content)
  306. if isinstance(sheet_name, str):
  307. assert np.array_equal(excel_dn.read().values, default_data_frame.values)
  308. else:
  309. assert np.array_equal(excel_dn.read()["sheet_name"].values, default_data_frame.values)
  310. excel_dn.write_with_column_names(content, columns)
  311. if isinstance(sheet_name, str):
  312. assert np.array_equal(excel_dn.read().values, df.values)
  313. else:
  314. assert np.array_equal(excel_dn.read()["sheet_name"].values, df.values)
  315. sheet_names = pd.ExcelFile(excel_file_with_sheet_name).sheet_names
  316. expected_sheet_name = sheet_name[0] if isinstance(sheet_name, list) else sheet_name
  317. assert sheet_names[0] == expected_sheet_name
  318. excel_dn.write(None)
  319. if isinstance(sheet_name, str):
  320. assert len(excel_dn.read()) == 0
  321. else:
  322. assert len(excel_dn.read()) == 1
  323. def test_read_multi_sheet_with_header(self):
  324. not_existing_excel = ExcelDataNode(
  325. "foo",
  326. Scope.SCENARIO,
  327. properties={"path": "WRONG.xlsx", "sheet_name": ["sheet_name_1", "sheet_name_2"]},
  328. )
  329. with pytest.raises(NoData):
  330. assert not_existing_excel.read() is None
  331. not_existing_excel.read_or_raise()
  332. path = os.path.join(pathlib.Path(__file__).parent.resolve(), "data_sample/example.xlsx")
  333. sheet_names = ["Sheet1", "Sheet2"]
  334. # Create ExcelDataNode without exposed_type (Default is pandas.DataFrame)
  335. excel_data_node_as_pandas = ExcelDataNode(
  336. "bar", Scope.SCENARIO, properties={"path": path, "sheet_name": sheet_names}
  337. )
  338. data_pandas = excel_data_node_as_pandas.read()
  339. assert isinstance(data_pandas, Dict)
  340. assert len(data_pandas) == 2
  341. assert all(
  342. len(data_pandas[sheet_name] == 5) and isinstance(data_pandas[sheet_name], pd.DataFrame)
  343. for sheet_name in sheet_names
  344. )
  345. assert list(data_pandas.keys()) == sheet_names
  346. for sheet_name in sheet_names:
  347. assert data_pandas[sheet_name].equals(pd.read_excel(path, sheet_name=sheet_name))
  348. excel_data_node_as_pandas_no_sheet_name = ExcelDataNode("bar", Scope.SCENARIO, properties={"path": path})
  349. data_pandas_no_sheet_name = excel_data_node_as_pandas_no_sheet_name.read()
  350. assert isinstance(data_pandas_no_sheet_name, Dict)
  351. for key in data_pandas_no_sheet_name.keys():
  352. assert isinstance(data_pandas_no_sheet_name[key], pd.DataFrame)
  353. assert data_pandas[key].equals(data_pandas_no_sheet_name[key])
  354. # Create ExcelDataNode with numpy exposed_type
  355. excel_data_node_as_numpy = ExcelDataNode(
  356. "bar",
  357. Scope.SCENARIO,
  358. properties={"path": path, "sheet_name": sheet_names, "exposed_type": "numpy"},
  359. )
  360. data_numpy = excel_data_node_as_numpy.read()
  361. assert isinstance(data_numpy, Dict)
  362. assert len(data_numpy) == 2
  363. assert all(
  364. len(data_numpy[sheet_name] == 5) and isinstance(data_numpy[sheet_name], np.ndarray)
  365. for sheet_name in sheet_names
  366. )
  367. assert list(data_numpy.keys()) == sheet_names
  368. for sheet_name in sheet_names:
  369. assert np.array_equal(data_pandas[sheet_name], pd.read_excel(path, sheet_name=sheet_name).to_numpy())
  370. excel_data_node_as_numpy_no_sheet_name = ExcelDataNode(
  371. "bar",
  372. Scope.SCENARIO,
  373. properties={"path": path, "exposed_type": "numpy"},
  374. )
  375. data_numpy_no_sheet_name = excel_data_node_as_numpy_no_sheet_name.read()
  376. assert isinstance(data_numpy_no_sheet_name, Dict)
  377. for key in data_numpy_no_sheet_name.keys():
  378. assert isinstance(data_numpy_no_sheet_name[key], np.ndarray)
  379. assert np.array_equal(data_numpy[key], data_numpy_no_sheet_name[key])
  380. # Create the same ExcelDataNode but with custom exposed_type
  381. non_existing_sheet_name_custom = ExcelDataNode(
  382. "bar",
  383. Scope.SCENARIO,
  384. properties={
  385. "path": path,
  386. "sheet_name": ["Sheet1", "xyz"],
  387. "exposed_type": MyCustomObject1,
  388. },
  389. )
  390. with pytest.raises(NonExistingExcelSheet):
  391. non_existing_sheet_name_custom.read()
  392. excel_data_node_as_custom_object = ExcelDataNode(
  393. "bar",
  394. Scope.SCENARIO,
  395. properties={"path": path, "sheet_name": sheet_names, "exposed_type": MyCustomObject1},
  396. )
  397. data_custom = excel_data_node_as_custom_object.read()
  398. assert isinstance(data_custom, Dict)
  399. assert len(data_custom) == 2
  400. assert all(len(data_custom[sheet_name]) == 5 for sheet_name in sheet_names)
  401. assert list(data_custom.keys()) == sheet_names
  402. for sheet_name in sheet_names:
  403. sheet_data_pandas, sheet_data_custom = data_pandas[sheet_name], data_custom[sheet_name]
  404. for (_, row_pandas), row_custom in zip(sheet_data_pandas.iterrows(), sheet_data_custom):
  405. assert isinstance(row_custom, MyCustomObject1)
  406. assert row_pandas["id"] == row_custom.id
  407. assert row_pandas["integer"] == row_custom.integer
  408. assert row_pandas["text"] == row_custom.text
  409. excel_data_node_as_custom_object_no_sheet_name = ExcelDataNode(
  410. "bar",
  411. Scope.SCENARIO,
  412. properties={"path": path, "exposed_type": MyCustomObject1},
  413. )
  414. data_custom_no_sheet_name = excel_data_node_as_custom_object_no_sheet_name.read()
  415. assert isinstance(data_custom_no_sheet_name, Dict)
  416. assert len(data_custom_no_sheet_name) == 2
  417. assert data_custom.keys() == data_custom_no_sheet_name.keys()
  418. for sheet_name in sheet_names:
  419. sheet_data_custom_no_sheet_name, sheet_data_custom = (
  420. data_custom_no_sheet_name[sheet_name],
  421. data_custom[sheet_name],
  422. )
  423. for row_custom_no_sheet_name, row_custom in zip(sheet_data_custom_no_sheet_name, sheet_data_custom):
  424. assert isinstance(row_custom_no_sheet_name, MyCustomObject1)
  425. assert row_custom_no_sheet_name.id == row_custom.id
  426. assert row_custom_no_sheet_name.integer == row_custom.integer
  427. assert row_custom_no_sheet_name.text == row_custom.text
  428. with pytest.raises(ExposedTypeLengthMismatch):
  429. dn = ExcelDataNode(
  430. "bar",
  431. Scope.SCENARIO,
  432. properties={
  433. "path": path,
  434. "sheet_name": ["Sheet1"],
  435. "exposed_type": [MyCustomObject1, MyCustomObject2],
  436. },
  437. )
  438. dn.read()
  439. custom_class_dict = {"Sheet1": MyCustomObject1, "Sheet2": MyCustomObject2}
  440. excel_data_node_as_multi_custom_object = ExcelDataNode(
  441. "bar",
  442. Scope.SCENARIO,
  443. properties={"path": path, "sheet_name": sheet_names, "exposed_type": custom_class_dict},
  444. )
  445. assert excel_data_node_as_multi_custom_object.properties["exposed_type"] == custom_class_dict
  446. excel_data_node_as_multi_custom_object = ExcelDataNode(
  447. "bar",
  448. Scope.SCENARIO,
  449. properties={"path": path, "sheet_name": sheet_names, "exposed_type": [MyCustomObject1, MyCustomObject2]},
  450. )
  451. assert excel_data_node_as_multi_custom_object.properties["exposed_type"] == [MyCustomObject1, MyCustomObject2]
  452. multi_data_custom = excel_data_node_as_multi_custom_object.read()
  453. assert isinstance(multi_data_custom, Dict)
  454. assert len(multi_data_custom) == 2
  455. assert all(len(multi_data_custom[sheet_name]) == 5 for sheet_name in sheet_names)
  456. assert list(multi_data_custom.keys()) == sheet_names
  457. for sheet_name, custom_class in custom_class_dict.items():
  458. sheet_data_pandas, sheet_data_custom = data_pandas[sheet_name], multi_data_custom[sheet_name]
  459. for (_, row_pandas), row_custom in zip(sheet_data_pandas.iterrows(), sheet_data_custom):
  460. assert isinstance(row_custom, custom_class)
  461. assert row_pandas["id"] == row_custom.id
  462. assert row_pandas["integer"] == row_custom.integer
  463. assert row_pandas["text"] == row_custom.text
  464. excel_data_node_as_multi_custom_object_no_sheet_name = ExcelDataNode(
  465. "bar",
  466. Scope.SCENARIO,
  467. properties={"path": path, "exposed_type": custom_class_dict},
  468. )
  469. assert excel_data_node_as_multi_custom_object_no_sheet_name.properties["exposed_type"] == custom_class_dict
  470. multi_data_custom_no_sheet_name = excel_data_node_as_multi_custom_object_no_sheet_name.read()
  471. assert isinstance(multi_data_custom_no_sheet_name, Dict)
  472. assert len(multi_data_custom_no_sheet_name) == 2
  473. assert multi_data_custom.keys() == multi_data_custom_no_sheet_name.keys()
  474. for sheet_name, custom_class in custom_class_dict.items():
  475. sheet_data_custom_no_sheet_name, sheet_data_custom = (
  476. multi_data_custom_no_sheet_name[sheet_name],
  477. multi_data_custom[sheet_name],
  478. )
  479. for row_custom_no_sheet_name, row_custom in zip(sheet_data_custom_no_sheet_name, sheet_data_custom):
  480. assert isinstance(row_custom_no_sheet_name, custom_class)
  481. assert row_custom_no_sheet_name.id == row_custom.id
  482. assert row_custom_no_sheet_name.integer == row_custom.integer
  483. assert row_custom_no_sheet_name.text == row_custom.text
  484. def test_read_multi_sheet_without_header(self):
  485. not_existing_excel = ExcelDataNode(
  486. "foo",
  487. Scope.SCENARIO,
  488. properties={"path": "WRONG.xlsx", "has_header": False, "sheet_name": ["sheet_name_1", "sheet_name_2"]},
  489. )
  490. with pytest.raises(NoData):
  491. assert not_existing_excel.read() is None
  492. not_existing_excel.read_or_raise()
  493. path = os.path.join(pathlib.Path(__file__).parent.resolve(), "data_sample/example.xlsx")
  494. sheet_names = ["Sheet1", "Sheet2"]
  495. # Create ExcelDataNode without exposed_type (Default is pandas.DataFrame)
  496. excel_data_node_as_pandas = ExcelDataNode(
  497. "bar", Scope.SCENARIO, properties={"path": path, "has_header": False, "sheet_name": sheet_names}
  498. )
  499. data_pandas = excel_data_node_as_pandas.read()
  500. assert isinstance(data_pandas, Dict)
  501. assert len(data_pandas) == 2
  502. assert all(len(data_pandas[sheet_name]) == 6 for sheet_name in sheet_names)
  503. assert list(data_pandas.keys()) == sheet_names
  504. for sheet_name in sheet_names:
  505. assert isinstance(data_pandas[sheet_name], pd.DataFrame)
  506. assert data_pandas[sheet_name].equals(pd.read_excel(path, header=None, sheet_name=sheet_name))
  507. excel_data_node_as_pandas_no_sheet_name = ExcelDataNode(
  508. "bar", Scope.SCENARIO, properties={"path": path, "has_header": False}
  509. )
  510. data_pandas_no_sheet_name = excel_data_node_as_pandas_no_sheet_name.read()
  511. assert isinstance(data_pandas_no_sheet_name, Dict)
  512. for key in data_pandas_no_sheet_name.keys():
  513. assert isinstance(data_pandas_no_sheet_name[key], pd.DataFrame)
  514. assert data_pandas[key].equals(data_pandas_no_sheet_name[key])
  515. # Create ExcelDataNode with numpy exposed_type
  516. excel_data_node_as_numpy = ExcelDataNode(
  517. "bar",
  518. Scope.SCENARIO,
  519. properties={"path": path, "has_header": False, "sheet_name": sheet_names, "exposed_type": "numpy"},
  520. )
  521. data_numpy = excel_data_node_as_numpy.read()
  522. assert isinstance(data_numpy, Dict)
  523. assert len(data_numpy) == 2
  524. assert all(
  525. len(data_numpy[sheet_name] == 6) and isinstance(data_numpy[sheet_name], np.ndarray)
  526. for sheet_name in sheet_names
  527. )
  528. assert list(data_numpy.keys()) == sheet_names
  529. for sheet_name in sheet_names:
  530. assert np.array_equal(
  531. data_pandas[sheet_name], pd.read_excel(path, header=None, sheet_name=sheet_name).to_numpy()
  532. )
  533. excel_data_node_as_numpy_no_sheet_name = ExcelDataNode(
  534. "bar",
  535. Scope.SCENARIO,
  536. properties={"path": path, "has_header": False, "exposed_type": "numpy"},
  537. )
  538. data_numpy_no_sheet_name = excel_data_node_as_numpy_no_sheet_name.read()
  539. assert isinstance(data_numpy_no_sheet_name, Dict)
  540. for key in data_numpy_no_sheet_name.keys():
  541. assert isinstance(data_numpy_no_sheet_name[key], np.ndarray)
  542. assert np.array_equal(data_numpy[key], data_numpy_no_sheet_name[key])
  543. # Create the same ExcelDataNode but with custom exposed_type
  544. non_existing_sheet_name_custom = ExcelDataNode(
  545. "bar",
  546. Scope.SCENARIO,
  547. properties={
  548. "path": path,
  549. "has_header": False,
  550. "sheet_name": ["Sheet1", "xyz"],
  551. "exposed_type": MyCustomObject1,
  552. },
  553. )
  554. with pytest.raises(NonExistingExcelSheet):
  555. non_existing_sheet_name_custom.read()
  556. excel_data_node_as_custom_object = ExcelDataNode(
  557. "bar",
  558. Scope.SCENARIO,
  559. properties={
  560. "path": path,
  561. "has_header": False,
  562. "sheet_name": sheet_names,
  563. "exposed_type": MyCustomObject1,
  564. },
  565. )
  566. data_custom = excel_data_node_as_custom_object.read()
  567. assert excel_data_node_as_custom_object.exposed_type == MyCustomObject1
  568. assert isinstance(data_custom, Dict)
  569. assert len(data_custom) == 2
  570. assert all(len(data_custom[sheet_name]) == 6 for sheet_name in sheet_names)
  571. assert list(data_custom.keys()) == sheet_names
  572. for sheet_name in sheet_names:
  573. sheet_data_pandas, sheet_data_custom = data_pandas[sheet_name], data_custom[sheet_name]
  574. for (_, row_pandas), row_custom in zip(sheet_data_pandas.iterrows(), sheet_data_custom):
  575. assert isinstance(row_custom, MyCustomObject1)
  576. assert row_pandas[0] == row_custom.id
  577. assert row_pandas[1] == row_custom.integer
  578. assert row_pandas[2] == row_custom.text
  579. excel_data_node_as_custom_object_no_sheet_name = ExcelDataNode(
  580. "bar",
  581. Scope.SCENARIO,
  582. properties={"path": path, "has_header": False, "exposed_type": MyCustomObject1},
  583. )
  584. data_custom_no_sheet_name = excel_data_node_as_custom_object_no_sheet_name.read()
  585. assert isinstance(data_custom_no_sheet_name, Dict)
  586. assert len(data_custom_no_sheet_name) == 2
  587. assert data_custom.keys() == data_custom_no_sheet_name.keys()
  588. for sheet_name in sheet_names:
  589. sheet_data_custom_no_sheet_name, sheet_data_custom = (
  590. data_custom_no_sheet_name[sheet_name],
  591. data_custom[sheet_name],
  592. )
  593. for row_custom_no_sheet_name, row_custom in zip(sheet_data_custom_no_sheet_name, sheet_data_custom):
  594. assert isinstance(row_custom_no_sheet_name, MyCustomObject1)
  595. assert row_custom_no_sheet_name.id == row_custom.id
  596. assert row_custom_no_sheet_name.integer == row_custom.integer
  597. assert row_custom_no_sheet_name.text == row_custom.text
  598. with pytest.raises(ExposedTypeLengthMismatch):
  599. dn = ExcelDataNode(
  600. "bar",
  601. Scope.SCENARIO,
  602. properties={
  603. "path": path,
  604. "sheet_name": ["Sheet1"],
  605. "exposed_type": [MyCustomObject1, MyCustomObject2],
  606. "has_header": False,
  607. },
  608. )
  609. dn.read()
  610. custom_class_dict = {"Sheet1": MyCustomObject1, "Sheet2": MyCustomObject2}
  611. excel_data_node_as_multi_custom_object = ExcelDataNode(
  612. "bar",
  613. Scope.SCENARIO,
  614. properties={
  615. "path": path,
  616. "sheet_name": sheet_names,
  617. "exposed_type": custom_class_dict,
  618. "has_header": False,
  619. },
  620. )
  621. assert excel_data_node_as_multi_custom_object.properties["exposed_type"] == custom_class_dict
  622. excel_data_node_as_multi_custom_object = ExcelDataNode(
  623. "bar",
  624. Scope.SCENARIO,
  625. properties={
  626. "path": path,
  627. "sheet_name": sheet_names,
  628. "exposed_type": [MyCustomObject1, MyCustomObject2],
  629. "has_header": False,
  630. },
  631. )
  632. assert excel_data_node_as_multi_custom_object.properties["exposed_type"] == [MyCustomObject1, MyCustomObject2]
  633. multi_data_custom = excel_data_node_as_multi_custom_object.read()
  634. assert isinstance(multi_data_custom, Dict)
  635. assert len(multi_data_custom) == 2
  636. assert all(len(multi_data_custom[sheet_name]) == 6 for sheet_name in sheet_names)
  637. assert list(multi_data_custom.keys()) == sheet_names
  638. for sheet_name, custom_class in custom_class_dict.items():
  639. sheet_data_pandas, sheet_data_custom = data_pandas[sheet_name], multi_data_custom[sheet_name]
  640. for (_, row_pandas), row_custom in zip(sheet_data_pandas.iterrows(), sheet_data_custom):
  641. assert isinstance(row_custom, custom_class)
  642. assert row_pandas[0] == row_custom.id
  643. assert row_pandas[1] == row_custom.integer
  644. assert row_pandas[2] == row_custom.text
  645. excel_data_node_as_multi_custom_object_no_sheet_name = ExcelDataNode(
  646. "bar",
  647. Scope.SCENARIO,
  648. properties={"path": path, "has_header": False, "exposed_type": custom_class_dict},
  649. )
  650. multi_data_custom_no_sheet_name = excel_data_node_as_multi_custom_object_no_sheet_name.read()
  651. assert isinstance(multi_data_custom_no_sheet_name, Dict)
  652. assert len(multi_data_custom_no_sheet_name) == 2
  653. assert multi_data_custom.keys() == multi_data_custom_no_sheet_name.keys()
  654. for sheet_name, custom_class in custom_class_dict.items():
  655. sheet_data_custom_no_sheet_name, sheet_data_custom = (
  656. multi_data_custom_no_sheet_name[sheet_name],
  657. multi_data_custom[sheet_name],
  658. )
  659. for row_custom_no_sheet_name, row_custom in zip(sheet_data_custom_no_sheet_name, sheet_data_custom):
  660. assert isinstance(row_custom_no_sheet_name, custom_class)
  661. assert row_custom_no_sheet_name.id == row_custom.id
  662. assert row_custom_no_sheet_name.integer == row_custom.integer
  663. assert row_custom_no_sheet_name.text == row_custom.text
  664. @pytest.mark.parametrize(
  665. "content,columns",
  666. [
  667. ([{"a": 11, "b": 22, "c": 33}, {"a": 44, "b": 55, "c": 66}], None),
  668. ([[11, 22, 33], [44, 55, 66]], None),
  669. ([[11, 22, 33], [44, 55, 66]], ["e", "f", "g"]),
  670. ],
  671. )
  672. def test_write_multi_sheet(self, excel_file_with_multi_sheet, default_multi_sheet_data_frame, content, columns):
  673. sheet_names = ["Sheet1", "Sheet2"]
  674. excel_dn = ExcelDataNode(
  675. "foo",
  676. Scope.SCENARIO,
  677. properties={"path": excel_file_with_multi_sheet, "sheet_name": sheet_names},
  678. )
  679. for sheet_name in sheet_names:
  680. assert np.array_equal(excel_dn.read()[sheet_name].values, default_multi_sheet_data_frame[sheet_name].values)
  681. multi_sheet_content = {sheet_name: pd.DataFrame(content) for sheet_name in sheet_names}
  682. excel_dn.write(multi_sheet_content)
  683. for sheet_name in sheet_names:
  684. assert np.array_equal(excel_dn.read()[sheet_name].values, multi_sheet_content[sheet_name].values)
  685. def test_write_multi_sheet_numpy(self, excel_file_with_multi_sheet):
  686. sheet_names = ["Sheet1", "Sheet2"]
  687. excel_dn = ExcelDataNode(
  688. "foo",
  689. Scope.SCENARIO,
  690. properties={"path": excel_file_with_multi_sheet, "sheet_name": sheet_names, "exposed_type": "numpy"},
  691. )
  692. sheets_data = [[11, 22, 33], [44, 55, 66]]
  693. data = {
  694. sheet_name: pd.DataFrame(sheet_data).to_numpy() for sheet_name, sheet_data in zip(sheet_names, sheets_data)
  695. }
  696. excel_dn.write(data)
  697. read_data = excel_dn.read()
  698. assert all(np.array_equal(data[sheet_name], read_data[sheet_name]) for sheet_name in sheet_names)
  699. @pytest.mark.parametrize(
  700. "content",
  701. [
  702. ([{"a": 11, "b": 22, "c": 33}, {"a": 44, "b": 55, "c": 66}]),
  703. (pd.DataFrame([{"a": 11, "b": 22, "c": 33}, {"a": 44, "b": 55, "c": 66}])),
  704. ([[11, 22, 33], [44, 55, 66]]),
  705. ],
  706. )
  707. def test_append_pandas_with_sheetname(self, excel_file, default_data_frame, content):
  708. dn = ExcelDataNode("foo", Scope.SCENARIO, properties={"path": excel_file, "sheet_name": "Sheet1"})
  709. assert_frame_equal(dn.read(), default_data_frame)
  710. dn.append(content)
  711. assert_frame_equal(
  712. dn.read(),
  713. pd.concat([default_data_frame, pd.DataFrame(content, columns=["a", "b", "c"])]).reset_index(drop=True),
  714. )
  715. @pytest.mark.parametrize(
  716. "content",
  717. [
  718. ([{"a": 11, "b": 22, "c": 33}, {"a": 44, "b": 55, "c": 66}]),
  719. (pd.DataFrame([{"a": 11, "b": 22, "c": 33}, {"a": 44, "b": 55, "c": 66}])),
  720. ([[11, 22, 33], [44, 55, 66]]),
  721. ],
  722. )
  723. def test_append_pandas_without_sheetname(self, excel_file, default_data_frame, content):
  724. dn = ExcelDataNode("foo", Scope.SCENARIO, properties={"path": excel_file})
  725. assert_frame_equal(dn.read()["Sheet1"], default_data_frame)
  726. dn.append(content)
  727. assert_frame_equal(
  728. dn.read()["Sheet1"],
  729. pd.concat([default_data_frame, pd.DataFrame(content, columns=["a", "b", "c"])]).reset_index(drop=True),
  730. )
  731. @pytest.mark.parametrize(
  732. "content",
  733. [
  734. (
  735. {
  736. "Sheet1": pd.DataFrame([{"a": 11, "b": 22, "c": 33}]),
  737. "Sheet2": pd.DataFrame([{"a": 44, "b": 55, "c": 66}]),
  738. }
  739. ),
  740. (
  741. {
  742. "Sheet1": pd.DataFrame({"a": [11, 44], "b": [22, 55], "c": [33, 66]}),
  743. "Sheet2": pd.DataFrame([{"a": 77, "b": 88, "c": 99}]),
  744. }
  745. ),
  746. ({"Sheet1": np.array([[11, 22, 33], [44, 55, 66]]), "Sheet2": np.array([[77, 88, 99]])}),
  747. ],
  748. )
  749. def test_append_pandas_multisheet(self, excel_file_with_multi_sheet, default_multi_sheet_data_frame, content):
  750. dn = ExcelDataNode(
  751. "foo", Scope.SCENARIO, properties={"path": excel_file_with_multi_sheet, "sheet_name": ["Sheet1", "Sheet2"]}
  752. )
  753. assert_frame_equal(dn.read()["Sheet1"], default_multi_sheet_data_frame["Sheet1"])
  754. assert_frame_equal(dn.read()["Sheet2"], default_multi_sheet_data_frame["Sheet2"])
  755. dn.append(content)
  756. assert_frame_equal(
  757. dn.read()["Sheet1"],
  758. pd.concat(
  759. [default_multi_sheet_data_frame["Sheet1"], pd.DataFrame(content["Sheet1"], columns=["a", "b", "c"])]
  760. ).reset_index(drop=True),
  761. )
  762. assert_frame_equal(
  763. dn.read()["Sheet2"],
  764. pd.concat(
  765. [default_multi_sheet_data_frame["Sheet2"], pd.DataFrame(content["Sheet2"], columns=["a", "b", "c"])]
  766. ).reset_index(drop=True),
  767. )
  768. @pytest.mark.parametrize(
  769. "content",
  770. [
  771. ({"Sheet1": pd.DataFrame([{"a": 11, "b": 22, "c": 33}])}),
  772. (pd.DataFrame({"a": [11, 44], "b": [22, 55], "c": [33, 66]})),
  773. ([[11, 22, 33], [44, 55, 66]]),
  774. ],
  775. )
  776. def test_append_only_first_sheet_of_a_multisheet_file(
  777. self, excel_file_with_multi_sheet, default_multi_sheet_data_frame, content
  778. ):
  779. dn = ExcelDataNode(
  780. "foo", Scope.SCENARIO, properties={"path": excel_file_with_multi_sheet, "sheet_name": ["Sheet1", "Sheet2"]}
  781. )
  782. assert_frame_equal(dn.read()["Sheet1"], default_multi_sheet_data_frame["Sheet1"])
  783. assert_frame_equal(dn.read()["Sheet2"], default_multi_sheet_data_frame["Sheet2"])
  784. dn.append(content)
  785. appended_content = content["Sheet1"] if isinstance(content, dict) else content
  786. assert_frame_equal(
  787. dn.read()["Sheet1"],
  788. pd.concat(
  789. [default_multi_sheet_data_frame["Sheet1"], pd.DataFrame(appended_content, columns=["a", "b", "c"])]
  790. ).reset_index(drop=True),
  791. )
  792. assert_frame_equal(dn.read()["Sheet2"], default_multi_sheet_data_frame["Sheet2"])
  793. def test_filter_pandas_exposed_type_with_sheetname(self, excel_file):
  794. dn = ExcelDataNode(
  795. "foo", Scope.SCENARIO, properties={"path": excel_file, "sheet_name": "Sheet1", "exposed_type": "pandas"}
  796. )
  797. dn.write(
  798. [
  799. {"foo": 1, "bar": 1},
  800. {"foo": 1, "bar": 2},
  801. {"foo": 1},
  802. {"foo": 2, "bar": 2},
  803. {"bar": 2},
  804. ]
  805. )
  806. # Test datanode indexing and slicing
  807. assert dn["foo"].equals(pd.Series([1, 1, 1, 2, None]))
  808. assert dn["bar"].equals(pd.Series([1, 2, None, 2, 2]))
  809. assert dn[:2].equals(pd.DataFrame([{"foo": 1.0, "bar": 1.0}, {"foo": 1.0, "bar": 2.0}]))
  810. # Test filter data
  811. filtered_by_filter_method = dn.filter(("foo", 1, Operator.EQUAL))
  812. filtered_by_indexing = dn[dn["foo"] == 1]
  813. expected_data = pd.DataFrame([{"foo": 1.0, "bar": 1.0}, {"foo": 1.0, "bar": 2.0}, {"foo": 1.0}])
  814. assert_frame_equal(filtered_by_filter_method.reset_index(drop=True), expected_data)
  815. assert_frame_equal(filtered_by_indexing.reset_index(drop=True), expected_data)
  816. filtered_by_filter_method = dn.filter(("foo", 1, Operator.NOT_EQUAL))
  817. filtered_by_indexing = dn[dn["foo"] != 1]
  818. expected_data = pd.DataFrame([{"foo": 2.0, "bar": 2.0}, {"bar": 2.0}])
  819. assert_frame_equal(filtered_by_filter_method.reset_index(drop=True), expected_data)
  820. assert_frame_equal(filtered_by_indexing.reset_index(drop=True), expected_data)
  821. filtered_by_filter_method = dn.filter(("bar", 2, Operator.EQUAL))
  822. filtered_by_indexing = dn[dn["bar"] == 2]
  823. expected_data = pd.DataFrame([{"foo": 1.0, "bar": 2.0}, {"foo": 2.0, "bar": 2.0}, {"bar": 2.0}])
  824. assert_frame_equal(filtered_by_filter_method.reset_index(drop=True), expected_data)
  825. assert_frame_equal(filtered_by_indexing.reset_index(drop=True), expected_data)
  826. filtered_by_filter_method = dn.filter([("bar", 1, Operator.EQUAL), ("bar", 2, Operator.EQUAL)], JoinOperator.OR)
  827. filtered_by_indexing = dn[(dn["bar"] == 1) | (dn["bar"] == 2)]
  828. expected_data = pd.DataFrame(
  829. [
  830. {"foo": 1.0, "bar": 1.0},
  831. {"foo": 1.0, "bar": 2.0},
  832. {"foo": 2.0, "bar": 2.0},
  833. {"bar": 2.0},
  834. ]
  835. )
  836. assert_frame_equal(filtered_by_filter_method.reset_index(drop=True), expected_data)
  837. assert_frame_equal(filtered_by_indexing.reset_index(drop=True), expected_data)
  838. def test_filter_pandas_exposed_type_without_sheetname(self, excel_file):
  839. dn = ExcelDataNode("foo", Scope.SCENARIO, properties={"path": excel_file, "exposed_type": "pandas"})
  840. dn.write(
  841. [
  842. {"foo": 1, "bar": 1},
  843. {"foo": 1, "bar": 2},
  844. {"foo": 1},
  845. {"foo": 2, "bar": 2},
  846. {"bar": 2},
  847. ]
  848. )
  849. assert len(dn.filter(("foo", 1, Operator.EQUAL))["Sheet1"]) == 3
  850. assert len(dn.filter(("foo", 1, Operator.NOT_EQUAL))["Sheet1"]) == 2
  851. assert len(dn.filter(("bar", 2, Operator.EQUAL))["Sheet1"]) == 3
  852. assert len(dn.filter([("bar", 1, Operator.EQUAL), ("bar", 2, Operator.EQUAL)], JoinOperator.OR)["Sheet1"]) == 4
  853. assert dn["Sheet1"]["foo"].equals(pd.Series([1, 1, 1, 2, None]))
  854. assert dn["Sheet1"]["bar"].equals(pd.Series([1, 2, None, 2, 2]))
  855. assert dn["Sheet1"][:2].equals(pd.DataFrame([{"foo": 1.0, "bar": 1.0}, {"foo": 1.0, "bar": 2.0}]))
  856. def test_filter_pandas_exposed_type_multisheet(self, excel_file):
  857. dn = ExcelDataNode(
  858. "foo",
  859. Scope.SCENARIO,
  860. properties={"path": excel_file, "sheet_name": ["sheet_1", "sheet_2"], "exposed_type": "pandas"},
  861. )
  862. dn.write(
  863. {
  864. "sheet_1": pd.DataFrame(
  865. [
  866. {"foo": 1, "bar": 1},
  867. {"foo": 1, "bar": 2},
  868. {"foo": 1},
  869. {"foo": 2, "bar": 2},
  870. {"bar": 2},
  871. ]
  872. ),
  873. "sheet_2": pd.DataFrame(
  874. [
  875. {"foo": 1, "bar": 3},
  876. {"foo": 1, "bar": 4},
  877. {"foo": 1},
  878. {"foo": 2, "bar": 4},
  879. {"bar": 4},
  880. ]
  881. ),
  882. }
  883. )
  884. assert len(dn.filter(("foo", 1, Operator.EQUAL))) == 2
  885. assert len(dn.filter(("foo", 1, Operator.EQUAL))["sheet_1"]) == 3
  886. assert len(dn.filter(("foo", 1, Operator.EQUAL))["sheet_2"]) == 3
  887. assert len(dn.filter(("foo", 1, Operator.NOT_EQUAL))) == 2
  888. assert len(dn.filter(("foo", 1, Operator.NOT_EQUAL))["sheet_1"]) == 2
  889. assert len(dn.filter(("foo", 1, Operator.NOT_EQUAL))["sheet_2"]) == 2
  890. assert len(dn.filter(("bar", 2, Operator.EQUAL))) == 2
  891. assert len(dn.filter(("bar", 2, Operator.EQUAL))["sheet_1"]) == 3
  892. assert len(dn.filter(("bar", 2, Operator.EQUAL))["sheet_2"]) == 0
  893. assert len(dn.filter([("bar", 1, Operator.EQUAL), ("bar", 2, Operator.EQUAL)], JoinOperator.OR)) == 2
  894. assert len(dn.filter([("bar", 1, Operator.EQUAL), ("bar", 2, Operator.EQUAL)], JoinOperator.OR)["sheet_1"]) == 4
  895. assert len(dn.filter([("bar", 1, Operator.EQUAL), ("bar", 2, Operator.EQUAL)], JoinOperator.OR)["sheet_2"]) == 0
  896. assert dn["sheet_1"]["foo"].equals(pd.Series([1, 1, 1, 2, None]))
  897. assert dn["sheet_2"]["foo"].equals(pd.Series([1, 1, 1, 2, None]))
  898. assert dn["sheet_1"]["bar"].equals(pd.Series([1, 2, None, 2, 2]))
  899. assert dn["sheet_2"]["bar"].equals(pd.Series([3, 4, None, 4, 4]))
  900. assert dn["sheet_1"][:2].equals(pd.DataFrame([{"foo": 1.0, "bar": 1.0}, {"foo": 1.0, "bar": 2.0}]))
  901. assert dn["sheet_2"][:2].equals(pd.DataFrame([{"foo": 1.0, "bar": 3.0}, {"foo": 1.0, "bar": 4.0}]))
  902. def test_filter_numpy_exposed_type_with_sheetname(self, excel_file):
  903. dn = ExcelDataNode(
  904. "foo", Scope.SCENARIO, properties={"path": excel_file, "sheet_name": "Sheet1", "exposed_type": "numpy"}
  905. )
  906. dn.write(
  907. [
  908. [1, 1],
  909. [1, 2],
  910. [1, 3],
  911. [2, 1],
  912. [2, 2],
  913. [2, 3],
  914. ]
  915. )
  916. # Test datanode indexing and slicing
  917. assert np.array_equal(dn[0], np.array([1, 1]))
  918. assert np.array_equal(dn[1], np.array([1, 2]))
  919. assert np.array_equal(dn[:3], np.array([[1, 1], [1, 2], [1, 3]]))
  920. assert np.array_equal(dn[:, 0], np.array([1, 1, 1, 2, 2, 2]))
  921. assert np.array_equal(dn[1:4, :1], np.array([[1], [1], [2]]))
  922. # Test filter data
  923. assert np.array_equal(dn.filter((0, 1, Operator.EQUAL)), np.array([[1, 1], [1, 2], [1, 3]]))
  924. assert np.array_equal(dn[dn[:, 0] == 1], np.array([[1, 1], [1, 2], [1, 3]]))
  925. assert np.array_equal(dn.filter((0, 1, Operator.NOT_EQUAL)), np.array([[2, 1], [2, 2], [2, 3]]))
  926. assert np.array_equal(dn[dn[:, 0] != 1], np.array([[2, 1], [2, 2], [2, 3]]))
  927. assert np.array_equal(dn.filter((1, 2, Operator.EQUAL)), np.array([[1, 2], [2, 2]]))
  928. assert np.array_equal(dn[dn[:, 1] == 2], np.array([[1, 2], [2, 2]]))
  929. assert np.array_equal(
  930. dn.filter([(1, 1, Operator.EQUAL), (1, 2, Operator.EQUAL)], JoinOperator.OR),
  931. np.array([[1, 1], [1, 2], [2, 1], [2, 2]]),
  932. )
  933. assert np.array_equal(dn[(dn[:, 1] == 1) | (dn[:, 1] == 2)], np.array([[1, 1], [1, 2], [2, 1], [2, 2]]))
  934. def test_filter_numpy_exposed_type_without_sheetname(self, excel_file):
  935. dn = ExcelDataNode("foo", Scope.SCENARIO, properties={"path": excel_file, "exposed_type": "numpy"})
  936. dn.write(
  937. [
  938. [1, 1],
  939. [1, 2],
  940. [1, 3],
  941. [2, 1],
  942. [2, 2],
  943. [2, 3],
  944. ]
  945. )
  946. assert len(dn.filter((0, 1, Operator.EQUAL))["Sheet1"]) == 3
  947. assert len(dn.filter((0, 1, Operator.NOT_EQUAL))["Sheet1"]) == 3
  948. assert len(dn.filter((1, 2, Operator.EQUAL))["Sheet1"]) == 2
  949. assert len(dn.filter([(0, 1, Operator.EQUAL), (1, 2, Operator.EQUAL)], JoinOperator.OR)["Sheet1"]) == 4
  950. assert np.array_equal(dn["Sheet1"][0], np.array([1, 1]))
  951. assert np.array_equal(dn["Sheet1"][1], np.array([1, 2]))
  952. assert np.array_equal(dn["Sheet1"][:3], np.array([[1, 1], [1, 2], [1, 3]]))
  953. assert np.array_equal(dn["Sheet1"][:, 0], np.array([1, 1, 1, 2, 2, 2]))
  954. assert np.array_equal(dn["Sheet1"][1:4, :1], np.array([[1], [1], [2]]))
  955. def test_filter_numpy_exposed_type_multisheet(self, excel_file):
  956. dn = ExcelDataNode(
  957. "foo",
  958. Scope.SCENARIO,
  959. properties={"path": excel_file, "sheet_name": ["sheet_1", "sheet_2"], "exposed_type": "numpy"},
  960. )
  961. dn.write(
  962. {
  963. "sheet_1": pd.DataFrame(
  964. [
  965. [1, 1],
  966. [1, 2],
  967. [1, 3],
  968. [2, 1],
  969. [2, 2],
  970. [2, 3],
  971. ]
  972. ),
  973. "sheet_2": pd.DataFrame(
  974. [
  975. [1, 4],
  976. [1, 5],
  977. [1, 6],
  978. [2, 4],
  979. [2, 5],
  980. [2, 6],
  981. ]
  982. ),
  983. }
  984. )
  985. assert len(dn.filter((0, 1, Operator.EQUAL))) == 2
  986. assert len(dn.filter((0, 1, Operator.EQUAL))["sheet_1"]) == 3
  987. assert len(dn.filter((0, 1, Operator.EQUAL))["sheet_2"]) == 3
  988. assert len(dn.filter((0, 1, Operator.NOT_EQUAL))) == 2
  989. assert len(dn.filter((0, 1, Operator.NOT_EQUAL))["sheet_1"]) == 3
  990. assert len(dn.filter((0, 1, Operator.NOT_EQUAL))["sheet_2"]) == 3
  991. assert len(dn.filter((1, 2, Operator.EQUAL))) == 2
  992. assert len(dn.filter((1, 2, Operator.EQUAL))["sheet_1"]) == 2
  993. assert len(dn.filter((1, 2, Operator.EQUAL))["sheet_2"]) == 0
  994. assert len(dn.filter([(1, 1, Operator.EQUAL), (1, 2, Operator.EQUAL)], JoinOperator.OR)) == 2
  995. assert len(dn.filter([(1, 1, Operator.EQUAL), (1, 2, Operator.EQUAL)], JoinOperator.OR)["sheet_1"]) == 4
  996. assert len(dn.filter([(1, 1, Operator.EQUAL), (1, 2, Operator.EQUAL)], JoinOperator.OR)["sheet_2"]) == 0
  997. assert np.array_equal(dn["sheet_1"][0], np.array([1, 1]))
  998. assert np.array_equal(dn["sheet_2"][0], np.array([1, 4]))
  999. assert np.array_equal(dn["sheet_1"][1], np.array([1, 2]))
  1000. assert np.array_equal(dn["sheet_2"][1], np.array([1, 5]))
  1001. assert np.array_equal(dn["sheet_1"][:3], np.array([[1, 1], [1, 2], [1, 3]]))
  1002. assert np.array_equal(dn["sheet_2"][:3], np.array([[1, 4], [1, 5], [1, 6]]))
  1003. assert np.array_equal(dn["sheet_1"][:, 0], np.array([1, 1, 1, 2, 2, 2]))
  1004. assert np.array_equal(dn["sheet_2"][:, 1], np.array([4, 5, 6, 4, 5, 6]))
  1005. assert np.array_equal(dn["sheet_1"][1:4, :1], np.array([[1], [1], [2]]))
  1006. assert np.array_equal(dn["sheet_2"][1:4, 1:2], np.array([[5], [6], [4]]))
  1007. def test_set_path(self):
  1008. dn = ExcelDataNode("foo", Scope.SCENARIO, properties={"default_path": "foo.xlsx"})
  1009. assert dn.path == "foo.xlsx"
  1010. dn.path = "bar.xlsx"
  1011. assert dn.path == "bar.xlsx"
  1012. @pytest.mark.parametrize(
  1013. ["properties", "exists"],
  1014. [
  1015. ({}, False),
  1016. ({"default_data": {"a": ["foo", "bar"]}}, True),
  1017. ],
  1018. )
  1019. def test_create_with_default_data(self, properties, exists):
  1020. dn = ExcelDataNode("foo", Scope.SCENARIO, DataNodeId("dn_id"), properties=properties)
  1021. assert os.path.exists(dn.path) is exists
  1022. def test_read_write_after_modify_path(self):
  1023. path = os.path.join(pathlib.Path(__file__).parent.resolve(), "data_sample/example.xlsx")
  1024. new_path = os.path.join(pathlib.Path(__file__).parent.resolve(), "data_sample/temp.xlsx")
  1025. dn = ExcelDataNode("foo", Scope.SCENARIO, properties={"default_path": path})
  1026. read_data = dn.read()
  1027. assert read_data is not None
  1028. dn.path = new_path
  1029. with pytest.raises(FileNotFoundError):
  1030. dn.read()
  1031. dn.write(read_data)
  1032. for sheet, df in dn.read().items():
  1033. assert np.array_equal(df.values, read_data[sheet].values)
  1034. def test_exposed_type_custom_class_after_modify_path(self):
  1035. path = os.path.join(pathlib.Path(__file__).parent.resolve(), "data_sample/example.xlsx") # ["Sheet1", "Sheet2"]
  1036. new_path = os.path.join(
  1037. pathlib.Path(__file__).parent.resolve(), "data_sample/example_2.xlsx"
  1038. ) # ["Sheet1", "Sheet2", "Sheet3"]
  1039. dn = ExcelDataNode("foo", Scope.SCENARIO, properties={"default_path": path, "exposed_type": MyCustomObject1})
  1040. assert dn.exposed_type == MyCustomObject1
  1041. dn.read()
  1042. dn.path = new_path
  1043. dn.read()
  1044. dn = ExcelDataNode(
  1045. "foo",
  1046. Scope.SCENARIO,
  1047. properties={"default_path": path, "exposed_type": MyCustomObject1, "sheet_name": ["Sheet4"]},
  1048. )
  1049. assert dn.exposed_type == MyCustomObject1
  1050. with pytest.raises(NonExistingExcelSheet):
  1051. dn.read()
  1052. def test_exposed_type_dict(self):
  1053. path = os.path.join(pathlib.Path(__file__).parent.resolve(), "data_sample/example.xlsx") # ["Sheet1", "Sheet2"]
  1054. dn = ExcelDataNode(
  1055. "foo",
  1056. Scope.SCENARIO,
  1057. properties={
  1058. "default_path": path,
  1059. "exposed_type": {
  1060. "Sheet1": MyCustomObject1,
  1061. "Sheet2": MyCustomObject2,
  1062. "Sheet3": MyCustomObject1,
  1063. },
  1064. },
  1065. )
  1066. data = dn.read()
  1067. assert isinstance(data, Dict)
  1068. assert isinstance(data["Sheet1"][0], MyCustomObject1)
  1069. assert isinstance(data["Sheet2"][0], MyCustomObject2)
  1070. def test_exposed_type_list(self):
  1071. path_1 = os.path.join(
  1072. pathlib.Path(__file__).parent.resolve(), "data_sample/example.xlsx"
  1073. ) # ["Sheet1", "Sheet2"]
  1074. path_2 = os.path.join(
  1075. pathlib.Path(__file__).parent.resolve(), "data_sample/example_2.xlsx"
  1076. ) # ["Sheet1", "Sheet2", "Sheet3"]
  1077. dn = ExcelDataNode(
  1078. "foo",
  1079. Scope.SCENARIO,
  1080. properties={"default_path": path_1, "exposed_type": [MyCustomObject1, MyCustomObject2]},
  1081. )
  1082. data = dn.read()
  1083. assert isinstance(data, Dict)
  1084. assert isinstance(data["Sheet1"][0], MyCustomObject1)
  1085. assert isinstance(data["Sheet2"][0], MyCustomObject2)
  1086. dn.path = path_2
  1087. with pytest.raises(ExposedTypeLengthMismatch):
  1088. dn.read()
  1089. def test_not_trying_to_read_sheet_names_when_exposed_type_is_set(self):
  1090. dn = ExcelDataNode(
  1091. "foo", Scope.SCENARIO, properties={"default_path": "notexistyet.xlsx", "exposed_type": MyCustomObject1}
  1092. )
  1093. assert dn.path == "notexistyet.xlsx"
  1094. assert dn.exposed_type == MyCustomObject1
  1095. dn = ExcelDataNode(
  1096. "foo",
  1097. Scope.SCENARIO,
  1098. properties={"default_path": "notexistyet.xlsx", "exposed_type": [MyCustomObject1, MyCustomObject2]},
  1099. )
  1100. assert dn.path == "notexistyet.xlsx"
  1101. assert dn.exposed_type == [MyCustomObject1, MyCustomObject2]
  1102. dn = ExcelDataNode(
  1103. "foo",
  1104. Scope.SCENARIO,
  1105. properties={
  1106. "default_path": "notexistyet.xlsx",
  1107. "exposed_type": {"Sheet1": MyCustomObject1, "Sheet2": MyCustomObject2},
  1108. },
  1109. )
  1110. assert dn.path == "notexistyet.xlsx"
  1111. assert dn.exposed_type == {"Sheet1": MyCustomObject1, "Sheet2": MyCustomObject2}
  1112. def test_exposed_type_default(self):
  1113. path = os.path.join(pathlib.Path(__file__).parent.resolve(), "data_sample/example.xlsx")
  1114. dn = ExcelDataNode("foo", Scope.SCENARIO, properties={"default_path": path, "sheet_name": "Sheet1"})
  1115. assert dn.exposed_type == "pandas"
  1116. data = dn.read()
  1117. assert isinstance(data, pd.DataFrame)
  1118. def test_pandas_exposed_type(self):
  1119. path = os.path.join(pathlib.Path(__file__).parent.resolve(), "data_sample/example.xlsx")
  1120. dn = ExcelDataNode(
  1121. "foo", Scope.SCENARIO, properties={"default_path": path, "exposed_type": "pandas", "sheet_name": "Sheet1"}
  1122. )
  1123. assert dn.exposed_type == "pandas"
  1124. data = dn.read()
  1125. assert isinstance(data, pd.DataFrame)
  1126. def test_complex_exposed_type_dict(self):
  1127. # ["Sheet1", "Sheet2", "Sheet3", "Sheet4", "Sheet5"]
  1128. path = os.path.join(pathlib.Path(__file__).parent.resolve(), "data_sample/example_4.xlsx")
  1129. dn = ExcelDataNode(
  1130. "foo",
  1131. Scope.SCENARIO,
  1132. properties={
  1133. "default_path": path,
  1134. "exposed_type": {
  1135. "Sheet1": MyCustomObject1,
  1136. "Sheet2": "numpy",
  1137. "Sheet3": "pandas",
  1138. },
  1139. "sheet_name": ["Sheet1", "Sheet2", "Sheet3", "Sheet4"],
  1140. },
  1141. )
  1142. data = dn.read()
  1143. assert isinstance(data, dict)
  1144. assert isinstance(data["Sheet1"], list)
  1145. assert isinstance(data["Sheet1"][0], MyCustomObject1)
  1146. assert isinstance(data["Sheet2"], np.ndarray)
  1147. assert isinstance(data["Sheet3"], pd.DataFrame)
  1148. assert isinstance(data["Sheet4"], pd.DataFrame)
  1149. assert data.get("Sheet5") is None
  1150. def test_complex_exposed_type_list(self):
  1151. # ["Sheet1", "Sheet2", "Sheet3", "Sheet4","Sheet5"]
  1152. path = os.path.join(pathlib.Path(__file__).parent.resolve(), "data_sample/example_4.xlsx")
  1153. dn = ExcelDataNode(
  1154. "foo",
  1155. Scope.SCENARIO,
  1156. properties={
  1157. "default_path": path,
  1158. "exposed_type": [MyCustomObject1, "numpy", "pandas"],
  1159. "sheet_name": ["Sheet1", "Sheet2", "Sheet3"],
  1160. },
  1161. )
  1162. data = dn.read()
  1163. assert isinstance(data, dict)
  1164. assert isinstance(data["Sheet1"], list)
  1165. assert isinstance(data["Sheet1"][0], MyCustomObject1)
  1166. assert isinstance(data["Sheet2"], np.ndarray)
  1167. assert isinstance(data["Sheet3"], pd.DataFrame)
  1168. def test_invalid_exposed_type(self):
  1169. path = os.path.join(pathlib.Path(__file__).parent.resolve(), "data_sample/example.xlsx")
  1170. with pytest.raises(InvalidExposedType):
  1171. ExcelDataNode(
  1172. "foo",
  1173. Scope.SCENARIO,
  1174. properties={"default_path": path, "exposed_type": "invalid", "sheet_name": "Sheet1"},
  1175. )
  1176. with pytest.raises(InvalidExposedType):
  1177. ExcelDataNode(
  1178. "foo",
  1179. Scope.SCENARIO,
  1180. properties={
  1181. "default_path": path,
  1182. "exposed_type": ["numpy", "invalid", "pandas"],
  1183. "sheet_name": "Sheet1",
  1184. },
  1185. )
  1186. with pytest.raises(InvalidExposedType):
  1187. ExcelDataNode(
  1188. "foo",
  1189. Scope.SCENARIO,
  1190. properties={
  1191. "default_path": path,
  1192. "exposed_type": {"Sheet1": "pandas", "Sheet2": "invalid"},
  1193. "sheet_name": "Sheet1",
  1194. },
  1195. )
  1196. def test_get_system_modified_date_instead_of_last_edit_date(self, tmpdir_factory):
  1197. temp_file_path = str(tmpdir_factory.mktemp("data").join("temp.xlsx"))
  1198. pd.DataFrame([]).to_excel(temp_file_path)
  1199. dn = ExcelDataNode("foo", Scope.SCENARIO, properties={"path": temp_file_path, "exposed_type": "pandas"})
  1200. dn.write(pd.DataFrame([1, 2, 3]))
  1201. previous_edit_date = dn.last_edit_date
  1202. sleep(0.1)
  1203. pd.DataFrame([4, 5, 6]).to_excel(temp_file_path)
  1204. new_edit_date = datetime.fromtimestamp(os.path.getmtime(temp_file_path))
  1205. assert previous_edit_date < dn.last_edit_date
  1206. assert new_edit_date == dn.last_edit_date
  1207. sleep(0.1)
  1208. dn.write(pd.DataFrame([7, 8, 9]))
  1209. assert new_edit_date < dn.last_edit_date
  1210. os.unlink(temp_file_path)