Browse Source

Merge pull request #1390 from Avaiga/feature/enterprise#362-add-can_create-api

Feature/enterprise#362 - Add can_create() api
Đỗ Trường Giang 11 months ago
parent
commit
30832d43c3

+ 2 - 0
taipy/config/_serializer/_base_serializer.py

@@ -81,6 +81,8 @@ class _BaseSerializer(object):
             return [cls._stringify(val) for val in as_dict]
         if isinstance(as_dict, tuple):
             return [cls._stringify(val) for val in as_dict]
+        if isinstance(as_dict, set):
+            return [cls._stringify(val) for val in as_dict]
         return as_dict
 
     @staticmethod

+ 15 - 0
taipy/core/data/_data_manager.py

@@ -24,6 +24,8 @@ from ..config.data_node_config import DataNodeConfig
 from ..cycle.cycle_id import CycleId
 from ..exceptions.exceptions import InvalidDataNodeType
 from ..notification import Event, EventEntityType, EventOperation, Notifier, _make_event
+from ..reason._reason_factory import _build_not_global_scope_reason, _build_wrong_config_type_reason
+from ..reason.reason import Reasons
 from ..scenario.scenario_id import ScenarioId
 from ..sequence.sequence_id import SequenceId
 from ._data_fs_repository import _DataFSRepository
@@ -68,6 +70,19 @@ class _DataManager(_Manager[DataNode], _VersionMixin):
             for dn_config, owner_id in dn_configs_and_owner_id
         }
 
