test_data_node.py 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966
  1. # Copyright 2021-2025 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. from datetime import datetime, timedelta
  13. from time import sleep
  14. from unittest import mock
  15. import freezegun
  16. import pandas as pd
  17. import pytest
  18. import taipy.core as tp
  19. from taipy import Scope
  20. from taipy.common.config import Config
  21. from taipy.common.config.exceptions.exceptions import InvalidConfigurationId
  22. from taipy.core.data._data_manager import _DataManager
  23. from taipy.core.data._data_manager_factory import _DataManagerFactory
  24. from taipy.core.data.data_node import DataNode
  25. from taipy.core.data.data_node_id import (
  26. EDIT_COMMENT_KEY,
  27. EDIT_EDITOR_ID_KEY,
  28. EDIT_JOB_ID_KEY,
  29. EDIT_TIMESTAMP_KEY,
  30. DataNodeId,
  31. )
  32. from taipy.core.data.in_memory import InMemoryDataNode
  33. from taipy.core.exceptions.exceptions import DataNodeIsBeingEdited, NoData
  34. from taipy.core.job.job_id import JobId
  35. from taipy.core.task.task import Task
  36. from .utils import FakeDataNode
  37. def funct_a_b(input: str):
  38. print("task_a_b") # noqa: T201
  39. return "B"
  40. def funct_b_c(input: str):
  41. print("task_b_c") # noqa: T201
  42. return "C"
  43. def funct_b_d(input: str):
  44. print("task_b_d") # noqa: T201
  45. return "D"
  46. class TestDataNode:
  47. def test_dn_equals(self, data_node):
  48. data_manager = _DataManagerFactory()._build_manager()
  49. dn_id = data_node.id
  50. data_manager._repository._save(data_node)
  51. # # To test if instance is same type
  52. task = Task("task", {}, print, [], [], dn_id)
  53. dn_2 = data_manager._get(dn_id)
  54. assert data_node == dn_2
  55. assert data_node != dn_id
  56. assert data_node != task
  57. def test_create_with_default_values(self):
  58. dn = DataNode("foo_bar")
  59. assert dn.config_id == "foo_bar"
  60. assert dn.scope == Scope.SCENARIO
  61. assert dn.id is not None
  62. assert dn.name is None
  63. assert dn.owner_id is None
  64. assert dn.parent_ids == set()
  65. assert dn.last_edit_date is None
  66. assert dn.job_ids == []
  67. assert not dn.is_ready_for_reading
  68. assert len(dn.properties) == 0
  69. def test_create_with_ranks(self):
  70. # Test _rank is propagated from the config
  71. cfg = Config.configure_data_node("foo_bar")
  72. cfg._ranks = {"A": 1, "B": 2, "C": 0}
  73. dn = DataNode("foo_bar")
  74. assert dn.config_id == "foo_bar"
  75. assert dn.scope == Scope.SCENARIO
  76. assert dn.id is not None
  77. assert dn.name is None
  78. assert dn.owner_id is None
  79. assert dn.parent_ids == set()
  80. assert dn.last_edit_date is None
  81. assert dn.job_ids == []
  82. assert not dn.is_ready_for_reading
  83. assert len(dn.properties) == 0
  84. assert dn._get_rank("A") == 1
  85. assert dn._get_rank("B") == 2
  86. assert dn._get_rank("C") == 0
  87. def test_is_up_to_date_when_not_written(self):
  88. dn_confg_1 = Config.configure_in_memory_data_node("dn_1", default_data="a")
  89. dn_confg_2 = Config.configure_in_memory_data_node("dn_2")
  90. task_config_1 = Config.configure_task("t1", funct_a_b, [dn_confg_1], [dn_confg_2])
  91. scenario_config = Config.configure_scenario("sc", [task_config_1])
  92. scenario = tp.create_scenario(scenario_config)
  93. assert scenario.dn_1.is_up_to_date is True
  94. assert scenario.dn_2.is_up_to_date is False
  95. tp.submit(scenario)
  96. assert scenario.dn_1.is_up_to_date is True
  97. assert scenario.dn_2.is_up_to_date is True
  98. def test_create(self):
  99. a_date = datetime.now()
  100. dn = DataNode(
  101. "foo_bar",
  102. Scope.SCENARIO,
  103. DataNodeId("an_id"),
  104. "a_scenario_id",
  105. {"a_parent_id"},
  106. a_date,
  107. [{"job_id": "a_job_id"}],
  108. edit_in_progress=False,
  109. prop="erty",
  110. name="a name",
  111. )
  112. assert dn.config_id == "foo_bar"
  113. assert dn.scope == Scope.SCENARIO
  114. assert dn.id == "an_id"
  115. assert dn.name == "a name"
  116. assert dn.owner_id == "a_scenario_id"
  117. assert dn.parent_ids == {"a_parent_id"}
  118. assert dn.last_edit_date == a_date
  119. assert dn.job_ids == ["a_job_id"]
  120. assert dn.is_ready_for_reading
  121. assert len(dn.properties) == 2
  122. assert dn.properties == {"prop": "erty", "name": "a name"}
  123. with pytest.raises(InvalidConfigurationId):
  124. DataNode("foo bar")
  125. def test_read_write(self):
  126. dn = FakeDataNode("foo_bar")
  127. _DataManagerFactory._build_manager()._repository._save(dn)
  128. assert dn.read() is None
  129. with pytest.raises(NoData):
  130. _DataManagerFactory._build_manager()._read(dn)
  131. with pytest.raises(NoData):
  132. dn.read_or_raise()
  133. assert dn.write_has_been_called == 0
  134. assert dn.read_has_been_called == 0
  135. assert not dn.is_ready_for_reading
  136. assert dn.last_edit_date is None
  137. assert dn.job_ids == []
  138. assert dn.edits == []
  139. dn.write("Any data")
  140. assert dn.write_has_been_called == 1
  141. assert dn.read_has_been_called == 0
  142. assert dn.last_edit_date is not None
  143. first_edition = dn.last_edit_date
  144. assert dn.is_ready_for_reading
  145. assert dn.job_ids == []
  146. assert len(dn.edits) == 1
  147. assert dn.get_last_edit()["timestamp"] == dn.last_edit_date
  148. sleep(0.1)
  149. dn.write("Any other data", job_id := JobId("a_job_id"))
  150. assert dn.write_has_been_called == 2
  151. assert dn.read_has_been_called == 0
  152. second_edition = dn.last_edit_date
  153. assert first_edition < second_edition
  154. assert dn.is_ready_for_reading
  155. assert dn.job_ids == [job_id]
  156. assert len(dn.edits) == 2
  157. assert dn.get_last_edit()["timestamp"] == dn.last_edit_date
  158. dn.read()
  159. assert dn.write_has_been_called == 2
  160. assert dn.read_has_been_called == 1
  161. second_edition = dn.last_edit_date
  162. assert first_edition < second_edition
  163. assert dn.is_ready_for_reading
  164. assert dn.job_ids == [job_id]
  165. def test_lock_initialization(self):
  166. dn = InMemoryDataNode("dn", Scope.SCENARIO)
  167. assert not dn.edit_in_progress
  168. assert dn._editor_id is None
  169. assert dn._editor_expiration_date is None
  170. def test_locked_dn_unlockable_only_by_same_editor(self):
  171. dn = InMemoryDataNode("dn", Scope.SCENARIO)
  172. _DataManagerFactory._build_manager()._repository._save(dn)
  173. dn.lock_edit("user_1")
  174. assert dn.edit_in_progress
  175. assert dn._editor_id == "user_1"
  176. assert dn._editor_expiration_date is not None
  177. with pytest.raises(DataNodeIsBeingEdited):
  178. dn.lock_edit("user_2")
  179. with pytest.raises(DataNodeIsBeingEdited):
  180. dn.unlock_edit("user_2")
  181. dn.unlock_edit("user_1")
  182. assert not dn.edit_in_progress
  183. assert dn._editor_id is None
  184. assert dn._editor_expiration_date is None
  185. def test_none_editor_can_lock_a_locked_dn(self):
  186. dn = InMemoryDataNode("dn", Scope.SCENARIO)
  187. _DataManagerFactory._build_manager()._repository._save(dn)
  188. dn.lock_edit("user")
  189. assert dn.edit_in_progress
  190. assert dn._editor_id == "user"
  191. assert dn._editor_expiration_date is not None
  192. dn.lock_edit()
  193. assert dn.edit_in_progress
  194. assert dn._editor_id is None
  195. assert dn._editor_expiration_date is None
  196. def test_none_editor_can_unlock_a_locked_dn(self):
  197. dn = InMemoryDataNode("dn", Scope.SCENARIO)
  198. _DataManagerFactory._build_manager()._repository._save(dn)
  199. dn.lock_edit("user")
  200. assert dn.edit_in_progress
  201. assert dn._editor_id == "user"
  202. assert dn._editor_expiration_date is not None
  203. dn.unlock_edit()
  204. assert not dn.edit_in_progress
  205. assert dn._editor_id is None
  206. assert dn._editor_expiration_date is None
  207. dn.lock_edit()
  208. assert dn.edit_in_progress
  209. assert dn._editor_id is None
  210. assert dn._editor_expiration_date is None
  211. dn.unlock_edit()
  212. assert not dn.edit_in_progress
  213. assert dn._editor_id is None
  214. assert dn._editor_expiration_date is None
  215. def test_ready_for_reading(self):
  216. dn = InMemoryDataNode("foo_bar", Scope.CYCLE)
  217. _DataManagerFactory._build_manager()._repository._save(dn)
  218. assert dn.last_edit_date is None
  219. assert not dn.is_ready_for_reading
  220. assert dn.job_ids == []
  221. dn.lock_edit()
  222. assert dn.last_edit_date is None
  223. assert not dn.is_ready_for_reading
  224. assert dn.job_ids == []
  225. dn.unlock_edit()
  226. assert dn.last_edit_date is None
  227. assert not dn.is_ready_for_reading
  228. assert dn.job_ids == []
  229. dn.lock_edit()
  230. assert dn.last_edit_date is None
  231. assert not dn.is_ready_for_reading
  232. assert dn.job_ids == []
  233. dn.write("toto", job_id := JobId("a_job_id"))
  234. assert dn.last_edit_date is not None
  235. assert dn.is_ready_for_reading
  236. assert dn.job_ids == [job_id]
  237. def test_is_valid_no_validity_period(self):
  238. dn = InMemoryDataNode("foo", Scope.SCENARIO, DataNodeId("id"), "name", "owner_id")
  239. _DataManagerFactory._build_manager()._repository._save(dn)
  240. # Test Never been written
  241. assert not dn.is_valid
  242. # test has been written
  243. dn.write("My data")
  244. assert dn.is_valid
  245. def test_is_valid_with_30_min_validity_period(self):
  246. dn = InMemoryDataNode(
  247. "foo", Scope.SCENARIO, DataNodeId("id"), "name", "owner_id", validity_period=timedelta(minutes=30)
  248. )
  249. _DataManagerFactory._build_manager()._repository._save(dn)
  250. # Test Never been written
  251. assert dn.is_valid is False
  252. # Has been written less than 30 minutes ago
  253. dn.write("My data")
  254. assert dn.is_valid is True
  255. # Has been written more than 30 minutes ago
  256. dn.last_edit_date = datetime.now() + timedelta(days=-1)
  257. assert dn.is_valid is False
  258. def test_is_valid_with_5_days_validity_period(self):
  259. dn = InMemoryDataNode("foo", Scope.SCENARIO, validity_period=timedelta(days=5))
  260. _DataManagerFactory._build_manager()._repository._save(dn)
  261. # Test Never been written
  262. assert dn.is_valid is False
  263. # Has been written less than 30 minutes ago
  264. dn.write("My data")
  265. assert dn.is_valid is True
  266. # Has been written more than 30 minutes ago
  267. dn._last_edit_date = datetime.now() - timedelta(days=6)
  268. _DataManager()._repository._save(dn)
  269. assert dn.is_valid is False
  270. def test_is_up_to_date(self, current_datetime):
  271. dn_confg_1 = Config.configure_in_memory_data_node("dn_1")
  272. dn_confg_2 = Config.configure_in_memory_data_node("dn_2")
  273. dn_confg_3 = Config.configure_in_memory_data_node("dn_3", scope=Scope.GLOBAL)
  274. task_config_1 = Config.configure_task("t1", print, [dn_confg_1], [dn_confg_2])
  275. task_config_2 = Config.configure_task("t2", print, [dn_confg_2], [dn_confg_3])
  276. scenario_config = Config.configure_scenario("sc", [task_config_1, task_config_2])
  277. scenario_1 = tp.create_scenario(scenario_config)
  278. assert len(_DataManager._get_all()) == 3
  279. dn_1_1 = scenario_1.data_nodes["dn_1"]
  280. dn_2_1 = scenario_1.data_nodes["dn_2"]
  281. dn_3_1 = scenario_1.data_nodes["dn_3"]
  282. assert dn_1_1.last_edit_date is None
  283. assert dn_2_1.last_edit_date is None
  284. assert dn_3_1.last_edit_date is None
  285. dn_1_1.last_edit_date = current_datetime + timedelta(1)
  286. dn_2_1.last_edit_date = current_datetime + timedelta(2)
  287. dn_3_1.last_edit_date = current_datetime + timedelta(3)
  288. assert dn_1_1.is_up_to_date
  289. assert dn_2_1.is_up_to_date
  290. assert dn_3_1.is_up_to_date
  291. dn_2_1.last_edit_date = current_datetime + timedelta(4)
  292. assert dn_1_1.is_up_to_date
  293. assert dn_2_1.is_up_to_date
  294. assert not dn_3_1.is_up_to_date
  295. dn_1_1.last_edit_date = current_datetime + timedelta(5)
  296. assert dn_1_1.is_up_to_date
  297. assert not dn_2_1.is_up_to_date
  298. assert not dn_3_1.is_up_to_date
  299. dn_1_1.last_edit_date = current_datetime + timedelta(1)
  300. dn_2_1.last_edit_date = current_datetime + timedelta(2)
  301. dn_3_1.last_edit_date = current_datetime + timedelta(3)
  302. def test_is_up_to_date_across_scenarios(self, current_datetime):
  303. dn_confg_1 = Config.configure_in_memory_data_node("dn_1", scope=Scope.SCENARIO)
  304. dn_confg_2 = Config.configure_in_memory_data_node("dn_2", scope=Scope.SCENARIO)
  305. dn_confg_3 = Config.configure_in_memory_data_node("dn_3", scope=Scope.GLOBAL)
  306. task_config_1 = Config.configure_task("t1", print, [dn_confg_1], [dn_confg_2])
  307. task_config_2 = Config.configure_task("t2", print, [dn_confg_2], [dn_confg_3])
  308. scenario_config = Config.configure_scenario("sc", [task_config_1, task_config_2])
  309. scenario_1 = tp.create_scenario(scenario_config)
  310. scenario_2 = tp.create_scenario(scenario_config)
  311. assert len(_DataManager._get_all()) == 5
  312. dn_1_1 = scenario_1.data_nodes["dn_1"]
  313. dn_2_1 = scenario_1.data_nodes["dn_2"]
  314. dn_1_2 = scenario_2.data_nodes["dn_1"]
  315. dn_2_2 = scenario_2.data_nodes["dn_2"]
  316. dn_3 = scenario_1.data_nodes["dn_3"]
  317. assert dn_3 == scenario_2.data_nodes["dn_3"]
  318. assert dn_1_1.last_edit_date is None
  319. assert dn_2_1.last_edit_date is None
  320. assert dn_1_2.last_edit_date is None
  321. assert dn_2_2.last_edit_date is None
  322. assert dn_3.last_edit_date is None
  323. dn_1_1.last_edit_date = current_datetime + timedelta(1)
  324. dn_2_1.last_edit_date = current_datetime + timedelta(2)
  325. dn_1_2.last_edit_date = current_datetime + timedelta(3)
  326. dn_2_2.last_edit_date = current_datetime + timedelta(4)
  327. dn_3.last_edit_date = current_datetime + timedelta(5)
  328. assert dn_1_1.is_up_to_date
  329. assert dn_2_1.is_up_to_date
  330. assert dn_1_2.is_up_to_date
  331. assert dn_2_2.is_up_to_date
  332. assert dn_3.is_up_to_date
  333. dn_2_1.last_edit_date = current_datetime + timedelta(6)
  334. assert dn_1_1.is_up_to_date
  335. assert dn_2_1.is_up_to_date
  336. assert dn_1_2.is_up_to_date
  337. assert dn_2_2.is_up_to_date
  338. assert not dn_3.is_up_to_date
  339. dn_2_1.last_edit_date = current_datetime + timedelta(2)
  340. dn_2_2.last_edit_date = current_datetime + timedelta(6)
  341. assert dn_1_1.is_up_to_date
  342. assert dn_2_1.is_up_to_date
  343. assert dn_1_2.is_up_to_date
  344. assert dn_2_2.is_up_to_date
  345. assert not dn_3.is_up_to_date
  346. dn_2_2.last_edit_date = current_datetime + timedelta(4)
  347. dn_1_1.last_edit_date = current_datetime + timedelta(6)
  348. assert dn_1_1.is_up_to_date
  349. assert not dn_2_1.is_up_to_date
  350. assert dn_1_2.is_up_to_date
  351. assert dn_2_2.is_up_to_date
  352. assert not dn_3.is_up_to_date
  353. dn_1_2.last_edit_date = current_datetime + timedelta(6)
  354. assert dn_1_1.is_up_to_date
  355. assert not dn_2_1.is_up_to_date
  356. assert dn_1_2.is_up_to_date
  357. assert not dn_2_2.is_up_to_date
  358. assert not dn_3.is_up_to_date
  359. def test_do_not_recompute_data_node_valid_but_continue_sequence_execution(self):
  360. a = Config.configure_data_node("A", "pickle", default_data="A")
  361. b = Config.configure_data_node("B", "pickle")
  362. c = Config.configure_data_node("C", "pickle")
  363. d = Config.configure_data_node("D", "pickle")
  364. task_a_b = Config.configure_task("task_a_b", funct_a_b, input=a, output=b, skippable=True)
  365. task_b_c = Config.configure_task("task_b_c", funct_b_c, input=b, output=c)
  366. task_b_d = Config.configure_task("task_b_d", funct_b_d, input=b, output=d)
  367. scenario_cfg = Config.configure_scenario("scenario", [task_a_b, task_b_c, task_b_d])
  368. scenario = tp.create_scenario(scenario_cfg)
  369. scenario.submit()
  370. assert scenario.A.read() == "A"
  371. assert scenario.B.read() == "B"
  372. assert scenario.C.read() == "C"
  373. assert scenario.D.read() == "D"
  374. scenario.submit()
  375. assert len(tp.get_jobs()) == 6
  376. jobs_and_status = [(job.task.config_id, job.status) for job in tp.get_jobs()]
  377. assert ("task_a_b", tp.Status.COMPLETED) in jobs_and_status
  378. assert ("task_a_b", tp.Status.SKIPPED) in jobs_and_status
  379. assert ("task_b_c", tp.Status.COMPLETED) in jobs_and_status
  380. assert ("task_b_d", tp.Status.COMPLETED) in jobs_and_status
  381. def test_data_node_update_after_writing(self):
  382. dn = FakeDataNode("foo")
  383. _DataManager._repository._save(dn)
  384. assert not _DataManager._get(dn.id).is_ready_for_reading
  385. dn.write("Any data")
  386. assert dn.is_ready_for_reading
  387. assert _DataManager._get(dn.id).is_ready_for_reading
  388. def test_expiration_date_raise_if_never_write(self):
  389. dn = FakeDataNode("foo")
  390. with pytest.raises(NoData):
  391. _ = dn.expiration_date
  392. def test_validity_null_if_never_write(self):
  393. dn = FakeDataNode("foo")
  394. assert dn.validity_period is None
  395. def test_auto_update_and_reload(self, current_datetime):
  396. dn_1 = InMemoryDataNode(
  397. "foo",
  398. scope=Scope.GLOBAL,
  399. id=DataNodeId("an_id"),
  400. owner_id=None,
  401. parent_ids=None,
  402. last_edit_date=current_datetime,
  403. edits=[{"job_id": "a_job_id"}],
  404. edit_in_progress=False,
  405. validity_period=None,
  406. properties={
  407. "name": "foo",
  408. },
  409. )
  410. dm = _DataManager()
  411. dm._repository._save(dn_1)
  412. dn_2 = dm._get(dn_1)
  413. # auto set & reload on scope attribute
  414. assert dn_1.scope == Scope.GLOBAL
  415. assert dn_2.scope == Scope.GLOBAL
  416. dn_1.scope = Scope.CYCLE
  417. assert dn_1.scope == Scope.CYCLE
  418. assert dn_2.scope == Scope.CYCLE
  419. dn_2.scope = Scope.SCENARIO
  420. assert dn_1.scope == Scope.SCENARIO
  421. assert dn_2.scope == Scope.SCENARIO
  422. new_datetime = current_datetime + timedelta(1)
  423. new_datetime_1 = current_datetime + timedelta(3)
  424. # auto set & reload on last_edit_date attribute
  425. assert dn_1.last_edit_date == current_datetime
  426. assert dn_2.last_edit_date == current_datetime
  427. dn_1.last_edit_date = new_datetime_1
  428. assert dn_1.last_edit_date == new_datetime_1
  429. assert dn_2.last_edit_date == new_datetime_1
  430. dn_2.last_edit_date = new_datetime
  431. assert dn_1.last_edit_date == new_datetime
  432. assert dn_2.last_edit_date == new_datetime
  433. # auto set & reload on name attribute
  434. assert dn_1.name == "foo"
  435. assert dn_2.name == "foo"
  436. dn_1.name = "fed"
  437. assert dn_1.name == "fed"
  438. assert dn_2.name == "fed"
  439. dn_2.name = "def"
  440. assert dn_1.name == "def"
  441. assert dn_2.name == "def"
  442. # auto set & reload on parent_ids attribute (set() object does not have auto set yet)
  443. assert dn_1.parent_ids == set()
  444. assert dn_2.parent_ids == set()
  445. dn_1._parent_ids.update(["sc2"])
  446. _DataManager._update(dn_1)
  447. assert dn_1.parent_ids == {"sc2"}
  448. assert dn_2.parent_ids == {"sc2"}
  449. dn_2._parent_ids.clear()
  450. dn_2._parent_ids.update(["sc1"])
  451. _DataManager._update(dn_2)
  452. assert dn_1.parent_ids == {"sc1"}
  453. assert dn_2.parent_ids == {"sc1"}
  454. dn_2._parent_ids.clear()
  455. _DataManager._update(dn_2)
  456. # auto set & reload on edit_in_progress attribute
  457. assert not dn_2.edit_in_progress
  458. assert not dn_1.edit_in_progress
  459. dn_1.edit_in_progress = True
  460. assert dn_1.edit_in_progress
  461. assert dn_2.edit_in_progress
  462. dn_2.unlock_edit()
  463. assert not dn_1.edit_in_progress
  464. assert not dn_2.edit_in_progress
  465. dn_1.lock_edit()
  466. assert dn_1.edit_in_progress
  467. assert dn_2.edit_in_progress
  468. # auto set & reload on validity_period attribute
  469. time_period_1 = timedelta(1)
  470. time_period_2 = timedelta(5)
  471. assert dn_1.validity_period is None
  472. assert dn_2.validity_period is None
  473. dn_1.validity_period = time_period_1
  474. assert dn_1.validity_period == time_period_1
  475. assert dn_2.validity_period == time_period_1
  476. dn_2.validity_period = time_period_2
  477. assert dn_1.validity_period == time_period_2
  478. assert dn_2.validity_period == time_period_2
  479. dn_1.last_edit_date = new_datetime
  480. assert len(dn_1.job_ids) == 1
  481. assert len(dn_2.job_ids) == 1
  482. with dn_1 as dn:
  483. assert dn.config_id == "foo"
  484. assert dn.owner_id is None
  485. assert dn.scope == Scope.SCENARIO
  486. assert dn.last_edit_date == new_datetime
  487. assert dn.name == "def"
  488. assert dn.edit_in_progress
  489. assert dn.validity_period == time_period_2
  490. assert len(dn.job_ids) == 1
  491. assert dn._is_in_context
  492. new_datetime_2 = new_datetime + timedelta(5)
  493. dn.scope = Scope.CYCLE
  494. dn.last_edit_date = new_datetime_2
  495. dn.name = "abc"
  496. dn.edit_in_progress = False
  497. dn.validity_period = None
  498. assert dn.config_id == "foo"
  499. assert dn.owner_id is None
  500. assert dn.scope == Scope.SCENARIO
  501. assert dn.last_edit_date == new_datetime
  502. assert dn.name == "def"
  503. assert dn.edit_in_progress
  504. assert dn.validity_period == time_period_2
  505. assert len(dn.job_ids) == 1
  506. assert dn_1.config_id == "foo"
  507. assert dn_1.owner_id is None
  508. assert dn_1.scope == Scope.CYCLE
  509. assert dn_1.last_edit_date == new_datetime_2
  510. assert dn_1.name == "abc"
  511. assert not dn_1.edit_in_progress
  512. assert dn_1.validity_period is None
  513. assert not dn_1._is_in_context
  514. assert len(dn_1.job_ids) == 1
  515. def test_auto_update_and_reload_properties(self):
  516. dn_1 = InMemoryDataNode("foo", scope=Scope.GLOBAL, properties={"name": "def"})
  517. dm = _DataManager()
  518. dm._repository._save(dn_1)
  519. dn_2 = dm._get(dn_1)
  520. # auto set & reload on properties attribute
  521. assert dn_1.properties == {"name": "def"}
  522. assert dn_2.properties == {"name": "def"}
  523. dn_1._properties["qux"] = 4
  524. assert dn_1.properties["qux"] == 4
  525. assert dn_2.properties["qux"] == 4
  526. assert dn_1.properties == {"qux": 4, "name": "def"}
  527. assert dn_2.properties == {"qux": 4, "name": "def"}
  528. dn_2._properties["qux"] = 5
  529. assert dn_1.properties["qux"] == 5
  530. assert dn_2.properties["qux"] == 5
  531. dn_1.properties["temp_key_1"] = "temp_value_1"
  532. dn_1.properties["temp_key_2"] = "temp_value_2"
  533. assert dn_1.properties == {
  534. "name": "def",
  535. "qux": 5,
  536. "temp_key_1": "temp_value_1",
  537. "temp_key_2": "temp_value_2",
  538. }
  539. assert dn_2.properties == {"name": "def", "qux": 5, "temp_key_1": "temp_value_1", "temp_key_2": "temp_value_2"}
  540. dn_1.properties.pop("temp_key_1")
  541. assert "temp_key_1" not in dn_1.properties.keys()
  542. assert "temp_key_1" not in dn_1.properties.keys()
  543. assert dn_1.properties == {"name": "def", "qux": 5, "temp_key_2": "temp_value_2"}
  544. assert dn_2.properties == {"name": "def", "qux": 5, "temp_key_2": "temp_value_2"}
  545. dn_2.properties.pop("temp_key_2")
  546. assert dn_1.properties == {"qux": 5, "name": "def"}
  547. assert dn_2.properties == {"qux": 5, "name": "def"}
  548. assert "temp_key_2" not in dn_1.properties.keys()
  549. assert "temp_key_2" not in dn_2.properties.keys()
  550. dn_1.properties["temp_key_3"] = 0
  551. assert dn_1.properties == {"qux": 5, "temp_key_3": 0, "name": "def"}
  552. assert dn_2.properties == {"qux": 5, "temp_key_3": 0, "name": "def"}
  553. dn_1.properties.update({"temp_key_3": 1})
  554. assert dn_1.properties == {"qux": 5, "temp_key_3": 1, "name": "def"}
  555. assert dn_2.properties == {"qux": 5, "temp_key_3": 1, "name": "def"}
  556. dn_1.properties.update({})
  557. assert dn_1.properties == {"qux": 5, "temp_key_3": 1, "name": "def"}
  558. assert dn_2.properties == {"qux": 5, "temp_key_3": 1, "name": "def"}
  559. dn_1.properties["temp_key_4"] = 0
  560. dn_1.properties["temp_key_5"] = 0
  561. with dn_1 as dn:
  562. assert dn._is_in_context
  563. assert dn.properties["qux"] == 5
  564. assert dn.properties["temp_key_3"] == 1
  565. assert dn.properties["temp_key_4"] == 0
  566. assert dn.properties["temp_key_5"] == 0
  567. dn.properties["qux"] = 9
  568. dn.properties.pop("temp_key_3")
  569. dn.properties.pop("temp_key_4")
  570. dn.properties.update({"temp_key_4": 1})
  571. dn.properties.update({"temp_key_5": 2})
  572. dn.properties.pop("temp_key_5")
  573. dn.properties.update({})
  574. assert dn.properties["qux"] == 5
  575. assert dn.properties["temp_key_3"] == 1
  576. assert dn.properties["temp_key_4"] == 0
  577. assert dn.properties["temp_key_5"] == 0
  578. assert not dn_1._is_in_context
  579. assert dn_1.properties["qux"] == 9
  580. assert "temp_key_3" not in dn_1.properties.keys()
  581. assert dn_1.properties["temp_key_4"] == 1
  582. assert "temp_key_5" not in dn_1.properties.keys()
  583. def test_get_parents(self, data_node):
  584. with mock.patch("taipy.core.get_parents") as mck:
  585. data_node.get_parents()
  586. mck.assert_called_once_with(data_node)
  587. def test_data_node_with_env_variable_value_not_stored(self):
  588. dn_config = Config.configure_data_node("A", prop="ENV[FOO]")
  589. with mock.patch.dict(os.environ, {"FOO": "bar"}):
  590. dn = _DataManager._bulk_get_or_create([dn_config])[dn_config]
  591. assert dn._properties.data["prop"] == "ENV[FOO]"
  592. assert dn.properties["prop"] == "bar"
  593. def test_path_populated_with_config_default_path(self):
  594. dn_config = Config.configure_data_node("data_node", "pickle", default_path="foo.p")
  595. assert dn_config.default_path == "foo.p"
  596. data_node = _DataManager._bulk_get_or_create([dn_config])[dn_config]
  597. assert data_node.path == "foo.p"
  598. data_node.path = "baz.p"
  599. assert data_node.path == "baz.p"
  600. def test_edit_edit_tracking(self):
  601. dn_config = Config.configure_data_node("A")
  602. data_node = _DataManager._bulk_get_or_create([dn_config])[dn_config]
  603. data_node.write(data="1", job_id="job_1")
  604. data_node.write(data="2", job_id="job_1")
  605. data_node.write(data="3", job_id="job_1")
  606. assert len(data_node.edits) == 3
  607. assert len(data_node.job_ids) == 3
  608. assert data_node.edits[-1] == data_node.get_last_edit()
  609. assert data_node.last_edit_date == data_node.get_last_edit().get("timestamp")
  610. date = datetime(2050, 1, 1, 12, 12)
  611. data_node.write(data="4", timestamp=date, message="This is a comment on this edit", env="staging")
  612. assert len(data_node.edits) == 4
  613. assert len(data_node.job_ids) == 3
  614. assert data_node.edits[-1] == data_node.get_last_edit()
  615. last_edit = data_node.get_last_edit()
  616. assert last_edit["message"] == "This is a comment on this edit"
  617. assert last_edit["env"] == "staging"
  618. assert last_edit["timestamp"] == date
  619. def test_label(self):
  620. a_date = datetime.now()
  621. dn = DataNode(
  622. "foo_bar",
  623. Scope.SCENARIO,
  624. DataNodeId("an_id"),
  625. "a_scenario_id",
  626. {"a_parent_id"},
  627. a_date,
  628. [{"job_id": "a_job_id"}],
  629. edit_in_progress=False,
  630. prop="erty",
  631. name="a name",
  632. )
  633. with mock.patch("taipy.core.get") as get_mck:
  634. class MockOwner:
  635. label = "owner_label"
  636. def get_label(self):
  637. return self.label
  638. get_mck.return_value = MockOwner()
  639. assert dn.get_label() == "owner_label > " + dn.name
  640. assert dn.get_simple_label() == dn.name
  641. def test_explicit_label(self):
  642. a_date = datetime.now()
  643. dn = DataNode(
  644. "foo_bar",
  645. Scope.SCENARIO,
  646. DataNodeId("an_id"),
  647. "a_scenario_id",
  648. {"a_parent_id"},
  649. a_date,
  650. [{"job_id": "a_job_id"}],
  651. edit_in_progress=False,
  652. label="a label",
  653. name="a name",
  654. )
  655. assert dn.get_label() == "a label"
  656. assert dn.get_simple_label() == "a label"
  657. def test_change_data_node_name(self):
  658. cgf = Config.configure_data_node("foo", scope=Scope.GLOBAL)
  659. dn = tp.create_global_data_node(cgf)
  660. dn.name = "bar"
  661. assert dn.name == "bar"
  662. # This new syntax will be the only one allowed: https://github.com/Avaiga/taipy-core/issues/806
  663. dn.properties["name"] = "baz"
  664. assert dn.name == "baz"
  665. def test_locked_data_node_write_should_fail_with_wrong_editor(self):
  666. dn_config = Config.configure_data_node("A")
  667. dn = _DataManager._bulk_get_or_create([dn_config])[dn_config]
  668. dn.lock_edit("editor_1")
  669. # Should raise exception for wrong editor
  670. with pytest.raises(DataNodeIsBeingEdited):
  671. dn.write("data", editor_id="editor_2")
  672. # Should succeed with correct editor
  673. dn.write("data", editor_id="editor_1")
  674. assert dn.read() == "data"
  675. def test_locked_data_node_write_should_fail_before_expiration_date_and_succeed_after(self):
  676. dn_config = Config.configure_data_node("A")
  677. dn = _DataManager._bulk_get_or_create([dn_config])[dn_config]
  678. lock_time = datetime.now()
  679. with freezegun.freeze_time(lock_time):
  680. dn.lock_edit("editor_1")
  681. with freezegun.freeze_time(lock_time + timedelta(minutes=29)):
  682. # Should raise exception for wrong editor and expiration date NOT passed
  683. with pytest.raises(DataNodeIsBeingEdited):
  684. dn.write("data", editor_id="editor_2")
  685. with freezegun.freeze_time(lock_time + timedelta(minutes=31)):
  686. # Should succeed with wrong editor but expiration date passed
  687. dn.write("data", editor_id="editor_2")
  688. assert dn.read() == "data"
  689. def test_locked_data_node_append_should_fail_with_wrong_editor(self):
  690. dn_config = Config.configure_csv_data_node("A")
  691. dn = _DataManager._bulk_get_or_create([dn_config])[dn_config]
  692. first_line = pd.DataFrame(data={"col1": [1], "col2": [3]})
  693. second_line = pd.DataFrame(data={"col1": [2], "col2": [4]})
  694. data = pd.DataFrame(data={"col1": [1, 2], "col2": [3, 4]})
  695. dn.write(first_line)
  696. assert first_line.equals(dn.read())
  697. dn.lock_edit("editor_1")
  698. with pytest.raises(DataNodeIsBeingEdited):
  699. dn.append(second_line, editor_id="editor_2")
  700. dn.append(second_line, editor_id="editor_1")
  701. assert dn.read().equals(data)
  702. def test_locked_data_node_append_should_fail_before_expiration_date_and_succeed_after(self):
  703. dn_config = Config.configure_csv_data_node("A")
  704. dn = _DataManager._bulk_get_or_create([dn_config])[dn_config]
  705. first_line = pd.DataFrame(data={"col1": [1], "col2": [3]})
  706. second_line = pd.DataFrame(data={"col1": [2], "col2": [4]})
  707. data = pd.DataFrame(data={"col1": [1, 2], "col2": [3, 4]})
  708. dn.write(first_line)
  709. assert first_line.equals(dn.read())
  710. lock_time = datetime.now()
  711. with freezegun.freeze_time(lock_time):
  712. dn.lock_edit("editor_1")
  713. with freezegun.freeze_time(lock_time + timedelta(minutes=29)):
  714. # Should raise exception for wrong editor and expiration date NOT passed
  715. with pytest.raises(DataNodeIsBeingEdited):
  716. dn.append(second_line, editor_id="editor_2")
  717. with freezegun.freeze_time(lock_time + timedelta(minutes=31)):
  718. # Should succeed with wrong editor but expiration date passed
  719. dn.append(second_line, editor_id="editor_2")
  720. assert dn.read().equals(data)
  721. def test_orchestrator_write_without_editor_id(self):
  722. dn_config = Config.configure_data_node("A")
  723. dn = _DataManager._bulk_get_or_create([dn_config])[dn_config]
  724. dn.lock_edit("editor_1")
  725. # Orchestrator write without editor_id should succeed
  726. dn.write("orchestrator_data")
  727. assert dn.read() == "orchestrator_data"
  728. def test_editor_fails_writing_a_data_node_locked_by_orchestrator(self):
  729. dn_config = Config.configure_data_node("A")
  730. dn = _DataManager._bulk_get_or_create([dn_config])[dn_config]
  731. dn.lock_edit() # Locked by orchestrator
  732. with pytest.raises(DataNodeIsBeingEdited):
  733. dn.write("data", editor_id="editor_1")
  734. # Orchestrator write without editor_id should succeed
  735. dn.write("orchestrator_data", job_id=JobId("job_1"))
  736. assert dn.read() == "orchestrator_data"
  737. def test_editor_fails_appending_a_data_node_locked_by_orchestrator(self):
  738. dn_config = Config.configure_csv_data_node("A")
  739. dn = _DataManager._bulk_get_or_create([dn_config])[dn_config]
  740. first_line = pd.DataFrame(data={"col1": [1], "col2": [3]})
  741. second_line = pd.DataFrame(data={"col1": [2], "col2": [4]})
  742. data = pd.DataFrame(data={"col1": [1, 2], "col2": [3, 4]})
  743. dn.write(first_line)
  744. assert first_line.equals(dn.read())
  745. dn = _DataManager._bulk_get_or_create([dn_config])[dn_config]
  746. dn.lock_edit() # Locked by orchestrator
  747. with pytest.raises(DataNodeIsBeingEdited):
  748. dn.append(second_line, editor_id="editor_1")
  749. assert dn.read().equals(first_line)
  750. dn.append(second_line, job_id=JobId("job_1"))
  751. assert dn.read().equals(data)
  752. def test_track_edit(self):
  753. dn_config = Config.configure_data_node("A")
  754. data_node = _DataManager._bulk_get_or_create([dn_config])[dn_config]
  755. before = datetime.now()
  756. data_node.track_edit(job_id="job_1")
  757. data_node.track_edit(editor_id="editor_1")
  758. data_node.track_edit(comment="This is a comment on this edit")
  759. data_node.track_edit(editor_id="editor_2", comment="This is another comment on this edit")
  760. data_node.track_edit(editor_id="editor_3", foo="bar")
  761. after = datetime.now()
  762. timestamp = datetime.now()
  763. data_node.track_edit(timestamp=timestamp)
  764. _DataManagerFactory._build_manager()._update(data_node)
  765. # To save the edits because track edit does not save the data node
  766. assert len(data_node.edits) == 6
  767. assert data_node.edits[-1] == data_node.get_last_edit()
  768. assert data_node.last_edit_date == data_node.get_last_edit().get(EDIT_TIMESTAMP_KEY)
  769. edit_0 = data_node.edits[0]
  770. assert len(edit_0) == 2
  771. assert edit_0[EDIT_JOB_ID_KEY] == "job_1"
  772. assert edit_0[EDIT_TIMESTAMP_KEY] >= before
  773. assert edit_0[EDIT_TIMESTAMP_KEY] <= after
  774. edit_1 = data_node.edits[1]
  775. assert len(edit_1) == 2
  776. assert edit_1[EDIT_EDITOR_ID_KEY] == "editor_1"
  777. assert edit_1[EDIT_TIMESTAMP_KEY] >= before
  778. assert edit_1[EDIT_TIMESTAMP_KEY] <= after
  779. edit_2 = data_node.edits[2]
  780. assert len(edit_2) == 2
  781. assert edit_2[EDIT_COMMENT_KEY] == "This is a comment on this edit"
  782. assert edit_2[EDIT_TIMESTAMP_KEY] >= before
  783. assert edit_2[EDIT_TIMESTAMP_KEY] <= after
  784. edit_3 = data_node.edits[3]
  785. assert len(edit_3) == 3
  786. assert edit_3[EDIT_EDITOR_ID_KEY] == "editor_2"
  787. assert edit_3[EDIT_COMMENT_KEY] == "This is another comment on this edit"
  788. assert edit_3[EDIT_TIMESTAMP_KEY] >= before
  789. assert edit_3[EDIT_TIMESTAMP_KEY] <= after
  790. edit_4 = data_node.edits[4]
  791. assert len(edit_4) == 3
  792. assert edit_4[EDIT_EDITOR_ID_KEY] == "editor_3"
  793. assert edit_4["foo"] == "bar"
  794. assert edit_4[EDIT_TIMESTAMP_KEY] >= before
  795. assert edit_4[EDIT_TIMESTAMP_KEY] <= after
  796. edit_5 = data_node.edits[5]
  797. assert len(edit_5) == 1
  798. assert edit_5[EDIT_TIMESTAMP_KEY] == timestamp
  799. def test_normalize_path(self):
  800. dn = DataNode(
  801. config_id="foo_bar",
  802. scope=Scope.SCENARIO,
  803. id=DataNodeId("an_id"),
  804. path=r"data\foo\bar.csv",
  805. )
  806. assert dn.config_id == "foo_bar"
  807. assert dn.scope == Scope.SCENARIO
  808. assert dn.id == "an_id"
  809. assert dn.properties["path"] == "data/foo/bar.csv"