Browse Source

Merge pull request #637 from Avaiga/feature/#612-expose-submission-to-taipy-core-APIs

feature/#612 exposed submission api to taipy core apis
Toan Quach 1 year ago
parent
commit
e98ec10396

+ 1 - 0
taipy/core/_entity/_entity.py

@@ -16,6 +16,7 @@ from ..notification import Notifier
 
 
 class _Entity:
+    _ID_PREFIX: str
     _MANAGER_NAME: str
     _is_in_context = False
     _in_context_attributes_changed_collector: List

+ 5 - 2
taipy/core/_entity/_reload.py

@@ -11,6 +11,7 @@
 
 import functools
 
+from .._manager._manager import _Manager
 from ..notification import EventOperation, Notifier, _make_event
 
 
@@ -89,7 +90,7 @@ def _self_setter(manager):
 
 
 @functools.lru_cache
-def _get_manager(manager: str):
+def _get_manager(manager: str) -> _Manager:
     from ..cycle._cycle_manager_factory import _CycleManagerFactory
     from ..data._data_manager_factory import _DataManagerFactory
     from ..job._job_manager_factory import _JobManagerFactory
@@ -106,4 +107,6 @@ def _get_manager(manager: str):
         "job": _JobManagerFactory._build_manager(),
         "task": _TaskManagerFactory._build_manager(),
         "submission": _SubmissionManagerFactory._build_manager(),
-    }[manager]
+    }[
+        manager
+    ]  # type: ignore

+ 2 - 0
taipy/core/_init.py

@@ -22,6 +22,8 @@ from .scenario.scenario import Scenario
 from .scenario.scenario_id import ScenarioId
 from .sequence.sequence import Sequence
 from .sequence.sequence_id import SequenceId
