Repository: Avaiga/taipy
Pull request: Feature/#392 - Sort mechanism for `get_primary_scenarios()` and `get_scenarios()` API

Problem statement:
Sort mechanism for get_all_scenarios and get_primaries functions
### Description

When calling `tp.get_scenarios()` or `tp.get_primary_scenarios()`, the user should be able to provide an optional field for sorting the scenarios.
The sort should be increasing or decreasing.
The sort could be on the name (default), id, creation_date, or tag.

Hints from issue discussion:
I would like to contribute to this issue. Could you please assign it to me?
Hello @Luke-0162, thank you for joining our project. I assigned the issue to you.

Please let me know if you need more information about the issue.
Hello @trgiangdo, thank you for your response. I am looking forward to solve this issue.

Could you please help clarify which functions or methods I will need to edit exactly in order to resolve this issue? I have been examining the _fetch_config_ method within the _scenario.py_ file, but I want to make sure I am on the right track.
In https://github.com/Avaiga/taipy/blob/develop/taipy/core/taipy.py, you can find the `get_scenarios()` and `get_primary_scenarios()` functions.
These 2 functions return a list of scenarios.

You should add 2 new parameters to this 2 functions, first is `sorted: bool` which indicates if the returned lists of scenarios is sorted or not, and second is the `sort_key: str` which indicates which key or attribute is used to sort the scenario.

The default value of the `sort_key` can be discuss latter.

For example, it should be something like:
```python
def get_scenarios(
    cycle: Optional[Cycle] = None,
    tag: Optional[str] = None,
    sorted: bool = False,
    sort_key: str = "creation_date",
) -> List[Scenario]:
    ...
    # Get the list of scenarios (already implemented)
    ...
    # Sort the scenario list and return the list
```

Similarly for the `get_primary_scenarios()` method

Hello @trgiangdo, 

Once again, thanks a lot for helping me out.  I have completed the implementation of the functions as discussed. Additionally, I have made a local test project which uses Taipy. This setup aims to allow me to test the new implementation of the get_scenarios() and get_primary_scenarios() functions. However, when I now install my edited version of Taipy by using the command "pip install -e ." inside the directory in which I put the Taipy GitHub project and try to use my version of Taipy in the test project, the modules "Config, Scope, Gui" (which are the only ones I am using) are not recognised. Meanwhile, I noticed that the Taipy import itself is recognised. So, I was wondering if you know how this problem is fixed and more specifically what is common practice for testing quickly whether a function works.

Additionally, I'm curious about any further steps required to address this issue. Should I be considering creating unit tests?

Thank you for your time and assistance. Looking forward to your insights!
1. You should not install your version of Taipy using `pip install -e`. You can either create a separate environment for running the test and install Taipy from your edited folder, or you can run the test from the taipy folder itself.
2. Yes, there should be unittests that cover what you've done. Then you can push a PR for us to review.
Hello @trgiangdo ,

I have finished implementing the sorting mechanism into the `tp.get_scenarios()` or `tp.get_primary_scenarios()` functions. Additionally, I have added unit tests that cover my implementations. However, when I try to commit my code in my fork repository and the mypy check is triggered, all checks pass except from some checks concerning _overloaded function signature_. I  cannot commit my code despite not being the initiator of these errors, as I have only edited existing functions and have not added any additional functions (apart from the unit tests). Can you inform me on how to continue?

Once again, thanks a lot for your help!
You can try to ignore the mypy error by adding the option `-n` to the commit command. Something like
```
git commit -m "message" -n
```
I successfully committed, however, now I am trying to submit a pull-request. As this is the first time for me to do this for an open-source project, I am not sure whether I have done it correctly. Can you help me out? @trgiangdo 
We can see your PR now. Let's start working from there

Commit message: Update taipy/core/taipy.py

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

Patch to review:
diff --git a/contributors.txt b/contributors.txt
index 600daf7485..0c6fbdd083 100644
--- a/contributors.txt
+++ b/contributors.txt
@@ -14,3 +14,4 @@ enarroied
 bobbyshermi
 Forchapeatl
 yarikoptic
