Ver Fonte

Feature/#392 - Sort mechanism for `get_primary_scenarios()` and `get_scenarios()` API (#1042)

* Implemented sort mechanism

* fix: remove default value usages from CoreSection.__init__

* refactor: merge JobConfig._config with the _properties

* fix: JobConfigChecker should check the execution mode

* Update config.pyi

* fix: reset the job configuration mode after the test

* fix: reset the repository type after the test

* clean C408 rule for codestyle

* Fix Builder API Default Property (#994) (#1034)

* Revert "Fix Builder API Default Property (#994) (#1034)"

This reverts commit 9e848994270b4d832792a96cbabf4d58ff325075.

* Revert "clean C408 rule for codestyle"

This reverts commit 56d0564f00c2fb3233d6758f7fe44ac52e8ac6c6.

* Revert "fix: reset the repository type after the test"

This reverts commit 55442c7032254c239d73403c7fded79eec90718e.

* Revert "fix: reset the job configuration mode after the test"

This reverts commit f5b035b3c4dcce4570c9def0e1c76d70a8c0e370.

* Revert "Update config.pyi"

This reverts commit c8cb56d053aad50dd38a44166724d8a830a74ce3.

* Revert "fix: JobConfigChecker should check the execution mode"

This reverts commit 536e4e8525b7f740c7d6b140b2d7e49bad25abef.

* Revert "refactor: merge JobConfig._config with the _properties"

This reverts commit 0a872e4e3390363149c4f2229e96fa4fad8c8ed5.

* Revert "fix: remove default value usages from CoreSection.__init__"

This reverts commit b0fc717b89c8ed15c5730f455d85ffedc957fd82.

* Update taipy/core/taipy.py

Co-authored-by: Đỗ Trường Giang <dtr.giang.1299@gmail.com>

* Sort mechanism: added second sorting criterion, added config_id, added and rewritten test cases

* Update taipy/core/taipy.py

Co-authored-by: Jean-Robin <jeanrobin.medori@avaiga.com>

* Update taipy/core/taipy.py

Co-authored-by: Jean-Robin <jeanrobin.medori@avaiga.com>

* Update taipy/core/taipy.py

Co-authored-by: Jean-Robin <jeanrobin.medori@avaiga.com>

* Update taipy/core/taipy.py

Co-authored-by: Jean-Robin <jeanrobin.medori@avaiga.com>

* Update taipy/core/taipy.py

Co-authored-by: Jean-Robin <jeanrobin.medori@avaiga.com>

* Update tests/core/scenario/test_scenario_manager.py

Co-authored-by: Jean-Robin <jeanrobin.medori@avaiga.com>

* Removed code duplication, added test cases

* Apply suggestions from code review

docstring documentation formatting

* removed optionality of descending bool

* Update taipy/core/scenario/_scenario_manager.py

Co-authored-by: Đỗ Trường Giang <dtr.giang.1299@gmail.com>

* Update taipy/core/taipy.py

Co-authored-by: Đỗ Trường Giang <dtr.giang.1299@gmail.com>

* Update taipy/core/taipy.py

Co-authored-by: Đỗ Trường Giang <dtr.giang.1299@gmail.com>

* Update taipy/core/taipy.py

Co-authored-by: Đỗ Trường Giang <dtr.giang.1299@gmail.com>

* Update taipy/core/taipy.py

Co-authored-by: Đỗ Trường Giang <dtr.giang.1299@gmail.com>

* Update taipy/core/taipy.py

Co-authored-by: Jean-Robin <jeanrobin.medori@avaiga.com>

* Update taipy/core/taipy.py

Co-authored-by: Jean-Robin <jeanrobin.medori@avaiga.com>

* Update taipy/core/taipy.py

Co-authored-by: Jean-Robin <jeanrobin.medori@avaiga.com>

* Update taipy/core/taipy.py

Co-authored-by: Jean-Robin <jeanrobin.medori@avaiga.com>

* Update tests/core/scenario/test_scenario_manager.py

Co-authored-by: Jean-Robin <jeanrobin.medori@avaiga.com>

* Update tests/core/scenario/test_scenario_manager.py

Co-authored-by: Jean-Robin <jeanrobin.medori@avaiga.com>

* Update tests/core/scenario/test_scenario_manager.py

Co-authored-by: Jean-Robin <jeanrobin.medori@avaiga.com>

* Update tests/core/scenario/test_scenario_manager.py

Co-authored-by: Jean-Robin <jeanrobin.medori@avaiga.com>

* Update tests/core/scenario/test_scenario_manager.py

Co-authored-by: Jean-Robin <jeanrobin.medori@avaiga.com>

* Update tests/core/scenario/test_scenario_manager.py

Co-authored-by: Jean-Robin <jeanrobin.medori@avaiga.com>

* Update tests/core/scenario/test_scenario_manager.py

Co-authored-by: Jean-Robin <jeanrobin.medori@avaiga.com>

* Update tests/core/test_taipy.py

Co-authored-by: Jean-Robin <jeanrobin.medori@avaiga.com>

* Update tests/core/scenario/test_scenario_manager.py

Co-authored-by: Jean-Robin <jeanrobin.medori@avaiga.com>

* Update tests/core/scenario/test_scenario_manager.py

Co-authored-by: Jean-Robin <jeanrobin.medori@avaiga.com>

* Update tests/core/test_taipy.py

Co-authored-by: Jean-Robin <jeanrobin.medori@avaiga.com>

* Fixing GitHub Issue: Removed Incorrectly Added Lines

* Fixed format error: trailing white-space

* fix scenario creation date on tests.

---------

Co-authored-by: trgiangdo <dtr.giang.1299@gmail.com>
Co-authored-by: trgiangdo <trgiangdo@users.noreply.github.com>
Co-authored-by: ooooo <3164076421@qq.com>
Co-authored-by: Dinh Long Nguyen <dinhlongviolin1@gmail.com>
Co-authored-by: Đỗ Trường Giang <do.giang@avaiga.com>
Co-authored-by: Jean-Robin <jeanrobin.medori@avaiga.com>
Luke-0162 há 1 ano atrás
pai
commit
31f2361957

+ 1 - 0
contributors.txt

@@ -14,3 +14,4 @@ enarroied
 bobbyshermi
 Forchapeatl
 yarikoptic
+Luke-0162

+ 19 - 1
taipy/core/scenario/_scenario_manager.py

@@ -11,7 +11,7 @@
 
 import datetime
 from functools import partial
-from typing import Any, Callable, List, Optional, Union
+from typing import Any, Callable, List, Literal, Optional, Union
 
 from taipy.config import Config
 
@@ -270,6 +270,24 @@ class _ScenarioManager(_Manager[Scenario], _VersionMixin):
     def _get_primary_scenarios(cls) -> List[Scenario]:
         return [scenario for scenario in cls._get_all() if scenario.is_primary]
 
+    @classmethod
+    def _sort_scenarios(
+        cls,
+        scenarios: List[Scenario],
+        descending: bool = False,
+        sort_key: Literal["name", "id", "config_id", "creation_date", "tags"] = "name",
+    ) -> List[Scenario]:
+        if sort_key in ["name", "config_id", "creation_date", "tags"]:
+            if sort_key == "tags":
+                scenarios.sort(key=lambda x: (tuple(sorted(x.tags)), x.id), reverse=descending)
+            else:
+                scenarios.sort(key=lambda x: (getattr(x, sort_key), x.id), reverse=descending)
+        elif sort_key == "id":
+            scenarios.sort(key=lambda x: x.id, reverse=descending)
+        else:
+            scenarios.sort(key=lambda x: (x.name, x.id), reverse=descending)
+        return scenarios
+
     @classmethod
     def _is_promotable_to_primary(cls, scenario: Union[Scenario, ScenarioId]) -> bool:
         if isinstance(scenario, str):

+ 52 - 15
taipy/core/taipy.py

@@ -13,7 +13,7 @@ import os
 import pathlib
 import shutil
 from datetime import datetime
-from typing import Any, Callable, Dict, List, Optional, Set, Union, overload
+from typing import Any, Callable, Dict, List, Literal, Optional, Set, Union, overload
 
 from taipy.config import Config, Scope
 from taipy.logger._taipy_logger import _TaipyLogger
@@ -508,7 +508,13 @@ def delete(entity_id: Union[TaskId, DataNodeId, SequenceId, ScenarioId, JobId, C
     raise ModelNotFound("NOT_DETERMINED", entity_id)
 
 
-def get_scenarios(cycle: Optional[Cycle] = None, tag: Optional[str] = None) -> List[Scenario]:
+def get_scenarios(
+    cycle: Optional[Cycle] = None,
+    tag: Optional[str] = None,
+    is_sorted: bool = False,
+    descending: bool = False,
+    sort_key: Literal["name", "id", "config_id", "creation_date", "tags"] = "name",
+) -> List[Scenario]:
     """Retrieve a list of existing scenarios filtered by cycle or tag.
 
     This function allows you to retrieve a list of scenarios based on optional
@@ -519,22 +525,34 @@ def get_scenarios(cycle: Optional[Cycle] = None, tag: Optional[str] = None) -> L
     Parameters:
          cycle (Optional[Cycle^]): The optional `Cycle^` to filter scenarios by.
          tag (Optional[str]): The optional tag to filter scenarios by.
+         is_sorted (bool): The option to sort scenarios. The default sorting key is name.
+         descending (bool): The option to sort scenarios on the sorting key in descending order.
+         sort_key (Literal["name", "id", "creation_date", "tags"]): The optiononal sort_key to
+             decide upon what key scenarios are sorted. The sorting is in increasing order for
+             dates, in alphabetical order for name and id, in lexographical order for tags.
 
     Returns:
-        The list of scenarios filtered by cycle or tag. If no filtering criteria
-            are provided, this method returns all existing scenarios.
+        The list of scenarios filtered by cycle or tag and optionally sorted by name, id, creation_date or tags.
+            If no filtering criterion is provided, this method returns all existing scenarios.
+            If is_sorted is set to true, the scenarios are sorted by sort_key. The scenarios
+            are sorted by name if an incorrect or no sort_key is provided.
     """
     scenario_manager = _ScenarioManagerFactory._build_manager()
     if not cycle and not tag:
-        return scenario_manager._get_all()
-    if cycle and not tag:
-        return scenario_manager._get_all_by_cycle(cycle)
-    if not cycle and tag:
-        return scenario_manager._get_all_by_tag(tag)
-    if cycle and tag:
+        scenarios = scenario_manager._get_all()
+    elif cycle and not tag:
+        scenarios = scenario_manager._get_all_by_cycle(cycle)
+    elif not cycle and tag:
+        scenarios = scenario_manager._get_all_by_tag(tag)
+    elif cycle and tag:
         cycles_scenarios = scenario_manager._get_all_by_cycle(cycle)
-        return [scenario for scenario in cycles_scenarios if scenario.has_tag(tag)]
-    return []
+        scenarios = [scenario for scenario in cycles_scenarios if scenario.has_tag(tag)]
+    else:
+        scenarios = []
+
+    if is_sorted:
+        scenario_manager._sort_scenarios(scenarios, descending, sort_key)
+    return scenarios
 
 
 def get_primary(cycle: Cycle) -> Optional[Scenario]:
@@ -550,13 +568,32 @@ def get_primary(cycle: Cycle) -> Optional[Scenario]:
     return _ScenarioManagerFactory._build_manager()._get_primary(cycle)
 
 
-def get_primary_scenarios() -> List[Scenario]:
+def get_primary_scenarios(
+    is_sorted: bool = False,
+    descending: bool = False,
+    sort_key: Literal["name", "id", "config_id", "creation_date", "tags"] = "name",
+) -> List[Scenario]:
     """Retrieve a list of all primary scenarios.
 
+    Parameters:
+         is_sorted (bool): The option to sort scenarios. The default sorting key is name.
+         descending (bool): The option to sort scenarios on the sorting key in descending order.
+         sort_key (Literal["name", "id", "creation_date", "tags"]): The optiononal sort_key to
+             decide upon what key scenarios are sorted. The sorting is in increasing order for
+             dates, in alphabetical order for name and id, in lexographical order for tags.
+
     Returns:
-        A list containing all primary scenarios.
+        The list containing all primary scenarios, optionally sorted by name, id, creation_date or tags.
+            The sorting is in increasing order for dates, in alphabetical order for name and
+            id, and in lexicographical order for tags. If sorted is set to true, but if an
+            incorrect or no sort_key is provided, the scenarios are sorted by name.
     """
-    return _ScenarioManagerFactory._build_manager()._get_primary_scenarios()
+    scenario_manager = _ScenarioManagerFactory._build_manager()
+    scenarios = scenario_manager._get_primary_scenarios()
+    if is_sorted:
+        scenario_manager._sort_scenarios(scenarios, descending, sort_key)
+    return scenarios
+
 
 
 def is_promotable(scenario: Union[Scenario, ScenarioId]) -> bool:

+ 72 - 0
tests/core/scenario/test_scenario_manager.py

@@ -19,6 +19,7 @@ from taipy.config.common.frequency import Frequency
 from taipy.config.common.scope import Scope
 from taipy.config.config import Config
 from taipy.core import Job
+from taipy.core import taipy as tp
 from taipy.core._orchestrator._orchestrator import _Orchestrator
 from taipy.core._version._version_manager import _VersionManager
 from taipy.core.common import _utils
@@ -813,6 +814,77 @@ def test_get_set_primary_scenario():
     assert _ScenarioManager._get_primary(cycle_1) == scenario_2
 
 
+def test_get_primary_scenarios_sorted():
+    scenario_1_cfg = Config.configure_scenario(id="scenario_1", frequency=Frequency.DAILY)
+    scenario_2_cfg = Config.configure_scenario(id="scenario_2", frequency=Frequency.DAILY)
+
+    not_primary_scenario = _ScenarioManager._create(scenario_1_cfg, name="not_primary_scenario")
+    now = datetime.now()
+    scenario_1 = _ScenarioManager._create(scenario_1_cfg, now, "B_scenario")
+    scenario_2 = _ScenarioManager._create(scenario_2_cfg, now + timedelta(days=2), "A_scenario")
+    scenario_3 = _ScenarioManager._create(scenario_2_cfg, now + timedelta(days=4), "C_scenario")
+    scenario_4 = _ScenarioManager._create(scenario_2_cfg, now + timedelta(days=3), "D_scenario")
+
+    _ScenarioManager._set_primary(scenario_1)
+    scenario_1.tags = ["banana", "kiwi"]
+    _ScenarioManager._set_primary(scenario_2)
+    scenario_2.tags = ["apple", "banana"]
+    _ScenarioManager._set_primary(scenario_3)
+    scenario_3.tags = ["banana", "kiwi"]
+    _ScenarioManager._set_primary(scenario_4)
+
+    all_scenarios = tp.get_scenarios()
+    assert not_primary_scenario in all_scenarios
+
+    primary_scenarios = _ScenarioManager._get_primary_scenarios()
+    assert not_primary_scenario not in primary_scenarios
+
+    primary_scenarios_sorted_by_name = [scenario_2, scenario_1, scenario_3, scenario_4]
+    assert primary_scenarios_sorted_by_name == _ScenarioManager._sort_scenarios(
+        primary_scenarios, descending=False, sort_key="name"
+    )
+
+    scenarios_with_same_config_id = [scenario_2, scenario_3, scenario_4]
+    scenarios_with_same_config_id.sort(key=lambda x: x.id)
+    primary_scenarios_sorted_by_config_id = [
+        scenario_1,
+        scenarios_with_same_config_id[0],
+        scenarios_with_same_config_id[1],
+        scenarios_with_same_config_id[2],
+    ]
+    assert primary_scenarios_sorted_by_config_id == _ScenarioManager._sort_scenarios(
+        primary_scenarios, descending=False, sort_key="config_id"
+    )
+
+    scenarios_sorted_by_id = [scenario_1, scenario_2, scenario_3, scenario_4]
+    scenarios_sorted_by_id.sort(key=lambda x: x.id)
+    assert scenarios_sorted_by_id == _ScenarioManager._sort_scenarios(
+        primary_scenarios, descending=False, sort_key="id"
+    )
+
+    primary_scenarios_sorted_by_creation_date = [scenario_1, scenario_2, scenario_4, scenario_3]
+    assert primary_scenarios_sorted_by_creation_date == _ScenarioManager._sort_scenarios(
+        primary_scenarios, descending=False, sort_key="creation_date"
+    )
+
+    scenarios_with_same_tags = [scenario_1, scenario_3]
+    scenarios_with_same_tags.sort(key=lambda x: x.id)
+    primary_scenarios_sorted_by_tags = [
+        scenario_4,
+        scenario_2,
+        scenarios_with_same_tags[0],
+        scenarios_with_same_tags[1],
+    ]
+    assert primary_scenarios_sorted_by_tags == _ScenarioManager._sort_scenarios(
+        primary_scenarios, descending=False, sort_key="tags"
+    )
+
+    primary_scenarios_sorted_by_name_descending_order = [scenario_4, scenario_3, scenario_1, scenario_2]
+    assert primary_scenarios_sorted_by_name_descending_order == _ScenarioManager._sort_scenarios(
+        primary_scenarios, descending=True, sort_key="name"
+    )
+
+
 def test_hard_delete_one_single_scenario_with_scenario_data_nodes():
     dn_input_config = Config.configure_data_node("my_input", "in_memory", scope=Scope.SCENARIO, default_data="testing")
     dn_output_config = Config.configure_data_node("my_output", "in_memory", scope=Scope.SCENARIO)

+ 47 - 0
tests/core/test_taipy.py

@@ -432,6 +432,53 @@ class TestTaipy:
             tp.get_scenarios(tag="tag")
             mck.assert_called_once_with("tag")
 
+    def test_get_scenarios_sorted(self):
+        scenario_1_cfg = Config.configure_scenario(id="scenario_1")
+        scenario_2_cfg = Config.configure_scenario(id="scenario_2")
+
+        now = datetime.datetime.now() + datetime.timedelta(seconds=1)
+        scenario_1 = _ScenarioManager._create(scenario_1_cfg, now, "B_scenario")
+        scenario_2 = _ScenarioManager._create(scenario_2_cfg, now + datetime.timedelta(seconds=1), "C_scenario")
+        scenario_3 = _ScenarioManager._create(scenario_2_cfg, now + datetime.timedelta(seconds=2), "A_scenario")
+        scenario_4 = _ScenarioManager._create(scenario_2_cfg, now + datetime.timedelta(seconds=3), "D_scenario")
+
+        _ScenarioManager._tag(scenario_1, "banana")
+        _ScenarioManager._tag(scenario_1, "kiwi")  # scenario_1 now has tags {"banana", "kiwi"}
+        _ScenarioManager._tag(scenario_2, "apple")
+        _ScenarioManager._tag(scenario_2, "banana")  # scenario_2 now has tags {"banana", "apple"}
+        _ScenarioManager._tag(scenario_3, "apple")
+        _ScenarioManager._tag(scenario_3, "kiwi")  # scenario_3 now has tags {"kiwi", "apple"}
+
+        scenarios_sorted_by_name = [scenario_3, scenario_1, scenario_2, scenario_4]
+        assert scenarios_sorted_by_name == tp.get_scenarios(is_sorted=True, sort_key="name")
+        assert scenarios_sorted_by_name == tp.get_scenarios(is_sorted=True, sort_key="wrong_sort_key")
+
+        scenarios_with_same_config_id = [scenario_2, scenario_3, scenario_4]
+        scenarios_with_same_config_id.sort(key=lambda x: x.id)
+        scenarios_sorted_by_config_id = [
+            scenario_1,
+            scenarios_with_same_config_id[0],
+            scenarios_with_same_config_id[1],
+            scenarios_with_same_config_id[2],
+        ]
+        assert scenarios_sorted_by_config_id == tp.get_scenarios(is_sorted=True, sort_key="config_id")
+
+        scenarios_sorted_by_id = [scenario_1, scenario_2, scenario_3, scenario_4]
+        scenarios_sorted_by_id.sort(key=lambda x: x.id)
+        assert scenarios_sorted_by_id == tp.get_scenarios(is_sorted=True, sort_key="id")
+
+        scenarios_sorted_by_creation_date = [scenario_1, scenario_2, scenario_3, scenario_4]
+        assert scenarios_sorted_by_creation_date == tp.get_scenarios(is_sorted=True, sort_key="creation_date")
+
+        # Note: the scenario without any tags comes first.
+        scenarios_sorted_by_tag = [scenario_4, scenario_2, scenario_3, scenario_1]
+        assert scenarios_sorted_by_tag == tp.get_scenarios(is_sorted=True, sort_key="tags")
+
+        scenarios_sorted_by_name_descending_order = [scenario_4, scenario_2, scenario_1, scenario_3]
+        assert scenarios_sorted_by_name_descending_order == tp.get_scenarios(
+            is_sorted=True, descending=True, sort_key="name"
+        )
+
     def test_get_scenario(self, scenario):
         with mock.patch("taipy.core.scenario._scenario_manager._ScenarioManager._get") as mck:
             scenario_id = ScenarioId("SCENARIO_id")