+from .submission.submission import Submission
+from .submission.submission_id import SubmissionId
 from .taipy import (
     cancel_job,
     clean_all_entities_by_version,

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

@@ -161,9 +161,9 @@ class _Manager(Generic[EntityType]):
         return cls._repository._export(id, folder_path)
 
     @classmethod
-    def _is_editable(cls, entity: Union[EntityType, _EntityIds]) -> bool:
+    def _is_editable(cls, entity: Union[EntityType, str]) -> bool:
         return True
 
     @classmethod
-    def _is_readable(cls, entity: Union[EntityType, _EntityIds]) -> bool:
+    def _is_readable(cls, entity: Union[EntityType, str]) -> bool:
         return True

+ 49 - 0
taipy/core/common/_check_instance.py

@@ -0,0 +1,49 @@
+# Copyright 2021-2024 Avaiga Private Limited
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+#        http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+# 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 Union
+
+from .._entity._entity import _Entity
+from ..cycle.cycle import Cycle
+from ..data.data_node import DataNode
+from ..job.job import Job
+from ..scenario.scenario import Scenario
+from ..sequence.sequence import Sequence
+from ..submission.submission import Submission
+from ..task.task import Task
+
+
+def _is_cycle(entity: Union[_Entity, str]) -> bool:
+    return isinstance(entity, Cycle) or (isinstance(entity, str) and entity.startswith(Cycle._ID_PREFIX))
+
+
+def _is_scenario(entity: Union[_Entity, str]) -> bool:
+    return isinstance(entity, Scenario) or (isinstance(entity, str) and entity.startswith(Scenario._ID_PREFIX))
+
+
+def _is_sequence(entity: Union[_Entity, str]) -> bool:
+    return isinstance(entity, Sequence) or (isinstance(entity, str) and entity.startswith(Sequence._ID_PREFIX))
+
+
+def _is_task(entity: Union[_Entity, str]) -> bool:
+    return isinstance(entity, Task) or (isinstance(entity, str) and entity.startswith(Task._ID_PREFIX))
+
+
+def _is_job(entity: Union[_Entity, str]) -> bool:
+    return isinstance(entity, Job) or (isinstance(entity, str) and entity.startswith(Job._ID_PREFIX))
+
+
+def _is_data_node(entity: Union[_Entity, str]) -> bool:
+    return isinstance(entity, DataNode) or (isinstance(entity, str) and entity.startswith(DataNode._ID_PREFIX))
+
+
+def _is_submission(entity: Union[_Entity, str]) -> bool:
+    return isinstance(entity, Submission) or (isinstance(entity, str) and entity.startswith(Submission._ID_PREFIX))

+ 10 - 0
taipy/core/exceptions/exceptions.py

@@ -151,6 +151,16 @@ class NonExistingJob(RuntimeError):
         self.message = f"Job: {job_id} does not exist."
 
 
+class SubmissionNotDeletedException(RuntimeError):
+    """Raised if there is an attempt to delete a submission that cannot be deleted.
+
+    This exception can be raised by `taipy.delete()^`.
+    """
+
+    def __init__(self, submission_id: str):
+        self.message = f"Submission: {submission_id} cannot be deleted."
+
+
 class DataNodeWritingError(RuntimeError):
     """Raised if an error happens during the writing in a data node."""
 

+ 6 - 2
taipy/core/job/_job_manager.py

@@ -57,14 +57,14 @@ class _JobManager(_Manager[Job], _VersionMixin):
 
     @classmethod
     def _delete(cls, job: Job, force=False):
-        if job.is_finished() or force:
+        if cls._is_deletable(job) or force:
             super()._delete(job.id)
             from .._orchestrator._dispatcher._job_dispatcher import _JobDispatcher
 
             _JobDispatcher._pop_dispatched_process(job.id)
         else:
             err = JobNotDeletedException(job.id)
-            cls._logger.warning(err)
+            cls._logger.error(err)
             raise err
 
     @classmethod
@@ -92,3 +92,7 @@ class _JobManager(_Manager[Job], _VersionMixin):
         if job.is_finished():
             return True
         return False
+
+    @classmethod
+    def _is_editable(cls, entity: Union[Job, str]) -> bool:
+        return False

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

@@ -11,13 +11,15 @@
 
 from typing import List, Optional, Union
 
+from .._entity._entity_ids import _EntityIds
 from .._manager._manager import _Manager
 from .._repository._abstract_repository import _AbstractRepository
 from .._version._version_mixin import _VersionMixin
+from ..exceptions.exceptions import SubmissionNotDeletedException
 from ..notification import EventEntityType, EventOperation, Notifier, _make_event
 from ..scenario.scenario import Scenario
 from ..sequence.sequence import Sequence
-from ..submission.submission import Submission
+from ..submission.submission import Submission, SubmissionId, SubmissionStatus
 from ..task.task import Task
 
 
@@ -53,3 +55,40 @@ class _SubmissionManager(_Manager[Submission], _VersionMixin):
             return submissions_of_task[0]
         else:
             return max(submissions_of_task)
+
+    @classmethod
+    def _is_editable(cls, entity: Union[Submission, str]) -> bool:
+        return False
+
+    @classmethod
+    def _delete(cls, submission: Union[Submission, SubmissionId]):
+        if isinstance(submission, str):
+            submission = cls._get(submission)
+        if cls._is_deletable(submission):
+            super()._delete(submission.id)
+        else:
+            err = SubmissionNotDeletedException(submission.id)
+            cls._logger.error(err)
+            raise err
+
+    @classmethod
+    def _hard_delete(cls, submission_id: SubmissionId):
+        submission = cls._get(submission_id)
+        entity_ids_to_delete = cls._get_children_entity_ids(submission)
+        entity_ids_to_delete.submission_ids.add(submission.id)
+        cls._delete_entities_of_multiple_types(entity_ids_to_delete)
+
+    @classmethod
+    def _get_children_entity_ids(cls, submission: Submission):
+        entity_ids = _EntityIds()
+
+        for job in submission.jobs:
+            entity_ids.job_ids.add(job.id)
+
+        return entity_ids
+
+    @classmethod
+    def _is_deletable(cls, submission: Union[Submission, SubmissionId]) -> bool:
+        if isinstance(submission, str):
+            submission = cls._get(submission)
+        return submission.is_finished() or submission.submission_status == SubmissionStatus.UNDEFINED

+ 22 - 0
taipy/core/submission/submission.py

@@ -201,6 +201,28 @@ class Submission(_Entity, _Labeled):
         else:
             self.submission_status = SubmissionStatus.UNDEFINED  # type: ignore
 
+    def is_finished(self) -> bool:
+        """Indicate if the submission is finished.
+
+        Returns:
+            True if the submission is finished.
+        """
+        return self.submission_status in [
+            SubmissionStatus.COMPLETED,
+            SubmissionStatus.FAILED,
+            SubmissionStatus.CANCELED,
+        ]
+
+    def is_deletable(self) -> bool:
+        """Indicate if the submission can be deleted.
+
+        Returns:
+            True if the submission can be deleted. False otherwise.
+        """
+        from ... import core as tp
+
+        return tp.is_deletable(self)
+
 
 @_make_event.register(Submission)
 def _make_event_for_submission(

+ 189 - 83
taipy/core/taipy.py

@@ -19,6 +19,15 @@ from taipy.logger._taipy_logger import _TaipyLogger
 
 from ._entity._entity import _Entity
 from ._version._version_manager_factory import _VersionManagerFactory
+from .common._check_instance import (
+    _is_cycle,
+    _is_data_node,
+    _is_job,
+    _is_scenario,
+    _is_sequence,
+    _is_submission,
+    _is_task,
+)
 from .common._warnings import _warn_no_core_service
 from .config.data_node_config import DataNodeConfig
 from .config.scenario_config import ScenarioConfig
@@ -44,7 +53,7 @@ from .sequence._sequence_manager_factory import _SequenceManagerFactory
 from .sequence.sequence import Sequence
 from .sequence.sequence_id import SequenceId
 from .submission._submission_manager_factory import _SubmissionManagerFactory
-from .submission.submission import Submission
+from .submission.submission import Submission, SubmissionId
 from .task._task_manager_factory import _TaskManagerFactory
 from .task.task import Task
 from .task.task_id import TaskId
@@ -73,7 +82,7 @@ def set(entity: Union[DataNode, Task, Sequence, Scenario, Cycle]):
         return _DataManagerFactory._build_manager()._set(entity)
 
 
-def is_submittable(entity: Union[Scenario, ScenarioId, Sequence, SequenceId, Task, TaskId]) -> bool:
+def is_submittable(entity: Union[Scenario, ScenarioId, Sequence, SequenceId, Task, TaskId, str]) -> bool:
     """Indicate if an entity can be submitted.
 
     This function checks if the given entity can be submitted for execution.
@@ -81,19 +90,38 @@ def is_submittable(entity: Union[Scenario, ScenarioId, Sequence, SequenceId, Tas
     Returns:
         True if the given entity can be submitted. False otherwise.
     """
-    if isinstance(entity, Scenario) or (isinstance(entity, str) and entity.startswith(Scenario._ID_PREFIX)):
-        return _ScenarioManagerFactory._build_manager()._is_submittable(entity)  # type: ignore
-    if isinstance(entity, Sequence) or (isinstance(entity, str) and entity.startswith(Sequence._ID_PREFIX)):
-        return _SequenceManagerFactory._build_manager()._is_submittable(entity)  # type: ignore
-    if isinstance(entity, Task) or (isinstance(entity, str) and entity.startswith(Task._ID_PREFIX)):
-        return _TaskManagerFactory._build_manager()._is_submittable(entity)  # type: ignore
+    if isinstance(entity, Scenario):
+        return _ScenarioManagerFactory._build_manager()._is_submittable(entity)
+    if isinstance(entity, str) and entity.startswith(Scenario._ID_PREFIX):
+        return _ScenarioManagerFactory._build_manager()._is_submittable(ScenarioId(entity))
+    if isinstance(entity, Sequence):
+        return _SequenceManagerFactory._build_manager()._is_submittable(entity)
+    if isinstance(entity, str) and entity.startswith(Sequence._ID_PREFIX):
+        return _SequenceManagerFactory._build_manager()._is_submittable(SequenceId(entity))
+    if isinstance(entity, Task):
+        return _TaskManagerFactory._build_manager()._is_submittable(entity)
+    if isinstance(entity, str) and entity.startswith(Task._ID_PREFIX):
+        return _TaskManagerFactory._build_manager()._is_submittable(TaskId(entity))
     return False
 
 
 def is_editable(
     entity: Union[
-        DataNode, Task, Job, Sequence, Scenario, Cycle, DataNodeId, TaskId, JobId, SequenceId, ScenarioId, CycleId
-    ]
+        DataNode,
+        Task,
+        Job,
+        Sequence,
+        Scenario,
+        Cycle,
+        Submission,
+        DataNodeId,
+        TaskId,
+        JobId,
+        SequenceId,
+        ScenarioId,
+        CycleId,
+        SubmissionId,
+    ],
 ) -> bool:
     """Indicate if an entity can be edited.
 
@@ -102,25 +130,54 @@ def is_editable(
     Returns:
         True if the given entity can be edited. False otherwise.
     """
-    if isinstance(entity, Cycle) or (isinstance(entity, str) and entity.startswith(Cycle._ID_PREFIX)):
-        return _CycleManagerFactory._build_manager()._is_editable(entity)  # type: ignore
-    if isinstance(entity, Scenario) or (isinstance(entity, str) and entity.startswith(Scenario._ID_PREFIX)):
-        return _ScenarioManagerFactory._build_manager()._is_editable(entity)  # type: ignore
-    if isinstance(entity, Sequence) or (isinstance(entity, str) and entity.startswith(Sequence._ID_PREFIX)):
-        return _SequenceManagerFactory._build_manager()._is_editable(entity)  # type: ignore
-    if isinstance(entity, Task) or (isinstance(entity, str) and entity.startswith(Task._ID_PREFIX)):
-        return _TaskManagerFactory._build_manager()._is_editable(entity)  # type: ignore
-    if isinstance(entity, Job) or (isinstance(entity, str) and entity.startswith(Job._ID_PREFIX)):
-        return _JobManagerFactory._build_manager()._is_editable(entity)  # type: ignore
-    if isinstance(entity, DataNode) or (isinstance(entity, str) and entity.startswith(DataNode._ID_PREFIX)):
-        return _DataManagerFactory._build_manager()._is_editable(entity)  # type: ignore
+    if isinstance(entity, Cycle):
+        return _CycleManagerFactory._build_manager()._is_editable(entity)
+    if isinstance(entity, str) and entity.startswith(Cycle._ID_PREFIX):
+        return _CycleManagerFactory._build_manager()._is_editable(CycleId(entity))
+    if isinstance(entity, Scenario):
+        return _ScenarioManagerFactory._build_manager()._is_editable(entity)
+    if isinstance(entity, str) and entity.startswith(Scenario._ID_PREFIX):
+        return _ScenarioManagerFactory._build_manager()._is_editable(ScenarioId(entity))
+    if isinstance(entity, Sequence):
+        return _SequenceManagerFactory._build_manager()._is_editable(entity)
+    if isinstance(entity, str) and entity.startswith(Sequence._ID_PREFIX):
+        return _SequenceManagerFactory._build_manager()._is_editable(SequenceId(entity))
+    if isinstance(entity, Task):
+        return _TaskManagerFactory._build_manager()._is_editable(entity)
+    if isinstance(entity, str) and entity.startswith(Task._ID_PREFIX):
+        return _TaskManagerFactory._build_manager()._is_editable(TaskId(entity))
+    if isinstance(entity, Job):
+        return _JobManagerFactory._build_manager()._is_editable(entity)
+    if isinstance(entity, str) and entity.startswith(Job._ID_PREFIX):
+        return _JobManagerFactory._build_manager()._is_editable(JobId(entity))
+    if isinstance(entity, DataNode):
+        return _DataManagerFactory._build_manager()._is_editable(entity)
+    if isinstance(entity, str) and entity.startswith(DataNode._ID_PREFIX):
+        return _DataManagerFactory._build_manager()._is_editable(DataNodeId(entity))
+    if isinstance(entity, Submission):
+        return _SubmissionManagerFactory._build_manager()._is_editable(entity)
+    if isinstance(entity, str) and entity.startswith(Submission._ID_PREFIX):
+        return _SubmissionManagerFactory._build_manager()._is_editable(SequenceId(entity))
     return False
 
 
 def is_readable(
     entity: Union[
-        DataNode, Task, Job, Sequence, Scenario, Cycle, DataNodeId, TaskId, JobId, SequenceId, ScenarioId, CycleId
-    ]
+        DataNode,
+        Task,
+        Job,
+        Sequence,
+        Scenario,
+        Cycle,
+        Submission,
+        DataNodeId,
+        TaskId,
+        JobId,
+        SequenceId,
+        ScenarioId,
+        CycleId,
+        SubmissionId,
+    ],
 ) -> bool:
     """Indicate if an entity can be read.
 
@@ -129,18 +186,34 @@ def is_readable(
     Returns:
         True if the given entity can be read. False otherwise.
     """