+Luke-0162
diff --git a/taipy/core/scenario/_scenario_manager.py b/taipy/core/scenario/_scenario_manager.py
index 515fa5bbb5..69b4ab1324 100644
--- a/taipy/core/scenario/_scenario_manager.py
+++ b/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
 
@@ -267,8 +267,16 @@ def _get_all_by_cycle(cls, cycle: Cycle) -> List[Scenario]:
         return cls._get_all_by(filters)
 
     @classmethod
-    def _get_primary_scenarios(cls) -> List[Scenario]:
-        return [scenario for scenario in cls._get_all() if scenario.is_primary]
+    def _get_primary_scenarios(
+        cls, sorted: Optional[bool] = False, sort_key: Optional[Literal["name", "id", "creation_date", "tags"]] = "name"
+    ) -> List[Scenario]:
+        scenarios = [scenario for scenario in cls._get_all() if scenario.is_primary]
+        if sorted:
+            if sort_key in ["name", "id", "creation_date", "tags"]:
+                scenarios.sort(key=lambda x: getattr(x, sort_key))
+            else:
+                scenarios.sort(key=lambda x: x.name)
+        return scenarios
 
     @classmethod
     def _is_promotable_to_primary(cls, scenario: Union[Scenario, ScenarioId]) -> bool:
diff --git a/taipy/core/taipy.py b/taipy/core/taipy.py
index d21fdbc87e..76c559cf41 100644
--- a/taipy/core/taipy.py
+++ b/taipy/core/taipy.py
@@ -12,7 +12,7 @@
 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.common.scope import Scope
 from taipy.logger._taipy_logger import _TaipyLogger
