Browse Source

Merge pull request #1836 from Avaiga/feature/#1805-protect-reason-apis

feature/#1805 added checking if entity exists
Toan Quach 7 months ago
parent
commit
b9f75ef4a8

+ 8 - 2
taipy/core/_manager/_manager.py

@@ -161,8 +161,14 @@ class _Manager(Generic[EntityType]):
 
 
     @classmethod
     @classmethod
     def _is_editable(cls, entity: Union[EntityType, str]) -> ReasonCollection:
     def _is_editable(cls, entity: Union[EntityType, str]) -> ReasonCollection:
-        return ReasonCollection()
+        reason_collection = ReasonCollection()
+        if cls._get(entity) is None:
+            reason_collection._add_reason(str(entity), EntityDoesNotExist(str(entity)))
+        return reason_collection
 
 
     @classmethod
     @classmethod
     def _is_readable(cls, entity: Union[EntityType, str]) -> ReasonCollection:
     def _is_readable(cls, entity: Union[EntityType, str]) -> ReasonCollection:
-        return ReasonCollection()
+        reason_collection = ReasonCollection()
+        if cls._get(entity) is None:
+            reason_collection._add_reason(str(entity), EntityDoesNotExist(str(entity)))
+        return reason_collection

+ 11 - 6
taipy/core/_orchestrator/_orchestrator_factory.py

@@ -9,11 +9,11 @@
 # an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
 # 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.
 # specific language governing permissions and limitations under the License.
 import typing
 import typing
-from importlib import util
 from typing import Optional, Type
 from typing import Optional, Type
 
 
 from taipy.config.config import Config
 from taipy.config.config import Config
 
 
+from ..common._check_dependencies import _TAIPY_ENTERPRISE_MODULE, _using_enterprise
 from ..common._utils import _load_fct
 from ..common._utils import _load_fct
 from ..exceptions.exceptions import ModeNotAvailable, OrchestratorNotBuilt
 from ..exceptions.exceptions import ModeNotAvailable, OrchestratorNotBuilt
 from ._abstract_orchestrator import _AbstractOrchestrator
 from ._abstract_orchestrator import _AbstractOrchestrator
@@ -22,7 +22,6 @@ from ._orchestrator import _Orchestrator
 
 
 
 
 class _OrchestratorFactory:
 class _OrchestratorFactory:
-    _TAIPY_ENTERPRISE_MODULE = "taipy.enterprise"
     _TAIPY_ENTERPRISE_CORE_ORCHESTRATOR_MODULE = _TAIPY_ENTERPRISE_MODULE + ".core._orchestrator._orchestrator"
     _TAIPY_ENTERPRISE_CORE_ORCHESTRATOR_MODULE = _TAIPY_ENTERPRISE_MODULE + ".core._orchestrator._orchestrator"
     _TAIPY_ENTERPRISE_CORE_DISPATCHER_MODULE = _TAIPY_ENTERPRISE_MODULE + ".core._orchestrator._dispatcher"
     _TAIPY_ENTERPRISE_CORE_DISPATCHER_MODULE = _TAIPY_ENTERPRISE_MODULE + ".core._orchestrator._dispatcher"
     __TAIPY_ENTERPRISE_BUILD_DISPATCHER_METHOD = "_build_dispatcher"
     __TAIPY_ENTERPRISE_BUILD_DISPATCHER_METHOD = "_build_dispatcher"
@@ -34,7 +33,7 @@ class _OrchestratorFactory:
     def _build_orchestrator(cls) -> Type[_AbstractOrchestrator]:
     def _build_orchestrator(cls) -> Type[_AbstractOrchestrator]:
         if cls._orchestrator:
         if cls._orchestrator:
             return cls._orchestrator  # type: ignore
             return cls._orchestrator  # type: ignore