-    if isinstance(entity, Cycle) or (isinstance(entity, str) and entity.startswith(Cycle._ID_PREFIX)):
-        return _CycleManagerFactory._build_manager()._is_readable(entity)  # type: ignore
-    if isinstance(entity, Scenario) or (isinstance(entity, str) and entity.startswith(Scenario._ID_PREFIX)):
-        return _ScenarioManagerFactory._build_manager()._is_readable(entity)  # type: ignore
-    if isinstance(entity, Sequence) or (isinstance(entity, str) and entity.startswith(Sequence._ID_PREFIX)):
-        return _SequenceManagerFactory._build_manager()._is_readable(entity)  # type: ignore
-    if isinstance(entity, Task) or (isinstance(entity, str) and entity.startswith(Task._ID_PREFIX)):
-        return _TaskManagerFactory._build_manager()._is_readable(entity)  # type: ignore
-    if isinstance(entity, Job) or (isinstance(entity, str) and entity.startswith(Job._ID_PREFIX)):
-        return _JobManagerFactory._build_manager()._is_readable(entity)  # type: ignore
-    if isinstance(entity, DataNode) or (isinstance(entity, str) and entity.startswith(DataNode._ID_PREFIX)):
-        return _DataManagerFactory._build_manager()._is_readable(entity)  # type: ignore
+    if isinstance(entity, Cycle):
+        return _CycleManagerFactory._build_manager()._is_readable(entity)
+    if isinstance(entity, str) and entity.startswith(Cycle._ID_PREFIX):
+        return _CycleManagerFactory._build_manager()._is_readable(CycleId(entity))
+    if isinstance(entity, Scenario):
+        return _ScenarioManagerFactory._build_manager()._is_readable(entity)
+    if isinstance(entity, str) and entity.startswith(Scenario._ID_PREFIX):
+        return _ScenarioManagerFactory._build_manager()._is_readable(ScenarioId(entity))
+    if isinstance(entity, Sequence):
+        return _SequenceManagerFactory._build_manager()._is_readable(entity)
+    if isinstance(entity, str) and entity.startswith(Sequence._ID_PREFIX):
+        return _SequenceManagerFactory._build_manager()._is_readable(SequenceId(entity))
+    if isinstance(entity, Task):
+        return _TaskManagerFactory._build_manager()._is_readable(entity)
+    if isinstance(entity, str) and entity.startswith(Task._ID_PREFIX):
+        return _TaskManagerFactory._build_manager()._is_readable(TaskId(entity))
+    if isinstance(entity, Job):
+        return _JobManagerFactory._build_manager()._is_readable(entity)
+    if isinstance(entity, str) and entity.startswith(Job._ID_PREFIX):
+        return _JobManagerFactory._build_manager()._is_readable(JobId(entity))
+    if isinstance(entity, DataNode):
+        return _DataManagerFactory._build_manager()._is_readable(entity)
+    if isinstance(entity, str) and entity.startswith(DataNode._ID_PREFIX):
+        return _DataManagerFactory._build_manager()._is_readable(DataNodeId(entity))
+    if isinstance(entity, Submission):
+        return _SubmissionManagerFactory._build_manager()._is_readable(entity)
+    if isinstance(entity, str) and entity.startswith(Submission._ID_PREFIX):
+        return _SubmissionManagerFactory._build_manager()._is_readable(SequenceId(entity))
     return False
 
 