@@ -505,7 +505,12 @@ 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,
+    sorted: Optional[bool] = False,
+    sort_key: Optional[Literal["name", "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
@@ -516,22 +521,38 @@ 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.
+         sorted (Optional[bool]): If True, the returned list of scenarios is sorted by the sorting key.
+         The default value is False.
+         sort_key (Optional[Literal["name", "id", "creation_date", "tags"]]): The optional sort_key to decide upon
+         what key scenarios are sorted. The default sorting key is "name".
 
     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.
+        The sorting is in increasing order for dates, in alphabetical order for name and id
+        and in lexographical order for tags.
+        If no filtering criteria are provided, this method returns all existing scenarios.
+        If sorted is set to true, but an incorrect or no sort_key is provided, then the scenarios are sorted by name.
     """
     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 sorted:
+        if sort_key in ["name", "id", "creation_date", "tags"]:
+            scenarios.sort(key=lambda x: getattr(x, sort_key))
+        else:
+            scenarios.sort(key=lambda x: x.name)
+
+    return scenarios
 
 
 def get_primary(cycle: Cycle) -> Optional[Scenario]:
@@ -547,13 +568,23 @@ def get_primary(cycle: Cycle) -> Optional[Scenario]:
     return _ScenarioManagerFactory._build_manager()._get_primary(cycle)
 
 
-def get_primary_scenarios() -> List[Scenario]:
+def get_primary_scenarios(
+    sorted: Optional[bool] = False, sort_key: Optional[Literal["name", "id", "creation_date", "tags"]] = "name"
+) -> List[Scenario]:
     """Retrieve a list of all primary scenarios.
 
+    Parameters:
+         sorted (Optional[bool]): The option to sort scenarios. The default sorting key is name.
+         sort_key (Optional[Literal["name", "id", "creation_date", "tags"]]): The optiononal sort_key to decide
+         upon what key scenarios are sorted.
+
     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 lexographical order for tags.
+        If sorted is set to true, but an incorrect or no sort_key is provided, then the scenarios are sorted by name.
     """
-    return _ScenarioManagerFactory._build_manager()._get_primary_scenarios()
+    return _ScenarioManagerFactory._build_manager()._get_primary_scenarios(sorted, sort_key)
 
 
 def is_promotable(scenario: Union[Scenario, ScenarioId]) -> bool:
diff --git a/tests/core/scenario/test_scenario_manager.py b/tests/core/scenario/test_scenario_manager.py
index 01ecdd8b1b..7e5ee8a56b 100644
--- a/tests/core/scenario/test_scenario_manager.py
+++ b/tests/core/scenario/test_scenario_manager.py
@@ -813,6 +813,37 @@ def test_get_set_primary_scenario():
     assert _ScenarioManager._get_primary(cycle_1) == scenario_2
 
 
+def test_get_primary_scenarios_sorted():
+    cycle_1 = _CycleManager._create(Frequency.DAILY, name="foo1")
+    cycle_2 = _CycleManager._create(Frequency.DAILY, name="foo2")
+
+    scenario_1_cfg = Config.configure_scenario(name="C_scenario", id="scenario_3", tags=["banana", "kiwi"])
+    scenario_2_cfg = Config.configure_scenario(name="B_scenario", id="scenario_1", tags=["apple", "banana"])
+
+    scenario_1 = _ScenarioManager._create(scenario_1_cfg)
+    scenario_2 = _ScenarioManager._create(scenario_2_cfg)
+
+    scenario_1.cycle = cycle_1
+    scenario_2.cycle = cycle_2
+
+    _ScenarioManager._set_primary(scenario_1)
+    _ScenarioManager._set_primary(scenario_2)
+
+    primary_scenarios_sorted_by_name = [scenario_2, scenario_1]
+    assert primary_scenarios_sorted_by_name == _ScenarioManager._get_primary_scenarios(sorted=True, sort_key="name")
+
+    primary_scenarios_sorted_by_id = [scenario_2, scenario_1]
+    assert primary_scenarios_sorted_by_id == _ScenarioManager._get_primary_scenarios(sorted=True, sort_key="id")
+
+    primary_scenarios_sorted_by_creation_date = [scenario_1, scenario_2]
+    assert primary_scenarios_sorted_by_creation_date == _ScenarioManager._get_primary_scenarios(
+        sorted=True, sort_key="creation_date"
+    )
+
+    primary_scenarios_sorted_by_tag = [scenario_2, scenario_1]
+    assert primary_scenarios_sorted_by_tag == _ScenarioManager._get_primary_scenarios(sorted=True, sort_key="tags")
+
+
 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)
diff --git a/tests/core/test_taipy.py b/tests/core/test_taipy.py
index b4e92238cc..b2b43e1a65 100644
--- a/tests/core/test_taipy.py
+++ b/tests/core/test_taipy.py
@@ -435,6 +435,28 @@ def test_get_scenarios(self, cycle):
             tp.get_scenarios(tag="tag")
             mck.assert_called_once_with("tag")
 
+    def test_get_scenarios_sorted(self):
+        scenario_1_cfg = Config.configure_scenario(name="B_scenario", id="scenario_3", tags=["banana", "kiwi"])
+        scenario_2_cfg = Config.configure_scenario(name="C_scenario", id="scenario_1", tags=["apple", "banana"])
+        scenario_3_cfg = Config.configure_scenario(name="A_scenario", id="scenario_2", tags=["apple", "kiwi"])
+
+        scenario_1 = _ScenarioManager._create(scenario_1_cfg)
+        scenario_2 = _ScenarioManager._create(scenario_2_cfg)
+        scenario_3 = _ScenarioManager._create(scenario_3_cfg)
+
+        scenarios_sorted_by_name = [scenario_3, scenario_1, scenario_2]
+        assert scenarios_sorted_by_name == tp.get_scenarios(sorted=True, sort_key="name")
+        assert scenarios_sorted_by_name == tp.get_scenarios(sorted=True, sort_key="wrong_sort_key")
+
+        scenarios_sorted_by_id = [scenario_2, scenario_3, scenario_1]
+        assert scenarios_sorted_by_id == tp.get_scenarios(sorted=True, sort_key="id")
+
+        scenarios_sorted_by_creation_date = [scenario_1, scenario_2, scenario_3]
+        assert scenarios_sorted_by_creation_date == tp.get_scenarios(sorted=True, sort_key="creation_date")
+
+        scenarios_sorted_by_tag = [scenario_2, scenario_3, scenario_1]
+        assert scenarios_sorted_by_tag == tp.get_scenarios(sorted=True, sort_key="tags")
+
     def test_get_scenario(self, scenario):
         with mock.patch("taipy.core.scenario._scenario_manager._ScenarioManager._get") as mck:
             scenario_id = ScenarioId("SCENARIO_id")


Code review comments:
diff_hunk: @@ -267,8 +267,16 @@ def _get_all_by_cycle(cls, cycle: Cycle) -> List[Scenario]:
         return cls._get_all_by(filters)
 
     @classmethod
-    def _get_primary_scenarios(cls) -> List[Scenario]:
-        return [scenario for scenario in cls._get_all() if scenario.is_primary]
+    def _get_primary_scenarios(
+        cls, sorted: Optional[bool] = False, sort_key: Optional[Literal["name", "id", "creation_date", "tags"]] = "name"
path: taipy/core/scenario/_scenario_manager.py
text: @user1:
Maybe, we can sort by `config_id` as well. It has some business meaning since it is populated by a user. So it makes sense to sort by `config_id` in case of multiple scenario configurations.
We can also sort by label (which can be different from the name). 

What do you think?


@author:
Yes, I agree, it is definitely possible to include config_id and label into the options for sort_key as well. However, notice that I refactored the code and now a dedicated method  `_ScenarioManager._sort_scenarios()` takes care of sorting separately from the `_ScenarioManager_get_primary_scenarios` function. So, I will include it in this function and in the `get_scenarios` function.

@user1:
Yes, sure. Sounds Great.

diff_hunk: @@ -813,6 +813,37 @@ def test_get_set_primary_scenario():
     assert _ScenarioManager._get_primary(cycle_1) == scenario_2
 
 
+def test_get_primary_scenarios_sorted():
+    cycle_1 = _CycleManager._create(Frequency.DAILY, name="foo1")
+    cycle_2 = _CycleManager._create(Frequency.DAILY, name="foo2")
+
+    scenario_1_cfg = Config.configure_scenario(name="C_scenario", id="scenario_3", tags=["banana", "kiwi"])
+    scenario_2_cfg = Config.configure_scenario(name="B_scenario", id="scenario_1", tags=["apple", "banana"])
+
+    scenario_1 = _ScenarioManager._create(scenario_1_cfg)
+    scenario_2 = _ScenarioManager._create(scenario_2_cfg)
+
+    scenario_1.cycle = cycle_1
+    scenario_2.cycle = cycle_2
+
+    _ScenarioManager._set_primary(scenario_1)
+    _ScenarioManager._set_primary(scenario_2)
+
+    primary_scenarios_sorted_by_name = [scenario_2, scenario_1]
+    assert primary_scenarios_sorted_by_name == _ScenarioManager._get_primary_scenarios(sorted=True, sort_key="name")
+
+    primary_scenarios_sorted_by_id = [scenario_2, scenario_1]
+    assert primary_scenarios_sorted_by_id == _ScenarioManager._get_primary_scenarios(sorted=True, sort_key="id")
+
+    primary_scenarios_sorted_by_creation_date = [scenario_1, scenario_2]
+    assert primary_scenarios_sorted_by_creation_date == _ScenarioManager._get_primary_scenarios(
+        sorted=True, sort_key="creation_date"
+    )
+
+    primary_scenarios_sorted_by_tag = [scenario_2, scenario_1]
+    assert primary_scenarios_sorted_by_tag == _ScenarioManager._get_primary_scenarios(sorted=True, sort_key="tags")
path: tests/core/scenario/test_scenario_manager.py
text: @user1:
A few remarks: 
1. I believe it would be interesting to test what happens when some scenarios have the same value
2. I believe the following way of configuring and creating scenarios is more representative.
- 2 scenario configs.
- 1 scenario from the first config
- 2 scenarios from the second config but attached to two different cycles.

```
    scenario_1_cfg = Config.configure_scenario(id="scenario_1", frequency=Frequency.DAILY)
    scenario_2_cfg = Config.configure_scenario(id="scenario_2", frequency=Frequency.DAILY)

    scenario_1 = _ScenarioManager._create(scenario_1_cfg, name="B_scenario")
    scenario_2 = _ScenarioManager._create(scenario_2_cfg, name="A_scenario", creation_date=datetime.datetime.now() + datetime.timedelta(days=2))
    scenario_3 = _ScenarioManager._create(scenario_2_cfg, name="C_scenario")

    scB = _ScenarioManager._set_primary(scenario_1)
    scB.tags = ["banana", "kiwi"]
    scA = _ScenarioManager._set_primary(scenario_2)
    scB.tags = ["apple", "banana"]
    scC = _ScenarioManager._set_primary(scenario_3)
    scB.tags = ["banana", "kiwi"]

    primary_scenarios_sorted_by_name = [scenario_2, scenario_1, scenario_3]
    assert primary_scenarios_sorted_by_name == _ScenarioManager._get_primary_scenarios(sorted=True, sort_key="name")

    primary_scenarios_sorted_by_id = [scenario_1, scenario_2, scenario_3]
    assert primary_scenarios_sorted_by_id == _ScenarioManager._get_primary_scenarios(sorted=True, sort_key="id")

    primary_scenarios_sorted_by_creation_date = [scenario_1, scenario_3, scenario_2]
    assert primary_scenarios_sorted_by_creation_date == _ScenarioManager._get_primary_scenarios(
        sorted=True, sort_key="creation_date"
    )

    primary_scenarios_sorted_by_tag = [scenario_2, scenario_1, scenario_3]
    assert primary_scenarios_sorted_by_tag == _ScenarioManager._get_primary_scenarios(sorted=True, sort_key="tags")
```

What do you think?

@author:
Thank you for showing how configuring and creating scenarios is done in a more representative way. Indeed, it would be interesting to look at what happens when two scenarios have the same value. I will have a look at this!

@author:
Hello @user1, 

Currently, I am busy implementing your recommended changes. However, I have some issues with using the representative way of configuring and creating scenarios, as now my primary scenarios seems to contain dates as well as scenarios when I print it. Hence, I think it would be nice if you can check whether I am doing it correctly. 
Here is what I currently have for the test function:
```suggestion
    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)

    scenario_1 = _ScenarioManager._create(scenario_1_cfg, name="B_scenario")
    scenario_2 = _ScenarioManager._create(scenario_2_cfg, name="A_scenario")
    scenario_3 = _ScenarioManager._create(scenario_2_cfg, name="C_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"]

    print("all scenarios: ", _ScenarioManager._get_all)
    primary_scenarios = _ScenarioManager._get_primary_scenarios()
    print("primary scenarios: ", primary_scenarios)

    primary_scenarios_sorted_by_name = [scenario_2, scenario_1, scenario_3]
    print(_ScenarioManager._sort_scenarios(primary_scenarios, sort_key="name"))
    assert primary_scenarios_sorted_by_name == _ScenarioManager._sort_scenarios(primary_scenarios, sort_key="name")

    primary_scenarios_sorted_by_id_version1 = [scenario_1, scenario_2, scenario_3]
    primary_scenarios_sorted_by_id_version2 = [scenario_1, scenario_3, scenario_2]
    result_sort_primary_scenarios_by_id = _ScenarioManager._sort_scenarios(primary_scenarios, sort_key="id")
    print("result of sort primary scenarios by id: ")
    print(result_sort_primary_scenarios_by_id)
    assert (primary_scenarios_sorted_by_id_version1 == result_sort_primary_scenarios_by_id or 
            primary_scenarios_sorted_by_id_version2 == result_sort_primary_scenarios_by_id)

    primary_scenarios_sorted_by_creation_date = [scenario_1, scenario_3, scenario_2]
    assert primary_scenarios_sorted_by_creation_date == _ScenarioManager._sort_scenarios(primary_scenarios, 
                                                                                         sort_key="creation_date")

    primary_scenarios_sorted_by_tags_version1 = [scenario_2, scenario_1, scenario_3]
    primary_scenarios_sorted_by_tags_version2 = [scenario_2, scenario_3, scenario_1]
    result_sort_primary_scenarios_by_tags = _ScenarioManager._sort_scenarios(primary_scenarios, sort_key="tags")
    print("result of sort primary scenarios by tags: ")
    print(result_sort_primary_scenarios_by_tags)
    assert (primary_scenarios_sorted_by_tags_version1 == result_sort_primary_scenarios_by_tags or 
            primary_scenarios_sorted_by_tags_version2 == result_sort_primary_scenarios_by_tags)

    primary_scenarios_sorted_by_name_descending_order = [scenario_3, scenario_1, scenario_2]
    assert primary_scenarios_sorted_by_name_descending_order == _ScenarioManager._sort_scenarios(
        primary_scenarios, descending=True, sort_key="name"
    )
```

I figured out that my implementation of the sort mechanism is not predictable when handling two scenarios with the same value, for instance the same name. So, if scenario A and B have the same name, the returned sorted list might either be [A, B] or [B, A]. However, I think this is fine. What do you think. I appreciate your help!


@user1:
1. Two scenarios from the same cycle cannot be primary simultaneously. In your example, your 3 scenarios are from the same cycle. So when you promote `scenario_3` as primary, the two others are demoted as not primary anymore.

    To make them all 3 primary, they must belong to different cycles. You can pass different days as `creation_date` when creating the scenarios. Something like that:

    ``` python
    scenario_1 = _ScenarioManager._create(scenario_1_cfg, name="B_scenario")
    scenario_2 = _ScenarioManager._create(scenario_2_cfg, name="A_scenario", creation_date=datetime.datetime.now() + datetime.timedelta(days=2))
    scenario_3 = _ScenarioManager._create(scenario_2_cfg, name="C_scenario", creation_date=datetime.datetime.now() + datetime.timedelta(days=4))
    ```

2. About the unpredictability: We can use a second criterion in case of an equal value. The `id` is guaranteed unique, so we can use it as the second criterion to make it predictable. It is made of the config_id concatenated with a random unique id.
    
    Something like the following should do the job:

    ``` python
    if sorted:
        if sort_key in ["name", "creation_date", "tags"]:
            scenarios.sort(key=lambda x: (getattr(x, sort_key), x.id)
        elif sort_key == "id":
            scenarios.sort(key=lambda x: x.id
        else:
            scenarios.sort(key=lambda x: (x.name, x.id)
    ```
    In the test, we need to determine at runtime whether scenario_2.id or scenario_3.id is 'smaller'.
    
I Hope this can help. Please tell me if it does make sense to you.

diff_hunk: @@ -435,6 +435,28 @@ def test_get_scenarios(self, cycle):
             tp.get_scenarios(tag="tag")
             mck.assert_called_once_with("tag")
 
+    def test_get_scenarios_sorted(self):
+        scenario_1_cfg = Config.configure_scenario(name="B_scenario", id="scenario_3", tags=["banana", "kiwi"])
+        scenario_2_cfg = Config.configure_scenario(name="C_scenario", id="scenario_1", tags=["apple", "banana"])
+        scenario_3_cfg = Config.configure_scenario(name="A_scenario", id="scenario_2", tags=["apple", "kiwi"])
path: tests/core/test_taipy.py
text: @user1:
Usually, the name and the tags are specific to a scenario instance. They are not passed when building a scenario configuration, but they are provided as parameters during the scenario creation.

You can have an example in my previous comment.


@author:
All right, I see. Thank you for clarifying this. I will change it.