-        if util.find_spec(cls._TAIPY_ENTERPRISE_MODULE) is not None:
+        if _using_enterprise():
             cls._orchestrator = _load_fct(
             cls._orchestrator = _load_fct(
                 cls._TAIPY_ENTERPRISE_CORE_ORCHESTRATOR_MODULE,
                 cls._TAIPY_ENTERPRISE_CORE_ORCHESTRATOR_MODULE,
                 "Orchestrator",
                 "Orchestrator",
@@ -51,7 +50,7 @@ class _OrchestratorFactory:
         if not cls._orchestrator:
         if not cls._orchestrator:
             raise OrchestratorNotBuilt
             raise OrchestratorNotBuilt
 
 
-        if util.find_spec(cls._TAIPY_ENTERPRISE_MODULE):
+        if _using_enterprise():
             cls.__build_enterprise_job_dispatcher(force_restart=force_restart)
             cls.__build_enterprise_job_dispatcher(force_restart=force_restart)
         elif Config.job_config.is_standalone:
         elif Config.job_config.is_standalone:
             cls.__build_standalone_job_dispatcher(force_restart=force_restart)
             cls.__build_standalone_job_dispatcher(force_restart=force_restart)
@@ -77,7 +76,7 @@ class _OrchestratorFactory:
             else:
             else:
                 return
                 return
 
 
-        if util.find_spec(cls._TAIPY_ENTERPRISE_MODULE) is not None:
+        if _using_enterprise():
             cls._dispatcher = _load_fct(
             cls._dispatcher = _load_fct(
                 cls._TAIPY_ENTERPRISE_CORE_DISPATCHER_MODULE, cls.__TAIPY_ENTERPRISE_BUILD_DISPATCHER_METHOD
                 cls._TAIPY_ENTERPRISE_CORE_DISPATCHER_MODULE, cls.__TAIPY_ENTERPRISE_BUILD_DISPATCHER_METHOD
             )(cls._orchestrator)
             )(cls._orchestrator)
@@ -89,7 +88,13 @@ class _OrchestratorFactory:
     def __build_development_job_dispatcher(cls):
     def __build_development_job_dispatcher(cls):
         if isinstance(cls._dispatcher, _StandaloneJobDispatcher):
         if isinstance(cls._dispatcher, _StandaloneJobDispatcher):
             cls._dispatcher.stop()
             cls._dispatcher.stop()
-        cls._dispatcher = _DevelopmentJobDispatcher(typing.cast(_AbstractOrchestrator, cls._orchestrator))
+
+        if _using_enterprise():
+            cls._dispatcher = _load_fct(
+                cls._TAIPY_ENTERPRISE_CORE_DISPATCHER_MODULE, cls.__TAIPY_ENTERPRISE_BUILD_DISPATCHER_METHOD
+            )(cls._orchestrator)
+        else:
+            cls._dispatcher = _DevelopmentJobDispatcher(typing.cast(_AbstractOrchestrator, cls._orchestrator))
 
 
     @classmethod
     @classmethod
     def __build_enterprise_job_dispatcher(cls, force_restart=False):
     def __build_enterprise_job_dispatcher(cls, force_restart=False):

+ 7 - 3
taipy/core/job/_job_manager.py

@@ -18,7 +18,7 @@ from .._version._version_manager_factory import _VersionManagerFactory
 from .._version._version_mixin import _VersionMixin
 from .._version._version_mixin import _VersionMixin
 from ..exceptions.exceptions import JobNotDeletedException
 from ..exceptions.exceptions import JobNotDeletedException
 from ..notification import EventEntityType, EventOperation, Notifier, _make_event
 from ..notification import EventEntityType, EventOperation, Notifier, _make_event
-from ..reason import JobIsNotFinished, ReasonCollection
+from ..reason import EntityDoesNotExist, JobIsNotFinished, ReasonCollection
 from ..task.task import Task
 from ..task.task import Task
 from .job import Job
 from .job import Job
 from .job_id import JobId
 from .job_id import JobId
@@ -92,9 +92,13 @@ class _JobManager(_Manager[Job], _VersionMixin):
         reason_collector = ReasonCollection()
         reason_collector = ReasonCollection()
 
 
         if isinstance(job, str):
         if isinstance(job, str):
-            job = cls._get(job)
+            job_id = job
+            job = cls._get(job, None)
+            if job is None:
+                reason_collector._add_reason(job_id, EntityDoesNotExist(job_id))
+                return reason_collector
 
 
-        if job and not job.is_finished():
+        if not job.is_finished():
             reason_collector._add_reason(job.id, JobIsNotFinished(job.id))
             reason_collector._add_reason(job.id, JobIsNotFinished(job.id))
 
 
         return reason_collector
         return reason_collector

+ 1 - 0
taipy/core/reason/__init__.py

@@ -26,6 +26,7 @@ from .reason import (
     DataNodeEditInProgress,
     DataNodeEditInProgress,
     DataNodeIsNotWritten,
     DataNodeIsNotWritten,
     EntityDoesNotExist,
     EntityDoesNotExist,
+    EntityIsNotAScenario,
     EntityIsNotSubmittableEntity,
     EntityIsNotSubmittableEntity,
     InvalidUploadFile,
     InvalidUploadFile,
     JobIsNotFinished,
     JobIsNotFinished,

+ 24 - 10
taipy/core/reason/reason.py

@@ -151,8 +151,9 @@ class NoFileToDownload(Reason, _DataNodeReasonMixin):
     """
     """
 
 
     def __init__(self, file_path: str, datanode_id: str):
     def __init__(self, file_path: str, datanode_id: str):
-        Reason.__init__(self, f"Path '{file_path}' from data node '{datanode_id}'"
-                              f" does not exist and cannot be downloaded.")
+        Reason.__init__(
+            self, f"Path '{file_path}' from data node '{datanode_id}'" f" does not exist and cannot be downloaded"
+        )
         _DataNodeReasonMixin.__init__(self, datanode_id)
         _DataNodeReasonMixin.__init__(self, datanode_id)
 
 
 
 
@@ -165,8 +166,9 @@ class NotAFile(Reason, _DataNodeReasonMixin):
     """
     """
 
 
     def __init__(self, file_path: str, datanode_id: str):
     def __init__(self, file_path: str, datanode_id: str):
-        Reason.__init__(self, f"Path '{file_path}' from data node '{datanode_id}'"
-                              f" is not a file and can t be downloaded.")
+        Reason.__init__(
+            self, f"Path '{file_path}' from data node '{datanode_id}'" f" is not a file and can t be downloaded"
+        )
         _DataNodeReasonMixin.__init__(self, datanode_id)
         _DataNodeReasonMixin.__init__(self, datanode_id)
 
 
 
 
@@ -193,7 +195,7 @@ class EntityDoesNotExist(Reason, _DataNodeReasonMixin):
     """
     """
 
 
     def __init__(self, entity_id: str):
     def __init__(self, entity_id: str):
-        Reason.__init__(self, f"Entity {entity_id} does not exist in the repository.")
+        Reason.__init__(self, f"Entity {entity_id} does not exist in the repository")
 
 
 
 
 class JobIsNotFinished(Reason, _DataNodeReasonMixin):
 class JobIsNotFinished(Reason, _DataNodeReasonMixin):
@@ -205,7 +207,19 @@ class JobIsNotFinished(Reason, _DataNodeReasonMixin):
     """
     """
 
 
     def __init__(self, job_id: str):
     def __init__(self, job_id: str):
-        Reason.__init__(self, f"The job {job_id} is not finished yet.")
+        Reason.__init__(self, f"The job {job_id} is not finished yet")
+
+
+class EntityIsNotAScenario(Reason, _DataNodeReasonMixin):
+    """
+    The entity is not a scenario, which prevents specific actions from being performed.
+
+    Attributes:
+        entity_id (str): The entity identifier.
+    """
+
+    def __init__(self, entity_id: str):
+        Reason.__init__(self, f"The entity {entity_id} is not a scenario")
 
 
 
 
 class ScenarioIsThePrimaryScenario(Reason, _DataNodeReasonMixin):
 class ScenarioIsThePrimaryScenario(Reason, _DataNodeReasonMixin):
@@ -218,7 +232,7 @@ class ScenarioIsThePrimaryScenario(Reason, _DataNodeReasonMixin):
     """
     """
 
 
     def __init__(self, scenario_id: str, cycle: str):
     def __init__(self, scenario_id: str, cycle: str):
-        Reason.__init__(self, f"The scenario {scenario_id} is the primary scenario of cycle {cycle}.")
+        Reason.__init__(self, f"The scenario {scenario_id} is the primary scenario of cycle {cycle}")
 
 
 
 
 class ScenarioDoesNotBelongToACycle(Reason, _DataNodeReasonMixin):
 class ScenarioDoesNotBelongToACycle(Reason, _DataNodeReasonMixin):
@@ -230,7 +244,7 @@ class ScenarioDoesNotBelongToACycle(Reason, _DataNodeReasonMixin):
     """
     """
 
 
     def __init__(self, scenario_id: str):
     def __init__(self, scenario_id: str):
-        Reason.__init__(self, f"The scenario {scenario_id} does not belong to any cycle.")
+        Reason.__init__(self, f"The scenario {scenario_id} does not belong to any cycle")
 
 
 
 
 class SubmissionIsNotFinished(Reason, _DataNodeReasonMixin):
 class SubmissionIsNotFinished(Reason, _DataNodeReasonMixin):
@@ -242,7 +256,7 @@ class SubmissionIsNotFinished(Reason, _DataNodeReasonMixin):
     """
     """
 
 
     def __init__(self, submission_id: str):
     def __init__(self, submission_id: str):
-        Reason.__init__(self, f"The submission {submission_id} is not finished yet.")
+        Reason.__init__(self, f"The submission {submission_id} is not finished yet")
 
 
 
 
 class SubmissionStatusIsUndefined(Reason, _DataNodeReasonMixin):
 class SubmissionStatusIsUndefined(Reason, _DataNodeReasonMixin):
@@ -254,4 +268,4 @@ class SubmissionStatusIsUndefined(Reason, _DataNodeReasonMixin):
     """
     """
 
 
     def __init__(self, submission_id: str):
     def __init__(self, submission_id: str):
-        Reason.__init__(self, f"The status of submission {submission_id} is undefined.")
+        Reason.__init__(self, f"The status of submission {submission_id} is undefined")

+ 20 - 6
taipy/core/scenario/_scenario_manager.py

@@ -41,6 +41,7 @@ from ..job.job import Job
 from ..notification import EventEntityType, EventOperation, Notifier, _make_event
 from ..notification import EventEntityType, EventOperation, Notifier, _make_event
 from ..reason import (
 from ..reason import (
     EntityDoesNotExist,
     EntityDoesNotExist,
+    EntityIsNotAScenario,
     EntityIsNotSubmittableEntity,
     EntityIsNotSubmittableEntity,
     ReasonCollection,
     ReasonCollection,
     ScenarioDoesNotBelongToACycle,
     ScenarioDoesNotBelongToACycle,
@@ -208,16 +209,21 @@ class _ScenarioManager(_Manager[Scenario], _VersionMixin):
 
 
     @classmethod
     @classmethod
     def _is_submittable(cls, scenario: Union[Scenario, ScenarioId]) -> ReasonCollection:
     def _is_submittable(cls, scenario: Union[Scenario, ScenarioId]) -> ReasonCollection:
+        reason_collector = ReasonCollection()
+
         if isinstance(scenario, str):
         if isinstance(scenario, str):
+            scenario_id = scenario
             scenario = cls._get(scenario)
             scenario = cls._get(scenario)
+            if scenario is None:
+                reason_collector._add_reason(scenario_id, EntityDoesNotExist(scenario_id))
+                return reason_collector
 
 
         if not isinstance(scenario, Scenario):
         if not isinstance(scenario, Scenario):
-            scenario = str(scenario)
-            reason_collector = ReasonCollection()
-            reason_collector._add_reason(scenario, EntityIsNotSubmittableEntity(scenario))
-            return reason_collector
+            reason_collector._add_reason(str(scenario), EntityIsNotSubmittableEntity(str(scenario)))
+        else:
+            return scenario.is_ready_to_run()
 
 
-        return scenario.is_ready_to_run()
+        return reason_collector
 
 
     @classmethod
     @classmethod
     def _submit(
     def _submit(
@@ -425,10 +431,18 @@ class _ScenarioManager(_Manager[Scenario], _VersionMixin):
         reason_collection = ReasonCollection()
         reason_collection = ReasonCollection()
 
 
         if isinstance(scenario, str):
         if isinstance(scenario, str):
+            scenario_id = scenario
             scenario = cls._get(scenario)
             scenario = cls._get(scenario)
-        if scenario.is_primary:
+            if scenario is None:
+                reason_collection._add_reason(scenario_id, EntityDoesNotExist(scenario_id))
+                return reason_collection
+
+        if not isinstance(scenario, Scenario):
+            reason_collection._add_reason(str(scenario), EntityIsNotAScenario(str(scenario)))
+        elif scenario.is_primary:
             if len(cls._get_all_by_cycle(scenario.cycle)) > 1:
             if len(cls._get_all_by_cycle(scenario.cycle)) > 1:
                 reason_collection._add_reason(scenario.id, ScenarioIsThePrimaryScenario(scenario.id, scenario.cycle.id))
                 reason_collection._add_reason(scenario.id, ScenarioIsThePrimaryScenario(scenario.id, scenario.cycle.id))
+
         return reason_collection
         return reason_collection
 
 
     @classmethod
     @classmethod

+ 10 - 5
taipy/core/sequence/_sequence_manager.py

@@ -342,16 +342,21 @@ class _SequenceManager(_Manager[Sequence], _VersionMixin):
 
 
     @classmethod
     @classmethod
     def _is_submittable(cls, sequence: Union[Sequence, SequenceId]) -> ReasonCollection:
     def _is_submittable(cls, sequence: Union[Sequence, SequenceId]) -> ReasonCollection:
+        reason_collector = ReasonCollection()
+
         if isinstance(sequence, str):
         if isinstance(sequence, str):
+            sequence_id = sequence
             sequence = cls._get(sequence)
             sequence = cls._get(sequence)
+            if sequence is None:
+                reason_collector._add_reason(sequence_id, EntityDoesNotExist(sequence_id))
+                return reason_collector
 
 
         if not isinstance(sequence, Sequence):
         if not isinstance(sequence, Sequence):
-            sequence = str(sequence)
-            reason_collector = ReasonCollection()
-            reason_collector._add_reason(sequence, EntityIsNotSubmittableEntity(sequence))
-            return reason_collector
+            reason_collector._add_reason(str(sequence), EntityIsNotSubmittableEntity(str(sequence)))
+        else:
+            return sequence.is_ready_to_run()
 
 
-        return sequence.is_ready_to_run()
+        return reason_collector
 
 
     @classmethod
     @classmethod
     def _submit(
     def _submit(

+ 5 - 1
taipy/core/submission/_submission_manager.py

@@ -21,7 +21,7 @@ from .._version._version_mixin import _VersionMixin
 from ..exceptions.exceptions import SubmissionNotDeletedException
 from ..exceptions.exceptions import SubmissionNotDeletedException
 from ..job.job import Job, Status
 from ..job.job import Job, Status
 from ..notification import EventEntityType, EventOperation, Notifier, _make_event
 from ..notification import EventEntityType, EventOperation, Notifier, _make_event
-from ..reason import ReasonCollection, SubmissionIsNotFinished
+from ..reason import EntityDoesNotExist, ReasonCollection, SubmissionIsNotFinished
 from ..scenario.scenario import Scenario
 from ..scenario.scenario import Scenario
 from ..sequence.sequence import Sequence
 from ..sequence.sequence import Sequence
 from ..submission.submission import Submission, SubmissionId, SubmissionStatus
 from ..submission.submission import Submission, SubmissionId, SubmissionStatus
@@ -178,7 +178,11 @@ class _SubmissionManager(_Manager[Submission], _VersionMixin):
         reason_collector = ReasonCollection()
         reason_collector = ReasonCollection()
 
 
         if isinstance(submission, str):
         if isinstance(submission, str):
+            submission_id = submission
             submission = cls._get(submission)
             submission = cls._get(submission)
+            if submission is None:
+                reason_collector._add_reason(submission_id, EntityDoesNotExist(submission_id))
+                return reason_collector
 
 
         if not submission.is_finished() and submission.submission_status != SubmissionStatus.UNDEFINED:
         if not submission.is_finished() and submission.submission_status != SubmissionStatus.UNDEFINED:
             reason_collector._add_reason(submission.id, SubmissionIsNotFinished(submission.id))
             reason_collector._add_reason(submission.id, SubmissionIsNotFinished(submission.id))

+ 13 - 4
taipy/core/task/_task_manager.py

@@ -26,7 +26,13 @@ from ..cycle.cycle_id import CycleId
 from ..data._data_manager_factory import _DataManagerFactory
 from ..data._data_manager_factory import _DataManagerFactory
 from ..exceptions.exceptions import NonExistingTask
 from ..exceptions.exceptions import NonExistingTask
 from ..notification import EventEntityType, EventOperation, Notifier, _make_event
 from ..notification import EventEntityType, EventOperation, Notifier, _make_event
-from ..reason import DataNodeEditInProgress, DataNodeIsNotWritten, EntityIsNotSubmittableEntity, ReasonCollection
+from ..reason import (
+    DataNodeEditInProgress,
+    DataNodeIsNotWritten,
+    EntityDoesNotExist,
+    EntityIsNotSubmittableEntity,
+    ReasonCollection,
+)
 from ..scenario.scenario_id import ScenarioId
 from ..scenario.scenario_id import ScenarioId
 from ..sequence.sequence_id import SequenceId
 from ..sequence.sequence_id import SequenceId
 from ..submission.submission import Submission
 from ..submission.submission import Submission
@@ -165,13 +171,16 @@ class _TaskManager(_Manager[Task], _VersionMixin):
 
 
     @classmethod
     @classmethod
     def _is_submittable(cls, task: Union[Task, TaskId]) -> ReasonCollection:
     def _is_submittable(cls, task: Union[Task, TaskId]) -> ReasonCollection:
+        reason_collection = ReasonCollection()
+
         if isinstance(task, str):
         if isinstance(task, str):
+            task_id = task
             task = cls._get(task)
             task = cls._get(task)
+            if task is None:
+                reason_collection._add_reason(task_id, EntityDoesNotExist(task_id))
 
 
-        reason_collection = ReasonCollection()
         if not isinstance(task, Task):
         if not isinstance(task, Task):
-            task = str(task)
-            reason_collection._add_reason(task, EntityIsNotSubmittableEntity(task))
+            reason_collection._add_reason(str(task), EntityIsNotSubmittableEntity(str(task)))
         else:
         else:
             data_manager = _DataManagerFactory._build_manager()
             data_manager = _DataManagerFactory._build_manager()
             for node in task.input.values():
             for node in task.input.values():

+ 8 - 0
tests/core/_manager/test_manager.py

@@ -166,7 +166,15 @@ class TestManager:
         MockManager._set(m)
         MockManager._set(m)
         assert MockManager._is_editable(m)
         assert MockManager._is_editable(m)
 
 
+        rc = MockManager._is_editable("some_entity")
+        assert not rc
+        assert "Entity some_entity does not exist in the repository." in rc.reasons
+
     def test_is_readable(self):
     def test_is_readable(self):
         m = MockEntity("uuid", "Foo")
         m = MockEntity("uuid", "Foo")
         MockManager._set(m)
         MockManager._set(m)
         assert MockManager._is_readable(m)
         assert MockManager._is_readable(m)
+
+        rc = MockManager._is_editable("some_entity")
+        assert not rc
+        assert "Entity some_entity does not exist in the repository." in rc.reasons

+ 4 - 0
tests/core/job/test_job_manager.py

@@ -412,6 +412,10 @@ def test_is_deletable():
     task = _create_task(print, 0, "task")
     task = _create_task(print, 0, "task")
     job = _OrchestratorFactory._orchestrator.submit_task(task).jobs[0]
     job = _OrchestratorFactory._orchestrator.submit_task(task).jobs[0]
 
 
+    rc = _JobManager._is_deletable("some_job")
+    assert not rc
+    assert "Entity some_job does not exist in the repository." in rc.reasons
+
     assert job.is_completed()
     assert job.is_completed()
     assert _JobManager._is_deletable(job)
     assert _JobManager._is_deletable(job)
     assert _JobManager._is_deletable(job.id)
     assert _JobManager._is_deletable(job.id)

+ 10 - 4
tests/core/scenario/test_scenario_manager.py

@@ -404,6 +404,10 @@ def test_is_deletable():
     scenario_1_primary = _ScenarioManager._create(scenario_config, creation_date=creation_date, name="1")
     scenario_1_primary = _ScenarioManager._create(scenario_config, creation_date=creation_date, name="1")
     scenario_2 = _ScenarioManager._create(scenario_config, creation_date=creation_date, name="2")
     scenario_2 = _ScenarioManager._create(scenario_config, creation_date=creation_date, name="2")
 
 
+    rc = _ScenarioManager._is_deletable("some_scenario")
+    assert not rc
+    assert "Entity some_scenario does not exist in the repository." in rc.reasons
+
     assert len(_ScenarioManager._get_all()) == 2
     assert len(_ScenarioManager._get_all()) == 2
     assert scenario_1_primary.is_primary
     assert scenario_1_primary.is_primary
     assert not _ScenarioManager._is_deletable(scenario_1_primary)
     assert not _ScenarioManager._is_deletable(scenario_1_primary)
@@ -681,12 +685,10 @@ def notify_multi_param(param, *args):
     assert len(param) == 3
     assert len(param) == 3
 
 
 
 
-def notify1(*args, **kwargs):
-    ...
+def notify1(*args, **kwargs): ...
 
 
 
 
-def notify2(*args, **kwargs):
-    ...
+def notify2(*args, **kwargs): ...
 
 
 
 
 def test_notification_unsubscribe(mocker):
 def test_notification_unsubscribe(mocker):
@@ -1045,6 +1047,10 @@ def test_is_submittable():
     scenario_config = Config.configure_scenario("sc", {task_config}, set(), Frequency.DAILY)
     scenario_config = Config.configure_scenario("sc", {task_config}, set(), Frequency.DAILY)
     scenario = _ScenarioManager._create(scenario_config)
     scenario = _ScenarioManager._create(scenario_config)
 
 
+    rc = _ScenarioManager._is_submittable("some_scenario")
+    assert not rc
+    assert "Entity some_scenario does not exist in the repository." in rc.reasons
+
     assert len(_ScenarioManager._get_all()) == 1
     assert len(_ScenarioManager._get_all()) == 1
     assert _ScenarioManager._is_submittable(scenario)
     assert _ScenarioManager._is_submittable(scenario)
     assert _ScenarioManager._is_submittable(scenario.id)
     assert _ScenarioManager._is_submittable(scenario.id)

+ 7 - 6
tests/core/sequence/test_sequence_manager.py

@@ -206,6 +206,10 @@ def test_is_submittable():
     scenario = Scenario("scenario", {task}, {}, set())
     scenario = Scenario("scenario", {task}, {}, set())
     _ScenarioManager._set(scenario)
     _ScenarioManager._set(scenario)
 
 
+    rc = _SequenceManager._is_submittable("some_sequence")
+    assert not rc
+    assert "Entity some_sequence does not exist in the repository." in rc.reasons
+
     scenario.add_sequences({"sequence": [task]})
     scenario.add_sequences({"sequence": [task]})
     sequence = scenario.sequences["sequence"]
     sequence = scenario.sequences["sequence"]
 
 
@@ -445,16 +449,13 @@ def test_get_or_create_data():
         sequence.WRONG.write(7)
         sequence.WRONG.write(7)
 
 
 
 
-def notify1(*args, **kwargs):
-    ...
+def notify1(*args, **kwargs): ...
 
 
 
 
-def notify2(*args, **kwargs):
-    ...
+def notify2(*args, **kwargs): ...
 
 
 
 
-def notify_multi_param(*args, **kwargs):
-    ...
+def notify_multi_param(*args, **kwargs): ...
 
 
 
 
 def test_sequence_notification_subscribe(mocker):
 def test_sequence_notification_subscribe(mocker):

+ 4 - 0
tests/core/submission/test_submission_manager.py

@@ -151,6 +151,10 @@ def test_is_deletable():
 
 
     assert len(submission_manager._get_all()) == 1
     assert len(submission_manager._get_all()) == 1
 
 
+    rc = submission_manager._is_deletable("some_submission")
+    assert not rc
+    assert "Entity some_submission does not exist in the repository." in rc.reasons
+
     assert submission._submission_status == SubmissionStatus.SUBMITTED
     assert submission._submission_status == SubmissionStatus.SUBMITTED
     assert not submission.is_deletable()
     assert not submission.is_deletable()
     assert not submission_manager._is_deletable(submission)
     assert not submission_manager._is_deletable(submission)

+ 4 - 0
tests/core/task/test_task_manager.py

@@ -308,6 +308,10 @@ def test_is_submittable():
     task_config = Config.configure_task("task", print, [dn_config])
     task_config = Config.configure_task("task", print, [dn_config])
     task = _TaskManager._bulk_get_or_create([task_config])[0]
     task = _TaskManager._bulk_get_or_create([task_config])[0]
 
 
+    rc = _TaskManager._is_submittable("some_task")
+    assert not rc
+    assert "Entity some_task does not exist in the repository" in rc.reasons
+
     assert len(_TaskManager._get_all()) == 1
     assert len(_TaskManager._get_all()) == 1
     assert _TaskManager._is_submittable(task)
     assert _TaskManager._is_submittable(task)
     assert _TaskManager._is_submittable(task.id)
     assert _TaskManager._is_submittable(task.id)

+ 16 - 2
tests/gui_core/test_context_is_editable.py

@@ -11,9 +11,15 @@
 
 
 from unittest.mock import Mock, patch
 from unittest.mock import Mock, patch
 
 
+import pytest
+
 from taipy.config.common.scope import Scope
 from taipy.config.common.scope import Scope
 from taipy.core import Job, JobId, Scenario, Task
 from taipy.core import Job, JobId, Scenario, Task
+from taipy.core.data._data_manager_factory import _DataManagerFactory
 from taipy.core.data.pickle import PickleDataNode
 from taipy.core.data.pickle import PickleDataNode
+from taipy.core.job._job_manager_factory import _JobManagerFactory
+from taipy.core.scenario._scenario_manager_factory import _ScenarioManagerFactory
+from taipy.core.task._task_manager_factory import _TaskManagerFactory
 from taipy.gui import Gui
 from taipy.gui import Gui
 from taipy.gui_core._context import _GuiCoreContext
 from taipy.gui_core._context import _GuiCoreContext
 
 
@@ -48,6 +54,13 @@ class MockState:
 
 
 
 
 class TestGuiCoreContext_is_editable:
 class TestGuiCoreContext_is_editable:
+    @pytest.fixture(scope="class", autouse=True)
+    def set_entity(self):
+        _ScenarioManagerFactory._build_manager()._set(a_scenario)
+        _TaskManagerFactory._build_manager()._set(a_task)
+        _JobManagerFactory._build_manager()._set(a_job)
+        _DataManagerFactory._build_manager()._set(a_datanode)
+
     def test_crud_scenario(self):
     def test_crud_scenario(self):
         with patch("taipy.gui_core._context.core_get", side_effect=mock_core_get):
         with patch("taipy.gui_core._context.core_get", side_effect=mock_core_get):
             gui_core_context = _GuiCoreContext(Mock())
             gui_core_context = _GuiCoreContext(Mock())
@@ -125,8 +138,9 @@ class TestGuiCoreContext_is_editable:
                 assert "is not editable" in str(assign.call_args.args[1])
                 assert "is not editable" in str(assign.call_args.args[1])
 
 
     def test_act_on_jobs(self):
     def test_act_on_jobs(self):
-        with patch("taipy.gui_core._context.core_get", side_effect=mock_core_get), patch(
-            "taipy.gui_core._context.is_deletable", side_effect=mock_is_true
+        with (
+            patch("taipy.gui_core._context.core_get", side_effect=mock_core_get),
+            patch("taipy.gui_core._context.is_deletable", side_effect=mock_is_true),
         ):
         ):
             gui_core_context = _GuiCoreContext(Mock())
             gui_core_context = _GuiCoreContext(Mock())
             assign = Mock()
             assign = Mock()

+ 16 - 2
tests/gui_core/test_context_is_promotable.py

@@ -11,9 +11,15 @@
 
 
 from unittest.mock import Mock, patch
 from unittest.mock import Mock, patch
 
 
+import pytest
+
 from taipy.config.common.scope import Scope
 from taipy.config.common.scope import Scope
 from taipy.core import Job, JobId, Scenario, Task
 from taipy.core import Job, JobId, Scenario, Task
+from taipy.core.data._data_manager_factory import _DataManagerFactory
 from taipy.core.data.pickle import PickleDataNode
 from taipy.core.data.pickle import PickleDataNode
+from taipy.core.job._job_manager_factory import _JobManagerFactory
+from taipy.core.scenario._scenario_manager_factory import _ScenarioManagerFactory
+from taipy.core.task._task_manager_factory import _TaskManagerFactory
 from taipy.gui_core._context import _GuiCoreContext
 from taipy.gui_core._context import _GuiCoreContext
 
 
 a_scenario = Scenario("scenario_config_id", None, {}, sequences={"sequence": {}})
 a_scenario = Scenario("scenario_config_id", None, {}, sequences={"sequence": {}})
@@ -47,9 +53,17 @@ class MockState:
 
 
 
 
 class TestGuiCoreContext_is_promotable:
 class TestGuiCoreContext_is_promotable:
+    @pytest.fixture(scope="class", autouse=True)
+    def set_entity(self):
+        _ScenarioManagerFactory._build_manager()._set(a_scenario)
+        _TaskManagerFactory._build_manager()._set(a_task)
+        _JobManagerFactory._build_manager()._set(a_job)
+        _DataManagerFactory._build_manager()._set(a_datanode)
+
     def test_edit_entity(self):
     def test_edit_entity(self):
-        with patch("taipy.gui_core._context.core_get", side_effect=mock_core_get), patch(
-            "taipy.gui_core._context.is_promotable", side_effect=mock_is_true
+        with (
+            patch("taipy.gui_core._context.core_get", side_effect=mock_core_get),
+            patch("taipy.gui_core._context.is_promotable", side_effect=mock_is_true),
         ):
         ):
             gui_core_context = _GuiCoreContext(Mock())
             gui_core_context = _GuiCoreContext(Mock())
             assign = Mock()
             assign = Mock()

+ 20 - 2
tests/gui_core/test_context_is_readable.py

@@ -14,11 +14,19 @@ import typing as t
 from datetime import datetime
 from datetime import datetime
 from unittest.mock import Mock, patch
 from unittest.mock import Mock, patch
 
 
+import pytest
+
 from taipy.config.common.frequency import Frequency
 from taipy.config.common.frequency import Frequency
 from taipy.config.common.scope import Scope
 from taipy.config.common.scope import Scope
 from taipy.core import Cycle, CycleId, Job, JobId, Scenario, Task
 from taipy.core import Cycle, CycleId, Job, JobId, Scenario, Task
+from taipy.core.cycle._cycle_manager_factory import _CycleManagerFactory
+from taipy.core.data._data_manager_factory import _DataManagerFactory
 from taipy.core.data.pickle import PickleDataNode
 from taipy.core.data.pickle import PickleDataNode
+from taipy.core.job._job_manager_factory import _JobManagerFactory
+from taipy.core.scenario._scenario_manager_factory import _ScenarioManagerFactory
+from taipy.core.submission._submission_manager_factory import _SubmissionManagerFactory
 from taipy.core.submission.submission import Submission, SubmissionStatus
 from taipy.core.submission.submission import Submission, SubmissionStatus
+from taipy.core.task._task_manager_factory import _TaskManagerFactory
 from taipy.gui import Gui
 from taipy.gui import Gui
 from taipy.gui_core._context import _GuiCoreContext
 from taipy.gui_core._context import _GuiCoreContext
 
 
@@ -64,6 +72,15 @@ class MockState:
 
 
 
 
 class TestGuiCoreContext_is_readable:
 class TestGuiCoreContext_is_readable:
+    @pytest.fixture(scope="class", autouse=True)
+    def set_entity(self):
+        _CycleManagerFactory._build_manager()._set(a_cycle)
+        _ScenarioManagerFactory._build_manager()._set(a_scenario)
+        _TaskManagerFactory._build_manager()._set(a_task)
+        _JobManagerFactory._build_manager()._set(a_job)
+        _DataManagerFactory._build_manager()._set(a_datanode)
+        _SubmissionManagerFactory._build_manager()._set(a_submission)
+
     def test_scenario_adapter(self):
     def test_scenario_adapter(self):
         with patch("taipy.gui_core._context.core_get", side_effect=mock_core_get):
         with patch("taipy.gui_core._context.core_get", side_effect=mock_core_get):
             gui_core_context = _GuiCoreContext(Mock())
             gui_core_context = _GuiCoreContext(Mock())
@@ -222,8 +239,9 @@ class TestGuiCoreContext_is_readable:
                 assert outcome is None
                 assert outcome is None
 
 
     def test_act_on_jobs(self):
     def test_act_on_jobs(self):
-        with patch("taipy.gui_core._context.core_get", side_effect=mock_core_get), patch(
-            "taipy.gui_core._context.is_deletable", side_effect=mock_is_true
+        with (
+            patch("taipy.gui_core._context.core_get", side_effect=mock_core_get),
+            patch("taipy.gui_core._context.is_deletable", side_effect=mock_is_true),
         ):
         ):
             gui_core_context = _GuiCoreContext(Mock())
             gui_core_context = _GuiCoreContext(Mock())
             assign = Mock()
             assign = Mock()