@@ -150,7 +223,7 @@ def submit(
     force: bool = False,
     wait: bool = False,
     timeout: Optional[Union[float, int]] = None,
-) -> Union[Job, List[Job]]:
+) -> Optional[Union[Job, List[Job]]]:
     """Submit a scenario, sequence or task entity for execution.
 
     This function submits the given entity for execution and returns the created job(s).
@@ -178,6 +251,7 @@ def submit(
         return _SequenceManagerFactory._build_manager()._submit(entity, force=force, wait=wait, timeout=timeout)
     if isinstance(entity, Task):
         return _TaskManagerFactory._build_manager()._submit(entity, force=force, wait=wait, timeout=timeout)
+    return None
 
 
 @overload
@@ -210,21 +284,26 @@ def exists(entity_id: JobId) -> bool:
     ...
 
 
+@overload
+def exists(entity_id: SubmissionId) -> bool:
+    ...
+
+
 @overload
 def exists(entity_id: str) -> bool:
     ...
 
 
-def exists(entity_id: Union[TaskId, DataNodeId, SequenceId, ScenarioId, JobId, CycleId, str]) -> bool:
+def exists(entity_id: Union[TaskId, DataNodeId, SequenceId, ScenarioId, JobId, CycleId, SubmissionId, str]) -> bool:
     """Check if an entity with the specified identifier exists.
 
     This function checks if an entity with the given identifier exists.
     It supports various types of entity identifiers, including `TaskId^`,
-    `DataNodeId^`, `SequenceId^`, `ScenarioId^`, `JobId^`, `CycleId^`, and string
+    `DataNodeId^`, `SequenceId^`, `ScenarioId^`, `JobId^`, `CycleId^`, `SubmissionId^`, and string
     representations.
 
     Parameters:
-        entity_id (Union[DataNodeId^, TaskId^, SequenceId^, ScenarioId^, JobId^, CycleId^]): The
+        entity_id (Union[DataNodeId^, TaskId^, SequenceId^, ScenarioId^, JobId^, CycleId^, SubmissionId^, str]): The
             identifier of the entity to check for existence.
 
     Returns:
@@ -235,21 +314,23 @@ def exists(entity_id: Union[TaskId, DataNodeId, SequenceId, ScenarioId, JobId, C
 
     Note:
         The function performs checks for various entity types
-        (`Job^`, `Cycle^`, `Scenario^`, `Sequence^`, `Task^`, `DataNode^`)
+        (`Job^`, `Cycle^`, `Scenario^`, `Sequence^`, `Task^`, `DataNode^`, `Submission^`)
         based on their respective identifier prefixes.
     """
-    if entity_id.startswith(Job._ID_PREFIX):
+    if _is_job(entity_id):
         return _JobManagerFactory._build_manager()._exists(JobId(entity_id))
-    if entity_id.startswith(Cycle._ID_PREFIX):
+    if _is_cycle(entity_id):
         return _CycleManagerFactory._build_manager()._exists(CycleId(entity_id))
-    if entity_id.startswith(Scenario._ID_PREFIX):
+    if _is_scenario(entity_id):
         return _ScenarioManagerFactory._build_manager()._exists(ScenarioId(entity_id))
-    if entity_id.startswith(Sequence._ID_PREFIX):
+    if _is_sequence(entity_id):
         return _SequenceManagerFactory._build_manager()._exists(SequenceId(entity_id))
-    if entity_id.startswith(Task._ID_PREFIX):
+    if _is_task(entity_id):
         return _TaskManagerFactory._build_manager()._exists(TaskId(entity_id))
-    if entity_id.startswith(DataNode._ID_PREFIX):
+    if _is_data_node(entity_id):
         return _DataManagerFactory._build_manager()._exists(DataNodeId(entity_id))
+    if _is_submission(entity_id):
+        return _SubmissionManagerFactory._build_manager()._exists(SubmissionId(entity_id))
     raise ModelNotFound("NOT_DETERMINED", entity_id)
 
 
@@ -284,18 +365,23 @@ def get(entity_id: JobId) -> Job:
 
 
 @overload
-def get(entity_id: str) -> Union[Task, DataNode, Sequence, Scenario, Job, Cycle]:
+def get(entity_id: SubmissionId) -> Submission:
+    ...
+
+
+@overload
+def get(entity_id: str) -> Union[Task, DataNode, Sequence, Scenario, Job, Cycle, Submission]:
     ...
 
 
 def get(
-    entity_id: Union[TaskId, DataNodeId, SequenceId, ScenarioId, JobId, CycleId, str]
-) -> Union[Task, DataNode, Sequence, Scenario, Job, Cycle]:
+    entity_id: Union[TaskId, DataNodeId, SequenceId, ScenarioId, JobId, CycleId, SubmissionId, str],
+) -> Union[Task, DataNode, Sequence, Scenario, Job, Cycle, Submission]:
     """Retrieve an entity by its unique identifier.
 
     This function allows you to retrieve an entity by specifying its identifier.
     The identifier must match the pattern of one of the supported entity types:
-    Task^, DataNode^, Sequence^, Job^, Cycle^, or Scenario^.
+    Task^, DataNode^, Sequence^, Job^, Cycle^, Submission^, or Scenario^.
 
 
     Parameters:
@@ -310,18 +396,21 @@ def get(
     Raises:
         ModelNotFound^: If the provided *entity_id* does not match any known entity pattern.
     """
-    if entity_id.startswith(Job._ID_PREFIX):
+    if _is_job(entity_id):
         return _JobManagerFactory._build_manager()._get(JobId(entity_id))
-    if entity_id.startswith(Cycle._ID_PREFIX):
+    if _is_cycle(entity_id):
         return _CycleManagerFactory._build_manager()._get(CycleId(entity_id))
-    if entity_id.startswith(Scenario._ID_PREFIX):
+    if _is_scenario(entity_id):
         return _ScenarioManagerFactory._build_manager()._get(ScenarioId(entity_id))
-    if entity_id.startswith(Sequence._ID_PREFIX):
+    if _is_sequence(entity_id):
         return _SequenceManagerFactory._build_manager()._get(SequenceId(entity_id))
-    if entity_id.startswith(Task._ID_PREFIX):
+    if _is_task(entity_id):
         return _TaskManagerFactory._build_manager()._get(TaskId(entity_id))
-    if entity_id.startswith(DataNode._ID_PREFIX):
+    if _is_data_node(entity_id):
         return _DataManagerFactory._build_manager()._get(DataNodeId(entity_id))
+    if _is_submission(entity_id):
+        return _SubmissionManagerFactory._build_manager()._get(SubmissionId(entity_id))
+
     raise ModelNotFound("NOT_DETERMINED", entity_id)
 
 
@@ -336,59 +425,74 @@ def get_tasks() -> List[Task]:
     return _TaskManagerFactory._build_manager()._get_all()
 
 
-def is_deletable(entity: Union[Scenario, Job, ScenarioId, JobId]) -> bool:
-    """Check if a `Scenario^` or a `Job^` can be deleted.
+def is_deletable(entity: Union[Scenario, Job, Submission, ScenarioId, JobId, SubmissionId]) -> bool:
+    """Check if a `Scenario^`, a `Job^` or a `Submission^` can be deleted.
 
     This function determines whether a scenario or a job can be safely
     deleted without causing conflicts or issues.
 
     Parameters:
-        entity (Union[Scenario, Job, ScenarioId, JobId]): The scenario or job to check.
+        entity (Union[Scenario, Job, Submission, ScenarioId, JobId, SubmissionId]): The scenario,
+        job or submission to check.
 
     Returns:
-        True if the given scenario or job can be deleted. False otherwise.
+        True if the given scenario, job or submission can be deleted. False otherwise.
     """
-    if isinstance(entity, str) and entity.startswith(Job._ID_PREFIX) or isinstance(entity, Job):
-        return _JobManagerFactory._build_manager()._is_deletable(entity)  # type: ignore
-    if isinstance(entity, str) and entity.startswith(Scenario._ID_PREFIX) or isinstance(entity, Scenario):
-        return _ScenarioManagerFactory._build_manager()._is_deletable(entity)  # type: ignore
+    if isinstance(entity, Job):
+        return _JobManagerFactory._build_manager()._is_deletable(entity)
+    if isinstance(entity, str) and entity.startswith(Job._ID_PREFIX):
+        return _JobManagerFactory._build_manager()._is_deletable(JobId(entity))
+    if isinstance(entity, Scenario):
+        return _ScenarioManagerFactory._build_manager()._is_deletable(entity)
+    if isinstance(entity, str) and entity.startswith(Scenario._ID_PREFIX):
+        return _ScenarioManagerFactory._build_manager()._is_deletable(ScenarioId(entity))
+    if isinstance(entity, Submission):
+        return _SubmissionManagerFactory._build_manager()._is_deletable(entity)
+    if isinstance(entity, str) and entity.startswith(Submission._ID_PREFIX):
+        return _SubmissionManagerFactory._build_manager()._is_deletable(SubmissionId(entity))
     return True
 
 
-def delete(entity_id: Union[TaskId, DataNodeId, SequenceId, ScenarioId, JobId, CycleId]):
+def delete(entity_id: Union[TaskId, DataNodeId, SequenceId, ScenarioId, JobId, CycleId, SubmissionId]):
     """Delete an entity and its nested entities.
 
     This function deletes the specified entity and recursively deletes all its nested entities.
     The behavior varies depending on the type of entity provided:
 
     - If a `CycleId` is provided, the nested scenarios, tasks, data nodes, and jobs are deleted.
-    - If a `ScenarioId` is provided, the nested tasks, data nodes, and jobs are deleted.
+    - If a `ScenarioId` is provided, the nested sequences, tasks, data nodes, submissions and jobs are deleted.
       If the scenario is primary, it can only be deleted if it is the only scenario in the cycle.
       In that case, its cycle is also deleted. Use the `is_deletable()^` function to check if
       the scenario can be deleted.
     - If a `SequenceId` is provided, the related jobs are deleted.
     - If a `TaskId` is provided, the related data nodes, and jobs are deleted.
+    - If a `DataNodeId` is provided, the data node is deleted.
+    - If a `SubmissionId^` is provided, the related jobs are deleted.
+      The submission can only be deleted if the execution has been finished.
+    - If a `JobId^` is provided, the job entity can only be deleted if the execution has been finished.
 
     Parameters:
-        entity_id (Union[TaskId, DataNodeId, SequenceId, ScenarioId, JobId, CycleId]):
+        entity_id (Union[TaskId, DataNodeId, SequenceId, ScenarioId, SubmissionId, JobId, CycleId]):
             The identifier of the entity to delete.
 
     Raises:
         ModelNotFound: No entity corresponds to the specified *entity_id*.
     """
-    if entity_id.startswith(Job._ID_PREFIX):
+    if _is_job(entity_id):
         job_manager = _JobManagerFactory._build_manager()
-        return job_manager._delete(job_manager._get(JobId(entity_id)))  # type: ignore
-    if entity_id.startswith(Cycle._ID_PREFIX):
+        return job_manager._delete(job_manager._get(JobId(entity_id)))
+    if _is_cycle(entity_id):
         return _CycleManagerFactory._build_manager()._hard_delete(CycleId(entity_id))
-    if entity_id.startswith(Scenario._ID_PREFIX):
+    if _is_scenario(entity_id):
         return _ScenarioManagerFactory._build_manager()._hard_delete(ScenarioId(entity_id))
-    if entity_id.startswith(Sequence._ID_PREFIX):
+    if _is_sequence(entity_id):
         return _SequenceManagerFactory._build_manager()._hard_delete(SequenceId(entity_id))
-    if entity_id.startswith(Task._ID_PREFIX):
+    if _is_task(entity_id):
         return _TaskManagerFactory._build_manager()._hard_delete(TaskId(entity_id))
-    if entity_id.startswith(DataNode._ID_PREFIX):
+    if _is_data_node(entity_id):
         return _DataManagerFactory._build_manager()._delete(DataNodeId(entity_id))
+    if _is_submission(entity_id):
+        return _SubmissionManagerFactory._build_manager()._hard_delete(SubmissionId(entity_id))
     raise ModelNotFound("NOT_DETERMINED", entity_id)
 
 
@@ -408,14 +512,15 @@ def get_scenarios(cycle: Optional[Cycle] = None, tag: Optional[str] = None) -> L
         The list of scenarios filtered by cycle or tag. If no filtering criteria
             are provided, this method returns all existing scenarios.
     """
+    scenario_manager = _ScenarioManagerFactory._build_manager()
     if not cycle and not tag:
-        return _ScenarioManagerFactory._build_manager()._get_all()
+        return scenario_manager._get_all()
     if cycle and not tag:
-        return _ScenarioManagerFactory._build_manager()._get_all_by_cycle(cycle)
+        return scenario_manager._get_all_by_cycle(cycle)
     if not cycle and tag:
-        return _ScenarioManagerFactory._build_manager()._get_all_by_tag(tag)
+        return scenario_manager._get_all_by_tag(tag)
     if cycle and tag:
-        cycles_scenarios = _ScenarioManagerFactory._build_manager()()._get_all_by_cycle(cycle)
+        cycles_scenarios = scenario_manager._get_all_by_cycle(cycle)
         return [scenario for scenario in cycles_scenarios if scenario.has_tag(tag)]
     return []
 
@@ -738,9 +843,9 @@ def create_global_data_node(config: DataNodeConfig) -> DataNode:
     """
     # Check if the data node config has GLOBAL scope
     if config.scope is not Scope.GLOBAL:
-        raise DataNodeConfigIsNotGlobal(config.id)  # type: ignore
+        raise DataNodeConfigIsNotGlobal(config.id)
 
-    if dns := _DataManagerFactory._build_manager()._get_by_config_id(config.id):  # type: ignore
+    if dns := _DataManagerFactory._build_manager()._get_by_config_id(config.id):
         return dns[0]
     return _DataManagerFactory._build_manager()._create_and_set(config, None, None)
 
@@ -770,6 +875,7 @@ def clean_all_entities_by_version(version_number=None) -> bool:
         return False
 
     _JobManagerFactory._build_manager()._delete_by_version(version_number)
+    _SubmissionManagerFactory._build_manager()._delete_by_version(version_number)
     _ScenarioManagerFactory._build_manager()._delete_by_version(version_number)
     _SequenceManagerFactory._build_manager()._delete_by_version(version_number)
     _TaskManagerFactory._build_manager()._delete_by_version(version_number)
@@ -800,7 +906,7 @@ def export_scenario(
 
     manager = _ScenarioManagerFactory._build_manager()
     scenario = manager._get(scenario_id)
-    entity_ids = manager._get_children_entity_ids(scenario)  # type: ignore
+    entity_ids = manager._get_children_entity_ids(scenario)
     entity_ids.scenario_ids = {scenario_id}
     entity_ids.cycle_ids = {scenario.cycle.id}
 
@@ -850,7 +956,7 @@ def get_parents(
                 parent_dict[k] = value
 
     if isinstance(entity, str):
-        entity = get(entity)  # type: ignore
+        entity = get(entity)
 
     parent_dict = parent_dict or dict()
 

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

@@ -60,6 +60,7 @@ def test_create_jobs():
     assert job_1.submit_id == "submit_id"
     assert job_1.submit_entity_id == "secnario_id"
     assert job_1.force
+    assert not _JobManager._is_editable(job_1)
 
     job_2 = _JobManager._create(task, [print], "submit_id_1", "secnario_id", False)
     assert _JobManager._get(job_2.id) == job_2
@@ -69,6 +70,7 @@ def test_create_jobs():
     assert job_2.submit_id == "submit_id_1"
     assert job_2.submit_entity_id == "secnario_id"
     assert not job_2.force
+    assert not _JobManager._is_editable(job_2)
 
 
 def test_get_job():

+ 3 - 2
tests/core/notification/test_notifier.py

@@ -800,7 +800,7 @@ def test_publish_deletion_event():
         registration_queue.get()
 
     tp.clean_all_entities_by_version()
-    assert registration_queue.qsize() == 5
+    assert registration_queue.qsize() == 6
 
     published_events = []
     while registration_queue.qsize() != 0:
@@ -808,12 +808,13 @@ def test_publish_deletion_event():
 
     expected_event_types = [
         EventEntityType.JOB,
+        EventEntityType.SUBMISSION,
         EventEntityType.CYCLE,
         EventEntityType.SCENARIO,
         EventEntityType.TASK,
         EventEntityType.DATA_NODE,
     ]
-    expected_event_entity_id = [None, cycle.id, scenario.id, None, None]
+    expected_event_entity_id = [None, None, cycle.id, scenario.id, None, None]
 
     assert all(
         event.entity_type == expected_event_types[i]

+ 40 - 0
tests/core/submission/test_submission.py

@@ -736,3 +736,43 @@ def test_update_submission_status_with_two_jobs_failed(job_ids, job_statuses, ex
 )
 def test_update_submission_status_with_two_jobs_canceled(job_ids, job_statuses, expected_submission_statuses):
     __test_update_submission_status_with_two_jobs(job_ids, job_statuses, expected_submission_statuses)
+
+
+def test_is_finished():
+    submission_manager = _SubmissionManagerFactory._build_manager()
+
+    submission = Submission("entity_id", "entity_type", "entity_config_id", "submission_id")
+    submission_manager._set(submission)
+
+    assert len(submission_manager._get_all()) == 1
+
+    assert submission._submission_status == SubmissionStatus.SUBMITTED
+    assert not submission.is_finished()
+
+    submission.submission_status = SubmissionStatus.UNDEFINED
+    assert submission.submission_status == SubmissionStatus.UNDEFINED
+    assert not submission.is_finished()
+
+    submission.submission_status = SubmissionStatus.CANCELED
+    assert submission.submission_status == SubmissionStatus.CANCELED
+    assert submission.is_finished()
+
+    submission.submission_status = SubmissionStatus.FAILED
+    assert submission.submission_status == SubmissionStatus.FAILED
+    assert submission.is_finished()
+
+    submission.submission_status = SubmissionStatus.BLOCKED
+    assert submission.submission_status == SubmissionStatus.BLOCKED
+    assert not submission.is_finished()
+
+    submission.submission_status = SubmissionStatus.RUNNING
+    assert submission.submission_status == SubmissionStatus.RUNNING
+    assert not submission.is_finished()
+
+    submission.submission_status = SubmissionStatus.PENDING
+    assert submission.submission_status == SubmissionStatus.PENDING
+    assert not submission.is_finished()
+
+    submission.submission_status = SubmissionStatus.COMPLETED
+    assert submission.submission_status == SubmissionStatus.COMPLETED
+    assert submission.is_finished()

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

@@ -12,10 +12,16 @@
 from datetime import datetime
 from time import sleep
 
+import pytest
+
 from taipy.core._version._version_manager_factory import _VersionManagerFactory
+from taipy.core.exceptions.exceptions import SubmissionNotDeletedException
+from taipy.core.job._job_manager_factory import _JobManagerFactory
+from taipy.core.job.job import Job
 from taipy.core.submission._submission_manager_factory import _SubmissionManagerFactory
 from taipy.core.submission.submission import Submission
 from taipy.core.submission.submission_status import SubmissionStatus
+from taipy.core.task._task_manager_factory import _TaskManagerFactory
 from taipy.core.task.task import Task
 
 
@@ -51,12 +57,24 @@ def test_get_all_submission():
     version_manager = _VersionManagerFactory._build_manager()
 
     submission_manager._set(
-        Submission("entity_id", "submission_id", "entity_config_id", version=version_manager._get_latest_version())
+        Submission(
+            "entity_id",
+            "entity_type",
+            "entity_config_id",
+            "submission_id",
+            version=version_manager._get_latest_version(),
+        )
     )
     for version_name in ["abc", "xyz"]:
         for i in range(10):
             submission_manager._set(
-                Submission("entity_id", f"submission_{version_name}_{i}", "entity_config_id", version=f"{version_name}")
+                Submission(
+                    "entity_id",
+                    "entity_type",
+                    "entity_config_id",
+                    f"submission_{version_name}_{i}",
+                    version=f"{version_name}",
+                )
             )
 
     assert len(submission_manager._get_all()) == 1
@@ -96,11 +114,17 @@ def test_get_latest_submission():
 def test_delete_submission():
     submission_manager = _SubmissionManagerFactory._build_manager()
 
-    submission = Submission("entity_id", "submission_id", "entity_config_id")
+    submission = Submission("entity_id", "entity_type", "entity_config_id", "submission_id")
+
     submission_manager._set(submission)
 
+    with pytest.raises(SubmissionNotDeletedException):
+        submission_manager._delete(submission.id)
+
+    submission.submission_status = SubmissionStatus.COMPLETED
+
     for i in range(10):
-        submission_manager._set(Submission("entity_id", f"submission_{i}", "entity_config_id"))
+        submission_manager._set(Submission("entity_id", "entity_type", "entity_config_id", f"submission_{i}"))
 
     assert len(submission_manager._get_all()) == 11
     assert isinstance(submission_manager._get(submission.id), Submission)
@@ -111,3 +135,82 @@ def test_delete_submission():
 
     submission_manager._delete_all()
     assert len(submission_manager._get_all()) == 0
+
+
+def test_is_deletable():
+    submission_manager = _SubmissionManagerFactory._build_manager()
+
+    submission = Submission("entity_id", "entity_type", "entity_config_id", "submission_id")
+    submission_manager._set(submission)
+
+    assert len(submission_manager._get_all()) == 1
+
+    assert submission._submission_status == SubmissionStatus.SUBMITTED
+    assert not submission.is_deletable()
+    assert not submission_manager._is_deletable(submission)
+    assert not submission_manager._is_deletable(submission.id)
+
+    submission.submission_status = SubmissionStatus.UNDEFINED
+    assert submission.submission_status == SubmissionStatus.UNDEFINED
+    assert submission.is_deletable()
+    assert submission_manager._is_deletable(submission)
+    assert submission_manager._is_deletable(submission.id)
+
+    submission.submission_status = SubmissionStatus.CANCELED
+    assert submission.submission_status == SubmissionStatus.CANCELED
+    assert submission.is_deletable()
+    assert submission_manager._is_deletable(submission)
+    assert submission_manager._is_deletable(submission.id)
+
+    submission.submission_status = SubmissionStatus.FAILED
+    assert submission.submission_status == SubmissionStatus.FAILED
+    assert submission.is_deletable()
+    assert submission_manager._is_deletable(submission)
+    assert submission_manager._is_deletable(submission.id)
+
+    submission.submission_status = SubmissionStatus.BLOCKED
+    assert submission.submission_status == SubmissionStatus.BLOCKED
+    assert not submission.is_deletable()
+    assert not submission_manager._is_deletable(submission)
+    assert not submission_manager._is_deletable(submission.id)
+
+    submission.submission_status = SubmissionStatus.RUNNING
+    assert submission.submission_status == SubmissionStatus.RUNNING
+    assert not submission.is_deletable()
+    assert not submission_manager._is_deletable(submission)
+    assert not submission_manager._is_deletable(submission.id)
+
+    submission.submission_status = SubmissionStatus.PENDING
+    assert submission.submission_status == SubmissionStatus.PENDING
+    assert not submission.is_deletable()
+    assert not submission_manager._is_deletable(submission)
+    assert not submission_manager._is_deletable(submission.id)
+
+    submission.submission_status = SubmissionStatus.COMPLETED
+    assert submission.submission_status == SubmissionStatus.COMPLETED
+    assert submission.is_deletable()
+    assert submission_manager._is_deletable(submission)
+    assert submission_manager._is_deletable(submission.id)
+
+
+def test_hard_delete():
+    submission_manager = _SubmissionManagerFactory._build_manager()
+    job_manager = _JobManagerFactory._build_manager()
+    task_manager = _TaskManagerFactory._build_manager()
+
+    task = Task("task_config_id", {}, print)
+    submission = Submission(task.id, task._ID_PREFIX, task.config_id, "SUBMISSION_submission_id")
+    job_1 = Job("JOB_job_id_1", task, submission.id, submission.entity_id)  # will be deleted with submission
+    job_2 = Job("JOB_job_id_2", task, "SUBMISSION_submission_id_2", submission.entity_id)  # will not be deleted
+    submission.jobs = [job_1]
+
+    task_manager._set(task)
+    submission_manager._set(submission)
+    job_manager._set(job_1)
+    job_manager._set(job_2)
+
+    assert len(job_manager._get_all()) == 2
+    assert len(submission_manager._get_all()) == 1
+    submission_manager._hard_delete(submission.id)
+    assert len(job_manager._get_all()) == 1
+    assert len(submission_manager._get_all()) == 0

+ 66 - 0
tests/core/submission/test_submission_manager_with_sql_repo.py

@@ -12,8 +12,11 @@
 from datetime import datetime
 from time import sleep
 
+import pytest
+
 from taipy.core import Task
 from taipy.core._version._version_manager_factory import _VersionManagerFactory
+from taipy.core.exceptions.exceptions import SubmissionNotDeletedException
 from taipy.core.submission._submission_manager_factory import _SubmissionManagerFactory
 from taipy.core.submission.submission import Submission
 from taipy.core.submission.submission_status import SubmissionStatus
@@ -111,6 +114,11 @@ def test_delete_submission(init_sql_repo):
     submission = Submission("entity_id", "submission_id", "entity_config_id")
     submission_manager._set(submission)
 
+    with pytest.raises(SubmissionNotDeletedException):
+        submission_manager._delete(submission.id)
+
+    submission.submission_status = SubmissionStatus.COMPLETED
+
     for i in range(10):
         submission_manager._set(Submission("entity_id", f"submission_{i}", "entity_config_id"))
 
@@ -123,3 +131,61 @@ def test_delete_submission(init_sql_repo):
 
     submission_manager._delete_all()
     assert len(submission_manager._get_all()) == 0
+
+
+def test_is_deletable(init_sql_repo):
+    init_managers()
+
+    submission_manager = _SubmissionManagerFactory._build_manager()
+
+    submission = Submission("entity_id", "submission_id", "entity_config_id")
+    submission_manager._set(submission)
+
+    assert len(submission_manager._get_all()) == 1
+
+    assert submission._submission_status == SubmissionStatus.SUBMITTED
+    assert not submission.is_deletable()
+    assert not submission_manager._is_deletable(submission)
+    assert not submission_manager._is_deletable(submission.id)
+
+    submission.submission_status = SubmissionStatus.UNDEFINED
+    assert submission.submission_status == SubmissionStatus.UNDEFINED
+    assert submission.is_deletable()
+    assert submission_manager._is_deletable(submission)
+    assert submission_manager._is_deletable(submission.id)
+
+    submission.submission_status = SubmissionStatus.CANCELED
+    assert submission.submission_status == SubmissionStatus.CANCELED
+    assert submission.is_deletable()
+    assert submission_manager._is_deletable(submission)
+    assert submission_manager._is_deletable(submission.id)
+
+    submission.submission_status = SubmissionStatus.FAILED
+    assert submission.submission_status == SubmissionStatus.FAILED
+    assert submission.is_deletable()
+    assert submission_manager._is_deletable(submission)
+    assert submission_manager._is_deletable(submission.id)
+
+    submission.submission_status = SubmissionStatus.BLOCKED
+    assert submission.submission_status == SubmissionStatus.BLOCKED
+    assert not submission.is_deletable()
+    assert not submission_manager._is_deletable(submission)
+    assert not submission_manager._is_deletable(submission.id)
+
+    submission.submission_status = SubmissionStatus.RUNNING
+    assert submission.submission_status == SubmissionStatus.RUNNING
+    assert not submission.is_deletable()
+    assert not submission_manager._is_deletable(submission)
+    assert not submission_manager._is_deletable(submission.id)
+
+    submission.submission_status = SubmissionStatus.PENDING
+    assert submission.submission_status == SubmissionStatus.PENDING
+    assert not submission.is_deletable()
+    assert not submission_manager._is_deletable(submission)
+    assert not submission_manager._is_deletable(submission.id)
+
+    submission.submission_status = SubmissionStatus.COMPLETED
+    assert submission.submission_status == SubmissionStatus.COMPLETED
+    assert submission.is_deletable()
+    assert submission_manager._is_deletable(submission)
+    assert submission_manager._is_deletable(submission.id)

+ 40 - 1
tests/core/test_taipy.py

@@ -32,6 +32,8 @@ from taipy.core import (
     ScenarioId,
     Sequence,
     SequenceId,
+    Submission,
+    SubmissionId,
     Task,
     TaskId,
 )
@@ -47,6 +49,7 @@ from taipy.core.exceptions.exceptions import DataNodeConfigIsNotGlobal, InvalidE
 from taipy.core.job._job_manager import _JobManager
 from taipy.core.job.job import Job
 from taipy.core.scenario._scenario_manager import _ScenarioManager
+from taipy.core.submission._submission_manager import _SubmissionManager
 from taipy.core.task._task_manager import _TaskManager
 
 
@@ -130,6 +133,16 @@ class TestTaipy:
             tp.is_editable(data_node)
             mck.assert_called_once_with(data_node)
 
+        with mock.patch("taipy.core.submission._submission_manager._SubmissionManager._is_editable") as mck:
+            submission = Submission(scenario.id, scenario._ID_PREFIX, scenario.config_id, "submission_id")
+            tp.is_editable(submission)
+            mck.assert_called_once_with(submission)
+
+        with mock.patch("taipy.core.submission._submission_manager._SubmissionManager._is_editable") as mck:
+            submission_id = SubmissionId("SUBMISSION_id")
+            tp.is_editable(submission_id)
+            mck.assert_called_once_with(submission_id)
+
     def test_is_editable(self):
         a_date = datetime.datetime.now()
         cycle = Cycle(Frequency.DAILY, {}, a_date, a_date, a_date)
@@ -137,18 +150,21 @@ class TestTaipy:
         task = Task("task_config_id", {}, print)
         job = Job(JobId("job_id"), task, "submit_id", scenario.id)
         dn = PickleDataNode(config_id="data_node_config_id", scope=Scope.SCENARIO)
+        submission = Submission(scenario.id, scenario._ID_PREFIX, scenario.config_id, "submission_id")
         _CycleManager._set(cycle)
         _ScenarioManager._set(scenario)
         _TaskManager._set(task)
         _JobManager._set(job)
         _DataManager._set(dn)
+        _SubmissionManager._set(submission)
         sequence = scenario.sequences["sequence"]
 
         assert tp.is_editable(scenario)
         assert tp.is_editable(sequence)
         assert tp.is_editable(task)
         assert tp.is_editable(cycle)
-        assert tp.is_editable(job)
+        assert not tp.is_editable(job)
+        assert not tp.is_editable(submission)
         assert tp.is_editable(dn)
 
     def test_is_readable_is_called(self, cycle, job, data_node):
@@ -209,6 +225,16 @@ class TestTaipy:
             tp.is_readable(data_node)
             mck.assert_called_once_with(data_node)
 
+        with mock.patch("taipy.core.submission._submission_manager._SubmissionManager._is_readable") as mck:
+            submission = Submission(scenario.id, scenario._ID_PREFIX, scenario.config_id, "submission_id")
+            tp.is_readable(submission)
+            mck.assert_called_once_with(submission)
+
+        with mock.patch("taipy.core.submission._submission_manager._SubmissionManager._is_readable") as mck:
+            submission_id = SubmissionId("SUBMISSION_id")
+            tp.is_readable(submission_id)
+            mck.assert_called_once_with(submission_id)
+
     def test_is_readable(self):
         a_date = datetime.datetime.now()
         cycle = Cycle(Frequency.DAILY, {}, a_date, a_date, a_date)
@@ -216,11 +242,13 @@ class TestTaipy:
         task = Task("task_config_id", {}, print)
         job = Job(JobId("a_job_id"), task, "submit_id", scenario.id)
         dn = PickleDataNode(config_id="a_data_node_config_id", scope=Scope.SCENARIO)
+        submission = Submission(scenario.id, scenario._ID_PREFIX, scenario.config_id, "submission_id")
         _CycleManager._set(cycle)
         _ScenarioManager._set(scenario)
         _TaskManager._set(task)
         _JobManager._set(job)
         _DataManager._set(dn)
+        _SubmissionManager._set(submission)
         sequence = scenario.sequences["sequence"]
 
         assert tp.is_readable(scenario)
@@ -229,6 +257,7 @@ class TestTaipy:
         assert tp.is_readable(cycle)
         assert tp.is_readable(job)
         assert tp.is_readable(dn)
+        assert tp.is_readable(submission)
 
     def test_is_submittable_is_called(self):
         with mock.patch("taipy.core.scenario._scenario_manager._ScenarioManager._is_submittable") as mck:
@@ -360,6 +389,16 @@ class TestTaipy:
             tp.is_deletable(job)
             mck.assert_called_once_with(job)
 
+        with mock.patch("taipy.core.submission._submission_manager._SubmissionManager._is_deletable") as mck:
+            submission = Submission(scenario.id, scenario._ID_PREFIX, scenario.config_id, "submission_id")
+            tp.is_deletable(submission)
+            mck.assert_called_once_with(submission)
+
+        with mock.patch("taipy.core.submission._submission_manager._SubmissionManager._is_deletable") as mck:
+            submission_id = SubmissionId("SUBMISSION_id")
+            tp.is_deletable(submission_id)
+            mck.assert_called_once_with(submission_id)
+
     def test_is_promotable(self):
         with mock.patch("taipy.core.scenario._scenario_manager._ScenarioManager._is_promotable_to_primary") as mck:
             scenario_id = ScenarioId("SCENARIO_id")