test_core_cli.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579
  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. from unittest.mock import patch
  12. import pytest
  13. from taipy.config.common.frequency import Frequency
  14. from taipy.config.common.scope import Scope
  15. from taipy.config.config import Config
  16. from taipy.core import Core, taipy
  17. from taipy.core._version._version_manager import _VersionManager
  18. from taipy.core._version._version_manager_factory import _VersionManagerFactory
  19. from taipy.core.common._utils import _load_fct
  20. from taipy.core.cycle._cycle_manager import _CycleManager
  21. from taipy.core.data._data_manager import _DataManager
  22. from taipy.core.exceptions.exceptions import NonExistingVersion
  23. from taipy.core.job._job_manager import _JobManager
  24. from taipy.core.scenario._scenario_manager import _ScenarioManager
  25. from taipy.core.sequence._sequence_manager import _SequenceManager
  26. from taipy.core.task._task_manager import _TaskManager
  27. from tests.core.utils import assert_true_after_time
  28. def test_core_cli_no_arguments():
  29. with patch("sys.argv", ["prog"]):
  30. core = Core()
  31. core.run()
  32. assert Config.core.mode == "development"
  33. assert Config.core.version_number == _VersionManagerFactory._build_manager()._get_development_version()
  34. assert not Config.core.force
  35. core.stop()
  36. def test_core_cli_development_mode():
  37. with patch("sys.argv", ["prog", "--development"]):
  38. core = Core()
  39. core.run()
  40. assert Config.core.mode == "development"
  41. assert Config.core.version_number == _VersionManagerFactory._build_manager()._get_development_version()
  42. core.stop()
  43. def test_core_cli_dev_mode():
  44. with patch("sys.argv", ["prog", "-dev"]):
  45. core = Core()
  46. core.run()
  47. assert Config.core.mode == "development"
  48. assert Config.core.version_number == _VersionManagerFactory._build_manager()._get_development_version()
  49. core.stop()
  50. def test_core_cli_experiment_mode():
  51. with patch("sys.argv", ["prog", "--experiment"]):
  52. core = Core()
  53. core.run()
  54. assert Config.core.mode == "experiment"
  55. assert Config.core.version_number == _VersionManagerFactory._build_manager()._get_latest_version()
  56. assert not Config.core.force
  57. core.stop()
  58. def test_core_cli_experiment_mode_with_version():
  59. with patch("sys.argv", ["prog", "--experiment", "2.1"]):
  60. core = Core()
  61. core.run()
  62. assert Config.core.mode == "experiment"
  63. assert Config.core.version_number == "2.1"
  64. assert not Config.core.force
  65. core.stop()
  66. def test_core_cli_experiment_mode_with_force_version(init_config):
  67. with patch("sys.argv", ["prog", "--experiment", "2.1", "--taipy-force"]):
  68. init_config()
  69. core = Core()
  70. core.run()
  71. assert Config.core.mode == "experiment"
  72. assert Config.core.version_number == "2.1"
  73. assert Config.core.force
  74. core.stop()
  75. def test_core_cli_production_mode():
  76. with patch("sys.argv", ["prog", "--production"]):
  77. core = Core()
  78. core.run()
  79. assert Config.core.mode == "production"
  80. assert Config.core.version_number == _VersionManagerFactory._build_manager()._get_latest_version()
  81. assert not Config.core.force
  82. core.stop()
  83. def test_dev_mode_clean_all_entities_of_the_latest_version():
  84. scenario_config = config_scenario()
  85. # Create a scenario in development mode
  86. with patch("sys.argv", ["prog"]):
  87. core = Core()
  88. core.run()
  89. scenario = _ScenarioManager._create(scenario_config)
  90. taipy.submit(scenario)
  91. core.stop()
  92. # Initial assertion
  93. assert len(_DataManager._get_all(version_number="all")) == 2
  94. assert len(_TaskManager._get_all(version_number="all")) == 1
  95. assert len(_SequenceManager._get_all(version_number="all")) == 1
  96. assert len(_ScenarioManager._get_all(version_number="all")) == 1
  97. assert len(_CycleManager._get_all(version_number="all")) == 1
  98. assert len(_JobManager._get_all(version_number="all")) == 1
  99. # Create a new scenario in experiment mode
  100. with patch("sys.argv", ["prog", "--experiment"]):
  101. core = Core()
  102. core.run()
  103. scenario = _ScenarioManager._create(scenario_config)
  104. taipy.submit(scenario)
  105. core.stop()
  106. # Assert number of entities in 2nd version
  107. assert len(_DataManager._get_all(version_number="all")) == 4
  108. assert len(_TaskManager._get_all(version_number="all")) == 2
  109. assert len(_SequenceManager._get_all(version_number="all")) == 2
  110. assert len(_ScenarioManager._get_all(version_number="all")) == 2
  111. # No new cycle is created since old dev version use the same cycle
  112. assert len(_CycleManager._get_all(version_number="all")) == 1
  113. assert len(_JobManager._get_all(version_number="all")) == 2
  114. # Run development mode again
  115. with patch("sys.argv", ["prog", "--development"]):
  116. core = Core()
  117. core.run()
  118. # The 1st dev version should be deleted run with development mode
  119. assert len(_DataManager._get_all(version_number="all")) == 2
  120. assert len(_TaskManager._get_all(version_number="all")) == 1
  121. assert len(_SequenceManager._get_all(version_number="all")) == 1
  122. assert len(_ScenarioManager._get_all(version_number="all")) == 1
  123. assert len(_CycleManager._get_all(version_number="all")) == 1
  124. assert len(_JobManager._get_all(version_number="all")) == 1
  125. # Submit new dev version
  126. scenario = _ScenarioManager._create(scenario_config)
  127. taipy.submit(scenario)
  128. core.stop()
  129. # Assert number of entities with 1 dev version and 1 exp version
  130. assert len(_DataManager._get_all(version_number="all")) == 4
  131. assert len(_TaskManager._get_all(version_number="all")) == 2
  132. assert len(_SequenceManager._get_all(version_number="all")) == 2
  133. assert len(_ScenarioManager._get_all(version_number="all")) == 2
  134. assert len(_CycleManager._get_all(version_number="all")) == 1
  135. assert len(_JobManager._get_all(version_number="all")) == 2
  136. # Assert number of entities of the latest version only
  137. assert len(_DataManager._get_all(version_number="latest")) == 2
  138. assert len(_TaskManager._get_all(version_number="latest")) == 1
  139. assert len(_SequenceManager._get_all(version_number="latest")) == 1
  140. assert len(_ScenarioManager._get_all(version_number="latest")) == 1
  141. assert len(_JobManager._get_all(version_number="latest")) == 1
  142. # Assert number of entities of the development version only
  143. assert len(_DataManager._get_all(version_number="development")) == 2
  144. assert len(_TaskManager._get_all(version_number="development")) == 1
  145. assert len(_SequenceManager._get_all(version_number="development")) == 1
  146. assert len(_ScenarioManager._get_all(version_number="development")) == 1
  147. assert len(_JobManager._get_all(version_number="development")) == 1
  148. # Assert number of entities of an unknown version
  149. with pytest.raises(NonExistingVersion):
  150. assert _DataManager._get_all(version_number="foo")
  151. with pytest.raises(NonExistingVersion):
  152. assert _TaskManager._get_all(version_number="foo")
  153. with pytest.raises(NonExistingVersion):
  154. assert _SequenceManager._get_all(version_number="foo")
  155. with pytest.raises(NonExistingVersion):
  156. assert _ScenarioManager._get_all(version_number="foo")
  157. with pytest.raises(NonExistingVersion):
  158. assert _JobManager._get_all(version_number="foo")
  159. def twice_doppelganger(a):
  160. return a * 2
  161. def test_dev_mode_clean_all_entities_when_config_is_alternated():
  162. data_node_1_config = Config.configure_data_node(
  163. id="d1", storage_type="pickle", default_data="abc", scope=Scope.SCENARIO
  164. )
  165. data_node_2_config = Config.configure_data_node(id="d2", storage_type="csv", default_path="foo.csv")
  166. task_config = Config.configure_task("my_task", twice_doppelganger, data_node_1_config, data_node_2_config)
  167. scenario_config = Config.configure_scenario("my_scenario", [task_config], frequency=Frequency.DAILY)
  168. # Create a scenario in development mode with the doppelganger function
  169. with patch("sys.argv", ["prog"]):
  170. core = Core()
  171. core.run()
  172. scenario = _ScenarioManager._create(scenario_config)
  173. taipy.submit(scenario)
  174. core.stop()
  175. # Delete the twice_doppelganger function
  176. # and clear cache of _load_fct() to simulate a new run
  177. del globals()["twice_doppelganger"]
  178. _load_fct.cache_clear()
  179. # Create a scenario in development mode with another function
  180. scenario_config = config_scenario()
  181. with patch("sys.argv", ["prog"]):
  182. core = Core()
  183. core.run()
  184. scenario = _ScenarioManager._create(scenario_config)
  185. taipy.submit(scenario)
  186. core.stop()
  187. def test_version_number_when_switching_mode():
  188. with patch("sys.argv", ["prog", "--development"]):
  189. core = Core()
  190. core.run()
  191. ver_1 = _VersionManager._get_latest_version()
  192. ver_dev = _VersionManager._get_development_version()
  193. assert ver_1 == ver_dev
  194. assert len(_VersionManager._get_all()) == 1
  195. core.stop()
  196. # Run with dev mode, the version number is the same
  197. with patch("sys.argv", ["prog", "--development"]):
  198. core = Core()
  199. core.run()
  200. ver_2 = _VersionManager._get_latest_version()
  201. assert ver_2 == ver_dev
  202. assert len(_VersionManager._get_all()) == 1
  203. core.stop()
  204. # When run with experiment mode, a new version is created
  205. with patch("sys.argv", ["prog", "--experiment"]):
  206. core = Core()
  207. core.run()
  208. ver_3 = _VersionManager._get_latest_version()
  209. assert ver_3 != ver_dev
  210. assert len(_VersionManager._get_all()) == 2
  211. core.stop()
  212. with patch("sys.argv", ["prog", "--experiment", "2.1"]):
  213. core = Core()
  214. core.run()
  215. ver_4 = _VersionManager._get_latest_version()
  216. assert ver_4 == "2.1"
  217. assert len(_VersionManager._get_all()) == 3
  218. core.stop()
  219. with patch("sys.argv", ["prog", "--experiment"]):
  220. core = Core()
  221. core.run()
  222. ver_5 = _VersionManager._get_latest_version()
  223. assert ver_5 != ver_3
  224. assert ver_5 != ver_4
  225. assert ver_5 != ver_dev
  226. assert len(_VersionManager._get_all()) == 4
  227. core.stop()
  228. # When run with production mode, the latest version is used as production
  229. with patch("sys.argv", ["prog", "--production"]):
  230. core = Core()
  231. core.run()
  232. ver_6 = _VersionManager._get_latest_version()
  233. production_versions = _VersionManager._get_production_versions()
  234. assert ver_6 == ver_5
  235. assert production_versions == [ver_6]
  236. assert len(_VersionManager._get_all()) == 4
  237. core.stop()
  238. # When run with production mode, the "2.1" version is used as production
  239. with patch("sys.argv", ["prog", "--production", "2.1"]):
  240. core = Core()
  241. core.run()
  242. ver_7 = _VersionManager._get_latest_version()
  243. production_versions = _VersionManager._get_production_versions()
  244. assert ver_7 == "2.1"
  245. assert production_versions == [ver_6, ver_7]
  246. assert len(_VersionManager._get_all()) == 4
  247. core.stop()
  248. # Run with dev mode, the version number is the same as the first dev version to override it
  249. with patch("sys.argv", ["prog", "--development"]):
  250. core = Core()
  251. core.run()
  252. ver_7 = _VersionManager._get_latest_version()
  253. assert ver_1 == ver_7
  254. assert len(_VersionManager._get_all()) == 4
  255. core.stop()
  256. def test_production_mode_load_all_entities_from_previous_production_version():
  257. scenario_config = config_scenario()
  258. with patch("sys.argv", ["prog", "--development"]):
  259. core = Core()
  260. core.run()
  261. scenario = _ScenarioManager._create(scenario_config)
  262. taipy.submit(scenario)
  263. core.stop()
  264. with patch("sys.argv", ["prog", "--production", "1.0"]):
  265. core = Core()
  266. core.run()
  267. production_ver_1 = _VersionManager._get_latest_version()
  268. assert _VersionManager._get_production_versions() == [production_ver_1]
  269. # When run production mode on a new app, a dev version is created alongside
  270. assert _VersionManager._get_development_version() not in _VersionManager._get_production_versions()
  271. assert len(_VersionManager._get_all()) == 2
  272. scenario = _ScenarioManager._create(scenario_config)
  273. taipy.submit(scenario)
  274. assert len(_DataManager._get_all()) == 2
  275. assert len(_TaskManager._get_all()) == 1
  276. assert len(_SequenceManager._get_all()) == 1
  277. assert len(_ScenarioManager._get_all()) == 1
  278. assert len(_CycleManager._get_all()) == 1
  279. assert len(_JobManager._get_all()) == 1
  280. core.stop()
  281. with patch("sys.argv", ["prog", "--production", "2.0"]):
  282. core = Core()
  283. core.run()
  284. production_ver_2 = _VersionManager._get_latest_version()
  285. assert _VersionManager._get_production_versions() == [production_ver_1, production_ver_2]
  286. assert len(_VersionManager._get_all()) == 3
  287. # All entities from previous production version should be saved
  288. scenario = _ScenarioManager._create(scenario_config)
  289. taipy.submit(scenario)
  290. assert len(_DataManager._get_all()) == 4
  291. assert len(_TaskManager._get_all()) == 2
  292. assert len(_SequenceManager._get_all()) == 2
  293. assert len(_ScenarioManager._get_all()) == 2
  294. assert len(_CycleManager._get_all()) == 1
  295. assert len(_JobManager._get_all()) == 2
  296. core.stop()
  297. def test_force_override_experiment_version():
  298. scenario_config = config_scenario()
  299. with patch("sys.argv", ["prog", "--experiment", "1.0"]):
  300. core = Core()
  301. core.run()
  302. ver_1 = _VersionManager._get_latest_version()
  303. assert ver_1 == "1.0"
  304. # When create new experiment version, a development version entity is also created as a placeholder
  305. assert len(_VersionManager._get_all()) == 2 # 2 version include 1 experiment 1 development
  306. scenario = _ScenarioManager._create(scenario_config)
  307. taipy.submit(scenario)
  308. assert len(_DataManager._get_all()) == 2
  309. assert len(_TaskManager._get_all()) == 1
  310. assert len(_SequenceManager._get_all()) == 1
  311. assert len(_ScenarioManager._get_all()) == 1
  312. assert len(_CycleManager._get_all()) == 1
  313. assert len(_JobManager._get_all()) == 1
  314. core.stop()
  315. Config.configure_global_app(foo="bar")
  316. # Without --taipy-force parameter, a SystemExit will be raised
  317. with pytest.raises(SystemExit):
  318. with patch("sys.argv", ["prog", "--experiment", "1.0"]):
  319. core = Core()
  320. core.run()
  321. core.stop()
  322. # With --taipy-force parameter
  323. with patch("sys.argv", ["prog", "--experiment", "1.0", "--taipy-force"]):
  324. core = Core()
  325. core.run()
  326. ver_2 = _VersionManager._get_latest_version()
  327. assert ver_2 == "1.0"
  328. assert len(_VersionManager._get_all()) == 2 # 2 version include 1 experiment 1 development
  329. # All entities from previous submit should be saved
  330. scenario = _ScenarioManager._create(scenario_config)
  331. taipy.submit(scenario)
  332. assert len(_DataManager._get_all()) == 4
  333. assert len(_TaskManager._get_all()) == 2
  334. assert len(_SequenceManager._get_all()) == 2
  335. assert len(_ScenarioManager._get_all()) == 2
  336. assert len(_CycleManager._get_all()) == 1
  337. assert len(_JobManager._get_all()) == 2
  338. core.stop()
  339. def test_force_override_production_version():
  340. scenario_config = config_scenario()
  341. with patch("sys.argv", ["prog", "--production", "1.0"]):
  342. core = Core()
  343. core.run()
  344. ver_1 = _VersionManager._get_latest_version()
  345. production_versions = _VersionManager._get_production_versions()
  346. assert ver_1 == "1.0"
  347. assert production_versions == ["1.0"]
  348. # When create new production version, a development version entity is also created as a placeholder
  349. assert len(_VersionManager._get_all()) == 2 # 2 version include 1 production 1 development
  350. scenario = _ScenarioManager._create(scenario_config)
  351. taipy.submit(scenario)
  352. assert len(_DataManager._get_all()) == 2
  353. assert len(_TaskManager._get_all()) == 1
  354. assert len(_SequenceManager._get_all()) == 1
  355. assert len(_ScenarioManager._get_all()) == 1
  356. assert len(_CycleManager._get_all()) == 1
  357. assert len(_JobManager._get_all()) == 1
  358. core.stop()
  359. Config.configure_global_app(foo="bar")
  360. # Without --taipy-force parameter, a SystemExit will be raised
  361. with pytest.raises(SystemExit):
  362. with patch("sys.argv", ["prog", "--production", "1.0"]):
  363. core = Core()
  364. core.run()
  365. core.stop()
  366. # With --taipy-force parameter
  367. with patch("sys.argv", ["prog", "--production", "1.0", "--taipy-force"]):
  368. core = Core()
  369. core.run()
  370. ver_2 = _VersionManager._get_latest_version()
  371. assert ver_2 == "1.0"
  372. assert len(_VersionManager._get_all()) == 2 # 2 version include 1 production 1 development
  373. # All entities from previous submit should be saved
  374. scenario = _ScenarioManager._create(scenario_config)
  375. taipy.submit(scenario)
  376. assert len(_DataManager._get_all()) == 4
  377. assert len(_TaskManager._get_all()) == 2
  378. assert len(_SequenceManager._get_all()) == 2
  379. assert len(_ScenarioManager._get_all()) == 2
  380. assert len(_CycleManager._get_all()) == 1
  381. assert len(_JobManager._get_all()) == 2
  382. core.stop()
  383. @pytest.mark.standalone
  384. def test_modified_job_configuration_dont_block_application_run(caplog, init_config):
  385. scenario_config = config_scenario()
  386. with patch("sys.argv", ["prog", "--experiment", "1.0"]):
  387. core = Core()
  388. Config.configure_job_executions(mode="development")
  389. core.run(force_restart=True)
  390. scenario = _ScenarioManager._create(scenario_config)
  391. jobs = taipy.submit(scenario).jobs
  392. assert all(job.is_finished() for job in jobs)
  393. core.stop()
  394. init_config()
  395. scenario_config = config_scenario()
  396. with patch("sys.argv", ["prog", "--experiment", "1.0"]):
  397. core = Core()
  398. Config.configure_job_executions(mode="standalone", max_nb_of_workers=3)
  399. core.run(force_restart=True)
  400. scenario = _ScenarioManager._create(scenario_config)
  401. jobs = taipy.submit(scenario).jobs
  402. assert_true_after_time(lambda: all(job.is_finished() for job in jobs))
  403. error_message = str(caplog.text)
  404. assert 'JOB "mode" was modified' in error_message
  405. assert 'JOB "max_nb_of_workers" was added' in error_message
  406. core.stop()
  407. @pytest.mark.standalone
  408. def test_modified_config_properties_without_force(caplog, init_config):
  409. scenario_config = config_scenario()
  410. with patch("sys.argv", ["prog", "--experiment", "1.0"]):
  411. core = Core()
  412. core.run()
  413. scenario = _ScenarioManager._create(scenario_config)
  414. taipy.submit(scenario)
  415. core.stop()
  416. init_config()
  417. scenario_config_2 = config_scenario_2()
  418. with pytest.raises(SystemExit):
  419. with patch("sys.argv", ["prog", "--experiment", "1.0"]):
  420. core = Core()
  421. core.run()
  422. scenario = _ScenarioManager._create(scenario_config_2)
  423. taipy.submit(scenario)
  424. core.stop()
  425. error_message = str(caplog.text)
  426. assert 'DATA_NODE "d3" was added' in error_message
  427. assert 'JOB "max_nb_of_workers" was added' in error_message
  428. assert 'DATA_NODE "d0" was removed' in error_message
  429. assert 'DATA_NODE "d2" has attribute "default_path" modified' in error_message
  430. assert 'CORE "root_folder" was modified' in error_message
  431. assert 'CORE "repository_type" was modified' in error_message
  432. assert 'JOB "mode" was modified' in error_message
  433. assert 'SCENARIO "my_scenario" has attribute "frequency" modified' in error_message
  434. assert 'SCENARIO "my_scenario" has attribute "tasks" modified' in error_message
  435. assert 'TASK "my_task" has attribute "inputs" modified' in error_message
  436. assert 'TASK "my_task" has attribute "function" modified' in error_message
  437. assert 'TASK "my_task" has attribute "outputs" modified' in error_message
  438. assert 'DATA_NODE "d2" has attribute "has_header" modified' in error_message
  439. assert 'DATA_NODE "d2" has attribute "exposed_type" modified' in error_message
  440. assert 'CORE "repository_properties" was added' in error_message
  441. def twice(a):
  442. return a * 2
  443. def config_scenario():
  444. Config.configure_data_node(id="d0")
  445. data_node_1_config = Config.configure_data_node(
  446. id="d1", storage_type="pickle", default_data="abc", scope=Scope.SCENARIO
  447. )
  448. data_node_2_config = Config.configure_data_node(id="d2", storage_type="csv", default_path="foo.csv")
  449. task_config = Config.configure_task("my_task", twice, data_node_1_config, data_node_2_config)
  450. scenario_config = Config.configure_scenario("my_scenario", [task_config], frequency=Frequency.DAILY)
  451. scenario_config.add_sequences({"my_sequence": [task_config]})
  452. return scenario_config
  453. def double_twice(a):
  454. return a * 2, a * 2
  455. def config_scenario_2():
  456. Config.configure_core(
  457. root_folder="foo_root",
  458. # Changing the "storage_folder" will fail since older versions are stored in older folder
  459. # storage_folder="foo_storage",
  460. repository_type="bar",
  461. repository_properties={"foo": "bar"},
  462. )
  463. Config.configure_job_executions(mode="standalone", max_nb_of_workers=3)
  464. data_node_1_config = Config.configure_data_node(
  465. id="d1", storage_type="pickle", default_data="abc", scope=Scope.SCENARIO
  466. )
  467. # Modify properties of "d2"
  468. data_node_2_config = Config.configure_data_node(
  469. id="d2", storage_type="csv", default_path="bar.csv", has_header=False, exposed_type="numpy"
  470. )
  471. # Add new data node "d3"
  472. data_node_3_config = Config.configure_data_node(
  473. id="d3", storage_type="csv", default_path="baz.csv", has_header=False, exposed_type="numpy"
  474. )
  475. # Modify properties of "my_task", including the function and outputs list
  476. Config.configure_task("my_task", double_twice, data_node_3_config, [data_node_1_config, data_node_2_config])
  477. task_config_1 = Config.configure_task("my_task_1", double_twice, data_node_3_config, [data_node_2_config])
  478. # Modify properties of "my_scenario", where tasks is now my_task_1
  479. scenario_config = Config.configure_scenario("my_scenario", [task_config_1], frequency=Frequency.MONTHLY)
  480. scenario_config.add_sequences({"my_sequence": [task_config_1]})
  481. return scenario_config