+    @classmethod
+    def _can_create(cls, config: Optional[DataNodeConfig] = None) -> Reasons:
+        config_id = getattr(config, "id", None) or str(config)
+        reason = Reasons(config_id)
+
+        if config is not None:
+            if not isinstance(config, DataNodeConfig):
+                reason._add_reason(config_id, _build_wrong_config_type_reason(config_id, "DataNodeConfig"))
+            elif config.scope is not Scope.GLOBAL:
+                reason._add_reason(config_id, _build_not_global_scope_reason(config_id))
+
+        return reason
+
     @classmethod
     def _create_and_set(
         cls, data_node_config: DataNodeConfig, owner_id: Optional[str], parent_ids: Optional[Set[str]]

+ 13 - 0
taipy/core/reason/_reason_factory.py

@@ -9,6 +9,8 @@
 # an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
 # specific language governing permissions and limitations under the License.
 
+from typing import Optional
+
 from ..data.data_node import DataNodeId
 
 
@@ -22,3 +24,14 @@ def _build_data_node_is_not_written(dn_id: DataNodeId) -> str:
 
 def _build_not_submittable_entity_reason(entity_id: str) -> str:
     return f"Entity {entity_id} is not a submittable entity"
+
+
+def _build_wrong_config_type_reason(config_id: str, config_type: Optional[str]) -> str:
+    if config_type:
+        return f'Object "{config_id}" must be a valid {config_type}'
+
+    return f'Object "{config_id}" is not a valid config to be created'
+
+
+def _build_not_global_scope_reason(config_id: str) -> str:
+    return f'Data node config "{config_id}" does not have GLOBAL scope'

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

@@ -46,7 +46,7 @@ from ..exceptions.exceptions import (
 from ..job._job_manager_factory import _JobManagerFactory
 from ..job.job import Job
 from ..notification import EventEntityType, EventOperation, Notifier, _make_event
-from ..reason._reason_factory import _build_not_submittable_entity_reason
+from ..reason._reason_factory import _build_not_submittable_entity_reason, _build_wrong_config_type_reason
 from ..reason.reason import Reasons
 from ..submission._submission_manager_factory import _SubmissionManagerFactory
 from ..submission.submission import Submission
@@ -114,6 +114,17 @@ class _ScenarioManager(_Manager[Scenario], _VersionMixin):
             _make_event(scenario, EventOperation.UPDATE, attribute_name="subscribers", attribute_value=params)
         )
 
+    @classmethod
+    def _can_create(cls, config: Optional[ScenarioConfig] = None) -> Reasons:
+        config_id = getattr(config, "id", None) or str(config)
+        reason = Reasons(config_id)
+
+        if config is not None:
+            if not isinstance(config, ScenarioConfig):
+                reason._add_reason(config_id, _build_wrong_config_type_reason(config_id, "ScenarioConfig"))
+
+        return reason
+
     @classmethod
     def _create(
         cls,

+ 14 - 0
taipy/core/taipy.py

@@ -873,6 +873,20 @@ def get_cycles() -> List[Cycle]:
     return _CycleManagerFactory._build_manager()._get_all()
 
 
+def can_create(config: Optional[Union[ScenarioConfig, DataNodeConfig]] = None) -> Reasons:
+    """Indicate if a config can be created. The config should be a scenario or data node config.
+
+    If no config is provided, the function indicates if any scenario or data node config can be created.
+
+    Returns:
+        True if the given config can be created. False otherwise.
+    """
+    if isinstance(config, DataNodeConfig):
+        return _DataManagerFactory._build_manager()._can_create(config)
+
+    return _ScenarioManagerFactory._build_manager()._can_create(config)
+
+
 def create_scenario(
     config: ScenarioConfig,
     creation_date: Optional[datetime] = None,

+ 22 - 0
tests/core/data/test_data_manager.py

@@ -45,6 +45,28 @@ class TestDataManager:
         assert dn.properties.get("foo") == "bar"
         assert dn.properties.get("baz") == "qux"
 
+    def test_can_create(self):
+        dn_config = Config.configure_data_node("dn", 10, scope=Scope.SCENARIO)
+        global_dn_config = Config.configure_data_node(
+            id="global_dn", storage_type="in_memory", scope=Scope.GLOBAL, data=10
+        )
+
+        reasons = _DataManager._can_create()
+        assert bool(reasons) is True
+        assert reasons._reasons == {}
+
+        reasons = _DataManager._can_create(global_dn_config)
+        assert bool(reasons) is True
+        assert reasons._reasons == {}
+
+        reasons = _DataManager._can_create(dn_config)
+        assert bool(reasons) is False
+        assert reasons._reasons == {dn_config.id: {'Data node config "dn" does not have GLOBAL scope'}}
+
+        reasons = _DataManager._can_create(1)
+        assert bool(reasons) is False
+        assert reasons._reasons == {"1": {'Object "1" must be a valid DataNodeConfig'}}
+
     def test_create_data_node_with_name_provided(self):
         dn_config = Config.configure_data_node(id="dn", foo="bar", name="acb")
         dn = _DataManager._create_and_set(dn_config, None, None)

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

@@ -365,6 +365,33 @@ def test_create_and_delete_scenario():
     assert len(_ScenarioManager._get_all()) == 0
 
 
+def test_can_create():
+    dn_config = Config.configure_in_memory_data_node("dn", 10)
+    task_config = Config.configure_task("task", print, [dn_config])
+    scenario_config = Config.configure_scenario("sc", {task_config}, [], Frequency.DAILY)
+
+    reasons = _ScenarioManager._can_create()
+    assert bool(reasons) is True
+    assert reasons._reasons == {}
+
+    reasons = _ScenarioManager._can_create(scenario_config)
+    assert bool(reasons) is True
+    assert reasons._reasons == {}
+    _ScenarioManager._create(scenario_config)
+
+    reasons = _ScenarioManager._can_create(task_config)
+    assert bool(reasons) is False
+    assert reasons._reasons == {task_config.id: {'Object "task" must be a valid ScenarioConfig'}}
+    with pytest.raises(AttributeError):
+        _ScenarioManager._create(task_config)
+
+    reasons = _ScenarioManager._can_create(1)
+    assert bool(reasons) is False
+    assert reasons._reasons == {"1": {'Object "1" must be a valid ScenarioConfig'}}
+    with pytest.raises(AttributeError):
+        _ScenarioManager._create(1)
+
+
 def test_is_deletable():
     assert len(_ScenarioManager._get_all()) == 0
     scenario_config = Config.configure_scenario("sc", None, None, Frequency.DAILY)

+ 12 - 0
tests/core/test_taipy.py

@@ -677,6 +677,18 @@ class TestTaipy:
             tp.exists(cycle_id)
             mck.assert_called_once_with(cycle_id)
 
+    def test_can_create(self):
+        global_dn_config = Config.configure_in_memory_data_node("global_dn", 10, scope=Scope.GLOBAL)
+        dn_config = Config.configure_in_memory_data_node("dn", 10)
+        task_config = Config.configure_task("task", print, [dn_config])
+        scenario_config = Config.configure_scenario("sc", {task_config}, [], Frequency.DAILY)
+
+        assert tp.can_create()
+        assert tp.can_create(scenario_config)
+        assert tp.can_create(global_dn_config)
+        assert not tp.can_create(dn_config)
+        assert not tp.can_create("1")
+
     def test_create_global_data_node(self):
         dn_cfg_global = DataNodeConfig("id", "pickle", Scope.GLOBAL)
         dn_cfg_scenario = DataNodeConfig("id", "pickle", Scope.SCENARIO)