Prechádzať zdrojové kódy

Merge pull request #664 from Avaiga/feature/#660-remove-in-memory-state

Remove in memory state in the submission
Toan Quach 1 rok pred
rodič
commit
9ebe56f0b1

+ 2 - 0
taipy/core/_init.py

@@ -42,11 +42,13 @@ from .taipy import (
     get_entities_by_config_id,
     get_jobs,
     get_latest_job,
+    get_latest_submission,
     get_parents,
     get_primary,
     get_primary_scenarios,
     get_scenarios,
     get_sequences,
+    get_submissions,
     get_tasks,
     is_deletable,
     is_editable,

+ 4 - 4
taipy/core/_orchestrator/_dispatcher/_job_dispatcher.py

@@ -29,7 +29,7 @@ class _JobDispatcher(threading.Thread):
 
     _STOP_FLAG = False
     _dispatched_processes: Dict = {}
-    __logger = _TaipyLogger._get_logger()
+    _logger = _TaipyLogger._get_logger()
     _nb_available_workers: int = 1
 
     def __init__(self, orchestrator: Optional[_AbstractOrchestrator]):
@@ -72,13 +72,13 @@ class _JobDispatcher(threading.Thread):
     def _execute_job(self, job: Job):
         if job.force or self._needs_to_run(job.task):
             if job.force:
-                self.__logger.info(f"job {job.id} is forced to be executed.")
+                self._logger.info(f"job {job.id} is forced to be executed.")
             job.running()
             self._dispatch(job)
         else:
             job._unlock_edit_on_outputs()
             job.skipped()
-            self.__logger.info(f"job {job.id} is skipped.")
+            self._logger.info(f"job {job.id} is skipped.")
 
     def _execute_jobs_synchronously(self):
         while not self.orchestrator.jobs_to_run.empty():
@@ -86,7 +86,7 @@ class _JobDispatcher(threading.Thread):
                 try:
                     job = self.orchestrator.jobs_to_run.get()
                 except Exception:  # In case the last job of the queue has been removed.
-                    self.__logger.warning(f"{job.id} is no longer in the list of jobs to run.")
+                    self._logger.warning(f"{job.id} is no longer in the list of jobs to run.")
             self._execute_job(job)
 
     @staticmethod

+ 1 - 1
taipy/core/_orchestrator/_dispatcher/_standalone_job_dispatcher.py

@@ -43,8 +43,8 @@ class _StandaloneJobDispatcher(_JobDispatcher):
         future = self._executor.submit(_TaskFunctionWrapper(job.id, job.task), config_as_string=config_as_string)
 
         self._set_dispatched_processes(job.id, future)  # type: ignore
-        future.add_done_callback(self._release_worker)
         future.add_done_callback(partial(self._update_job_status_from_future, job))
+        future.add_done_callback(self._release_worker)
 
     def _release_worker(self, _):
         self._nb_available_workers += 1

+ 12 - 13
taipy/core/_orchestrator/_orchestrator.py

@@ -14,7 +14,7 @@ from datetime import datetime
 from multiprocessing import Lock
 from queue import Queue
 from time import sleep
-from typing import Callable, Dict, Iterable, List, Optional, Set, Union
+from typing import Callable, Iterable, List, Optional, Set, Union
 
 from taipy.config.config import Config
 from taipy.logger._taipy_logger import _TaipyLogger
@@ -39,7 +39,6 @@ class _Orchestrator(_AbstractOrchestrator):
     blocked_jobs: List = []
     lock = Lock()
     __logger = _TaipyLogger._get_logger()
-    _submission_entities: Dict[str, Submission] = {}
 
     @classmethod
     def initialize(cls):
@@ -66,9 +65,10 @@ class _Orchestrator(_AbstractOrchestrator):
                 finished in asynchronous mode.
              timeout (Union[float, int]): The optional maximum number of seconds to wait for the jobs to be finished
                 before returning.
-             **properties (dict[str, any]): A keyworded variable length list of additional arguments.
+             **properties (dict[str, any]): A keyworded variable length list of user additional arguments
+                that will be stored within the `Submission^`. It can be accessed via `Submission.properties^`.
         Returns:
-            The created Jobs.
+            The created `Submission^` containing the information about the submission.
         """
         submission = _SubmissionManagerFactory._build_manager()._create(
             submittable.id,  # type: ignore
@@ -76,7 +76,6 @@ class _Orchestrator(_AbstractOrchestrator):
             getattr(submittable, "config_id", None),
             **properties,
         )
-        cls._submission_entities[submission.id] = submission
         jobs = []
         tasks = submittable._get_sorted_tasks()
         with cls.lock:
@@ -120,15 +119,15 @@ class _Orchestrator(_AbstractOrchestrator):
                 in asynchronous mode.
              timeout (Union[float, int]): The optional maximum number of seconds to wait for the job
                 to be finished before returning.
-             **properties (dict[str, any]): A keyworded variable length list of additional arguments.
+             **properties (dict[str, any]): A keyworded variable length list of user additional arguments
+                that will be stored within the `Submission^`. It can be accessed via `Submission.properties^`.
         Returns:
-            The created `Job^`.
+            The created `Submission^` containing the information about the submission.
         """
         submission = _SubmissionManagerFactory._build_manager()._create(
             task.id, task._ID_PREFIX, task.config_id, **properties
         )
         submit_id = submission.id
-        cls._submission_entities[submission.id] = submission
         with cls.lock:
             job = cls._lock_dn_output_and_create_job(
                 task,
@@ -147,10 +146,6 @@ class _Orchestrator(_AbstractOrchestrator):
                 cls._wait_until_job_finished(job, timeout=timeout)
         return submission
 
-    @classmethod
-    def _update_submission_status(cls, job: Job):
-        cls._submission_entities[job.submit_id]._update_submission_status(job)
-
     @classmethod
     def _lock_dn_output_and_create_job(
         cls,
@@ -168,6 +163,10 @@ class _Orchestrator(_AbstractOrchestrator):
 
         return job
 
+    @classmethod
+    def _update_submission_status(cls, job: Job):
+        _SubmissionManagerFactory._build_manager()._get(job.submit_id)._update_submission_status(job)
+
     @classmethod
     def _orchestrate_job_to_run_or_block(cls, jobs: List[Job]):
         blocked_jobs = []
@@ -194,7 +193,7 @@ class _Orchestrator(_AbstractOrchestrator):
             return True
 
         start = datetime.now()
-        jobs = jobs if isinstance(jobs, Iterable) else [jobs]
+        jobs = list(jobs) if isinstance(jobs, Iterable) else [jobs]
         index = 0
         while __check_if_timeout(start, timeout) and index < len(jobs):
             try:

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

@@ -50,9 +50,11 @@ class _JobManager(_Manager[Job], _VersionMixin):
             force=force,
             version=version,
         )
-        Notifier.publish(_make_event(job, EventOperation.CREATION))
         job._on_status_change(*callbacks)
         cls._set(job)
+
+        Notifier.publish(_make_event(job, EventOperation.CREATION))
+
         return job
 
     @classmethod

+ 15 - 0
taipy/core/submission/_submission_converter.py

@@ -31,6 +31,12 @@ class _SubmissionConverter(_AbstractConverter):
             creation_date=submission._creation_date.isoformat(),
             submission_status=submission._submission_status,
             version=submission._version,
+            is_abandoned=submission._is_abandoned,
+            is_completed=submission._is_completed,
+            is_canceled=submission._is_canceled,
+            running_jobs=list(submission._running_jobs),
+            blocked_jobs=list(submission._blocked_jobs),
+            pending_jobs=list(submission._pending_jobs),
         )
 
     @classmethod
@@ -46,4 +52,13 @@ class _SubmissionConverter(_AbstractConverter):
             submission_status=model.submission_status,
             version=model.version,
         )
+
+        submission._is_abandoned = model.is_abandoned
+        submission._is_completed = model.is_completed
+        submission._is_canceled = model.is_canceled
+
+        submission._running_jobs = set(model.running_jobs)
+        submission._blocked_jobs = set(model.blocked_jobs)
+        submission._pending_jobs = set(model.pending_jobs)
+
         return submission

+ 25 - 1
taipy/core/submission/_submission_model.py

@@ -12,7 +12,7 @@
 from dataclasses import dataclass
 from typing import Any, Dict, List, Optional, Union
 
-from sqlalchemy import JSON, Column, Enum, String, Table
+from sqlalchemy import JSON, Boolean, Column, Enum, String, Table
 
 from .._repository._base_taipy_model import _BaseModel
 from .._repository.db._sql_base_model import mapper_registry
@@ -35,6 +35,12 @@ class _SubmissionModel(_BaseModel):
         Column("creation_date", String),
         Column("submission_status", Enum(SubmissionStatus)),
         Column("version", String),
+        Column("is_completed", Boolean),
+        Column("is_abandoned", Boolean),
+        Column("is_canceled", Boolean),
+        Column("running_jobs", JSON),
+        Column("blocked_jobs", JSON),
+        Column("pending_jobs", JSON),
     )
     id: str
     entity_id: str
@@ -45,6 +51,12 @@ class _SubmissionModel(_BaseModel):
     creation_date: str
     submission_status: SubmissionStatus
     version: str
+    is_completed: bool
+    is_abandoned: bool
+    is_canceled: bool
+    running_jobs: List[str]
+    blocked_jobs: List[str]
+    pending_jobs: List[str]
 
     @staticmethod
     def from_dict(data: Dict[str, Any]):
@@ -58,6 +70,12 @@ class _SubmissionModel(_BaseModel):
             creation_date=data["creation_date"],
             submission_status=SubmissionStatus._from_repr(data["submission_status"]),
             version=data["version"],
+            is_completed=data["is_completed"],
+            is_abandoned=data["is_abandoned"],
+            is_canceled=data["is_canceled"],
+            running_jobs=_BaseModel._deserialize_attribute(data["running_jobs"]),
+            blocked_jobs=_BaseModel._deserialize_attribute(data["blocked_jobs"]),
+            pending_jobs=_BaseModel._deserialize_attribute(data["pending_jobs"]),
         )
 
     def to_list(self):
@@ -71,4 +89,10 @@ class _SubmissionModel(_BaseModel):
             self.creation_date,
             repr(self.submission_status),
             self.version,
+            self.is_completed,
+            self.is_abandoned,
+            self.is_canceled,
+            _BaseModel._serialize_attribute(self.running_jobs),
+            _BaseModel._serialize_attribute(self.blocked_jobs),
+            _BaseModel._serialize_attribute(self.pending_jobs),
         ]

+ 83 - 44
taipy/core/submission/submission.py

@@ -11,9 +11,8 @@
 
 import threading
 import uuid
-from collections.abc import MutableSet
 from datetime import datetime
-from typing import Any, Dict, List, Optional, Union
+from typing import Any, Dict, List, Optional, Set, Union
 
 from .._entity._entity import _Entity
 from .._entity._labeled import _Labeled
@@ -69,13 +68,13 @@ class Submission(_Entity, _Labeled):
         properties = properties or {}
         self._properties = _Properties(self, **properties.copy())
 
-        self.__abandoned = False
-        self.__completed = False
+        self._is_abandoned = False
+        self._is_completed = False
+        self._is_canceled = False
 
-        self.__is_canceled = False
-        self.__running_jobs: MutableSet[str] = set()
-        self.__blocked_jobs: MutableSet[str] = set()
-        self.__pending_jobs: MutableSet[str] = set()
+        self._running_jobs: Set = set()
+        self._blocked_jobs: Set = set()
+        self._pending_jobs: Set = set()
 
     @staticmethod
     def __new_id() -> str:
@@ -145,7 +144,7 @@ class Submission(_Entity, _Labeled):
 
     @property  # type: ignore
     @_self_reload(_MANAGER_NAME)
-    def submission_status(self):
+    def submission_status(self) -> SubmissionStatus:
         return self._submission_status
 
     @submission_status.setter  # type: ignore
@@ -153,6 +152,36 @@ class Submission(_Entity, _Labeled):
     def submission_status(self, submission_status):
         self._submission_status = submission_status
 
+    @property  # type: ignore
+    @_self_reload(_MANAGER_NAME)
+    def is_abandoned(self) -> bool:
+        return self._is_abandoned
+
+    @is_abandoned.setter  # type: ignore
+    @_self_setter(_MANAGER_NAME)
+    def is_abandoned(self, val):
+        self._is_abandoned = val
+
+    @property  # type: ignore
+    @_self_reload(_MANAGER_NAME)
+    def is_completed(self) -> bool:
+        return self._is_completed
+
+    @is_completed.setter  # type: ignore
+    @_self_setter(_MANAGER_NAME)
+    def is_completed(self, val):
+        self._is_completed = val
+
+    @property  # type: ignore
+    @_self_reload(_MANAGER_NAME)
+    def is_canceled(self) -> bool:
+        return self._is_canceled
+
+    @is_canceled.setter  # type: ignore
+    @_self_setter(_MANAGER_NAME)
+    def is_canceled(self, val):
+        self._is_canceled = val
+
     def __lt__(self, other):
         return self.creation_date.timestamp() < other.creation_date.timestamp()
 
@@ -166,52 +195,62 @@ class Submission(_Entity, _Labeled):
         return self.creation_date.timestamp() >= other.creation_date.timestamp()
 
     def _update_submission_status(self, job: Job):
-        if self._submission_status == SubmissionStatus.FAILED:
-            return
+        from ._submission_manager_factory import _SubmissionManagerFactory
 
-        job_status = job.status
+        submission_manager = _SubmissionManagerFactory._build_manager()
 
-        if job_status == Status.FAILED:
-            self.submission_status = SubmissionStatus.FAILED  # type: ignore
+        submission = submission_manager._get(self)
+
+        if submission._submission_status == SubmissionStatus.FAILED:
             return
 
         with self.lock:
+            job_status = job.status
+
+            if job_status == Status.FAILED:
+                submission._submission_status = SubmissionStatus.FAILED
+                _SubmissionManagerFactory._build_manager()._set(submission)
+                return
             if job_status == Status.CANCELED:
-                self.__is_canceled = True
+                submission._is_canceled = True
             elif job_status == Status.BLOCKED:
-                self.__blocked_jobs.add(job.id)
-                self.__pending_jobs.discard(job.id)
+                submission._blocked_jobs.add(job.id)
+                submission._pending_jobs.discard(job.id)
             elif job_status == Status.PENDING or job_status == Status.SUBMITTED:
-                self.__pending_jobs.add(job.id)
-                self.__blocked_jobs.discard(job.id)
+                submission._pending_jobs.add(job.id)
+                submission._blocked_jobs.discard(job.id)
             elif job_status == Status.RUNNING:
-                self.__running_jobs.add(job.id)
-                self.__pending_jobs.discard(job.id)
+                submission._running_jobs.add(job.id)
+                submission._pending_jobs.discard(job.id)
             elif job_status == Status.COMPLETED or job_status == Status.SKIPPED:
-                self.__completed = True
-                self.__blocked_jobs.discard(job.id)
-                self.__pending_jobs.discard(job.id)
-                self.__running_jobs.discard(job.id)
+                submission._is_completed = True  # type: ignore
+                submission._blocked_jobs.discard(job.id)
+                submission._pending_jobs.discard(job.id)
+                submission._running_jobs.discard(job.id)
             elif job_status == Status.ABANDONED:
-                self.__abandoned = True
-                self.__running_jobs.discard(job.id)
-                self.__blocked_jobs.discard(job.id)
-                self.__pending_jobs.discard(job.id)
-
-        if self.__is_canceled:
-            self.submission_status = SubmissionStatus.CANCELED  # type: ignore
-        elif self.__abandoned:
-            self.submission_status = SubmissionStatus.UNDEFINED  # type: ignore
-        elif self.__running_jobs:
-            self.submission_status = SubmissionStatus.RUNNING  # type: ignore
-        elif self.__pending_jobs:
-            self.submission_status = SubmissionStatus.PENDING  # type: ignore
-        elif self.__blocked_jobs:
-            self.submission_status = SubmissionStatus.BLOCKED  # type: ignore
-        elif self.__completed:
-            self.submission_status = SubmissionStatus.COMPLETED  # type: ignore
-        else:
-            self.submission_status = SubmissionStatus.UNDEFINED  # type: ignore
+                submission._is_abandoned = True  # type: ignore
+                submission._running_jobs.discard(job.id)
+                submission._blocked_jobs.discard(job.id)
+                submission._pending_jobs.discard(job.id)
+
+            submission_manager._set(submission)
+
+            # The submission_status is set later to make sure notification for updating
+            # the submission_status attribute is triggered
+            if submission._is_canceled:
+                submission.submission_status = SubmissionStatus.CANCELED  # type: ignore
+            elif submission._is_abandoned:
+                submission.submission_status = SubmissionStatus.UNDEFINED  # type: ignore
+            elif submission._running_jobs:
+                submission.submission_status = SubmissionStatus.RUNNING  # type: ignore
+            elif submission._pending_jobs:
+                submission.submission_status = SubmissionStatus.PENDING  # type: ignore
+            elif submission._blocked_jobs:
+                submission.submission_status = SubmissionStatus.BLOCKED  # type: ignore
+            elif submission._is_completed:
+                submission.submission_status = SubmissionStatus.COMPLETED  # type: ignore
+            else:
+                submission.submission_status = SubmissionStatus.UNDEFINED  # type: ignore
 
     def is_finished(self) -> bool:
         """Indicate if the submission is finished.

+ 11 - 2
taipy/core/taipy.py

@@ -241,8 +241,8 @@ def submit(
             in asynchronous mode.
         timeout (Union[float, int]): The optional maximum number of seconds to wait
             for the jobs to be finished before returning.
-        **properties (dict[str, any]): A keyworded variable length list of additional arguments
-            that will be stored within the `Submission^`.
+        **properties (dict[str, any]): A keyworded variable length list of user additional arguments
+            that will be stored within the `Submission^`. It can be accessed via `Submission.properties^`.
 
     Returns:
         The created `Submission^` containing the information about the submission.
@@ -738,6 +738,15 @@ def get_jobs() -> List[Job]:
     return _JobManagerFactory._build_manager()._get_all()
 
 
+def get_submissions() -> List[Submission]:
+    """Return all the existing submissions.
+
+    Returns:
+        The list of all submissions.
+    """
+    return _SubmissionManagerFactory._build_manager()._get_all()
+
+
 def delete_job(job: Job, force: Optional[bool] = False):
     """Delete a job.
 

+ 24 - 9
tests/core/_orchestrator/test_orchestrator__submit_task.py

@@ -16,6 +16,7 @@ import pytest
 
 from taipy.config import Config
 from taipy.core import taipy
+from taipy.core._orchestrator._orchestrator import _Orchestrator
 from taipy.core._orchestrator._orchestrator_factory import _OrchestratorFactory
 from taipy.core.submission._submission_manager_factory import _SubmissionManagerFactory
 from taipy.core.submission.submission_status import SubmissionStatus
@@ -62,11 +63,13 @@ def test_submit_task_development_mode():
     assert job.submit_entity_id == scenario.t1.id
     assert job.creation_date == submit_time
     assert job.stacktrace == []
-    assert len(job._subscribers) == 2  # submission._update_submission_status and orchestrator._on_status_change
+    assert len(job._subscribers) == 2  # _Orchestrator._update_submission_status and _Orchestrator._on_status_change
+    assert job._subscribers[0].__code__ == _Orchestrator._update_submission_status.__code__
+    assert job._subscribers[1].__code__ == _Orchestrator._on_status_change.__code__
 
     # submission is created and correct
     assert len(_SubmissionManagerFactory._build_manager()._get_all()) == 1
-    submission = _SubmissionManagerFactory._build_manager()._get(job.submit_id)
+    submission = _SubmissionManagerFactory._build_manager()._get(submission)
     assert submission.creation_date == submit_time
     assert submission.submission_status == SubmissionStatus.COMPLETED
     assert submission.jobs == [job]
@@ -100,12 +103,14 @@ def test_submit_task_development_mode_blocked_job():
     assert job.is_blocked()  # input data is not ready
     assert job.submit_entity_id == scenario.t2.id
     assert job.creation_date == submit_time
-    assert len(job._subscribers) == 2  # submission._update_submission_status and orchestrator._on_status_change
+    assert len(job._subscribers) == 2  # _Orchestrator._update_submission_status and _Orchestrator._on_status_change
+    assert job._subscribers[0].__code__ == _Orchestrator._update_submission_status.__code__
+    assert job._subscribers[1].__code__ == _Orchestrator._on_status_change.__code__
     assert job.stacktrace == []
 
     # submission is created and correct
     assert len(_SubmissionManagerFactory._build_manager()._get_all()) == 1
-    submission = _SubmissionManagerFactory._build_manager()._get(job.submit_id)
+    submission = _SubmissionManagerFactory._build_manager()._get(submission)
     assert submission.submission_status == SubmissionStatus.BLOCKED
     assert submission.creation_date == submit_time
     assert submission.jobs == [job]
@@ -144,12 +149,14 @@ def test_submit_task_standalone_mode():
     assert not job.force
     assert job.is_pending()
     assert job.submit_entity_id == sc.t1.id
-    assert len(job._subscribers) == 2  # submission._update_submission_status and orchestrator._on_status_change
+    assert len(job._subscribers) == 2  # _Orchestrator._update_submission_status and _Orchestrator._on_status_change
+    assert job._subscribers[0].__code__ == _Orchestrator._update_submission_status.__code__
+    assert job._subscribers[1].__code__ == _Orchestrator._on_status_change.__code__
     assert job.stacktrace == []
 
     # submission is created and correct
     assert len(_SubmissionManagerFactory._build_manager()._get_all()) == 1
-    submission = _SubmissionManagerFactory._build_manager()._get(job.submit_id)
+    submission = _SubmissionManagerFactory._build_manager()._get(submission)
     assert submission.creation_date == submit_time
     assert submission.submission_status == SubmissionStatus.PENDING
     assert submission.jobs == [job]
@@ -188,12 +195,14 @@ def test_submit_task_standalone_mode_blocked_job():
     assert not job.force
     assert job.is_blocked()  # input data is not ready
     assert job.stacktrace == []
-    assert len(job._subscribers) == 2  # submission._update_submission_status and orchestrator._on_status_change
+    assert len(job._subscribers) == 2  # _Orchestrator._update_submission_status and _Orchestrator._on_status_change
+    assert job._subscribers[0].__code__ == _Orchestrator._update_submission_status.__code__
+    assert job._subscribers[1].__code__ == _Orchestrator._on_status_change.__code__
     assert job.submit_entity_id == sc.t2.id
 
     # submission is created and correct
     assert len(_SubmissionManagerFactory._build_manager()._get_all()) == 1
-    submission = _SubmissionManagerFactory._build_manager()._get(job.submit_id)
+    submission = _SubmissionManagerFactory._build_manager()._get(submission)
     assert submission.creation_date == submit_time
     assert submission.submission_status == SubmissionStatus.BLOCKED
     assert submission.jobs == [job]
@@ -219,5 +228,11 @@ def test_submit_task_with_callbacks_and_force_and_wait():
         # job exists and is correct
         assert job.task == scenario.t1
         assert job.force
-        assert len(job._subscribers) == 3  # nothing, _update_submission_status, and _on_status_change
+        assert (
+            len(job._subscribers) == 3
+        )  # nothing, _Orchestrator._update_submission_status, and _Orchestrator._on_status_change
+        assert job._subscribers[0].__code__ == nothing.__code__
+        assert job._subscribers[1].__code__ == _Orchestrator._update_submission_status.__code__
+        assert job._subscribers[2].__code__ == _Orchestrator._on_status_change.__code__
+
         mck.assert_called_once_with(job, timeout=2)

+ 6 - 0
tests/core/conftest.py

@@ -56,6 +56,7 @@ from taipy.core.sequence._sequence_manager_factory import _SequenceManagerFactor
 from taipy.core.sequence.sequence import Sequence
 from taipy.core.sequence.sequence_id import SequenceId
 from taipy.core.submission._submission_manager_factory import _SubmissionManagerFactory
+from taipy.core.submission.submission import Submission
 from taipy.core.task._task_manager_factory import _TaskManagerFactory
 from taipy.core.task.task import Task
 
@@ -285,6 +286,11 @@ def job(task):
     return Job(JobId("job"), task, "foo", "bar", version="random_version_number")
 
 
+@pytest.fixture(scope="function")
+def submission(task):
+    return Submission(task.id, task._ID_PREFIX, task.config_id, properties={})
+
+
 @pytest.fixture(scope="function")
 def _version():
     return _Version(id="foo", config=Config._applied_config)

+ 62 - 90
tests/core/data/test_data_node.py

@@ -498,6 +498,57 @@ class TestDataNode:
         assert dn_1.validity_period == time_period_2
         assert dn_2.validity_period == time_period_2
 
+        dn_1.last_edit_date = new_datetime
+
+        assert len(dn_1.job_ids) == 1
+        assert len(dn_2.job_ids) == 1
+
+        with dn_1 as dn:
+            assert dn.config_id == "foo"
+            assert dn.owner_id is None
+            assert dn.scope == Scope.SCENARIO
+            assert dn.last_edit_date == new_datetime
+            assert dn.name == "def"
+            assert dn.edit_in_progress
+            assert dn.validity_period == time_period_2
+            assert len(dn.job_ids) == 1
+            assert dn._is_in_context
+
+            new_datetime_2 = new_datetime + timedelta(5)
+
+            dn.scope = Scope.CYCLE
+            dn.last_edit_date = new_datetime_2
+            dn.name = "abc"
+            dn.edit_in_progress = False
+            dn.validity_period = None
+
+            assert dn.config_id == "foo"
+            assert dn.owner_id is None
+            assert dn.scope == Scope.SCENARIO
+            assert dn.last_edit_date == new_datetime
+            assert dn.name == "def"
+            assert dn.edit_in_progress
+            assert dn.validity_period == time_period_2
+            assert len(dn.job_ids) == 1
+
+        assert dn_1.config_id == "foo"
+        assert dn_1.owner_id is None
+        assert dn_1.scope == Scope.CYCLE
+        assert dn_1.last_edit_date == new_datetime_2
+        assert dn_1.name == "abc"
+        assert not dn_1.edit_in_progress
+        assert dn_1.validity_period is None
+        assert not dn_1._is_in_context
+        assert len(dn_1.job_ids) == 1
+
+    def test_auto_set_and_reload_properties(self):
+        dn_1 = InMemoryDataNode("foo", scope=Scope.GLOBAL, properties={"name": "def"})
+
+        dm = _DataManager()
+        dm._set(dn_1)
+
+        dn_2 = dm._get(dn_1)
+
         # auto set & reload on properties attribute
         assert dn_1.properties == {"name": "def"}
         assert dn_2.properties == {"name": "def"}
@@ -519,100 +570,37 @@ class TestDataNode:
             "temp_key_1": "temp_value_1",
             "temp_key_2": "temp_value_2",
         }
-        assert dn_2.properties == {
-            "name": "def",
-            "qux": 5,
-            "temp_key_1": "temp_value_1",
-            "temp_key_2": "temp_value_2",
-        }
+        assert dn_2.properties == {"name": "def", "qux": 5, "temp_key_1": "temp_value_1", "temp_key_2": "temp_value_2"}
         dn_1.properties.pop("temp_key_1")
         assert "temp_key_1" not in dn_1.properties.keys()
         assert "temp_key_1" not in dn_1.properties.keys()
-        assert dn_1.properties == {
-            "name": "def",
-            "qux": 5,
-            "temp_key_2": "temp_value_2",
-        }
-        assert dn_2.properties == {
-            "name": "def",
-            "qux": 5,
-            "temp_key_2": "temp_value_2",
-        }
+        assert dn_1.properties == {"name": "def", "qux": 5, "temp_key_2": "temp_value_2"}
+        assert dn_2.properties == {"name": "def", "qux": 5, "temp_key_2": "temp_value_2"}
         dn_2.properties.pop("temp_key_2")
-        assert dn_1.properties == {
-            "qux": 5,
-            "name": "def",
-        }
-        assert dn_2.properties == {
-            "qux": 5,
-            "name": "def",
-        }
+        assert dn_1.properties == {"qux": 5, "name": "def"}
+        assert dn_2.properties == {"qux": 5, "name": "def"}
         assert "temp_key_2" not in dn_1.properties.keys()
         assert "temp_key_2" not in dn_2.properties.keys()
 
         dn_1.properties["temp_key_3"] = 0
-        assert dn_1.properties == {
-            "qux": 5,
-            "temp_key_3": 0,
-            "name": "def",
-        }
-        assert dn_2.properties == {
-            "qux": 5,
-            "temp_key_3": 0,
-            "name": "def",
-        }
+        assert dn_1.properties == {"qux": 5, "temp_key_3": 0, "name": "def"}
+        assert dn_2.properties == {"qux": 5, "temp_key_3": 0, "name": "def"}
         dn_1.properties.update({"temp_key_3": 1})
-        assert dn_1.properties == {
-            "qux": 5,
-            "temp_key_3": 1,
-            "name": "def",
-        }
-        assert dn_2.properties == {
-            "qux": 5,
-            "temp_key_3": 1,
-            "name": "def",
-        }
+        assert dn_1.properties == {"qux": 5, "temp_key_3": 1, "name": "def"}
+        assert dn_2.properties == {"qux": 5, "temp_key_3": 1, "name": "def"}
         dn_1.properties.update(dict())
-        assert dn_1.properties == {
-            "qux": 5,
-            "temp_key_3": 1,
-            "name": "def",
-        }
-        assert dn_2.properties == {
-            "qux": 5,
-            "temp_key_3": 1,
-            "name": "def",
-        }
+        assert dn_1.properties == {"qux": 5, "temp_key_3": 1, "name": "def"}
+        assert dn_2.properties == {"qux": 5, "temp_key_3": 1, "name": "def"}
         dn_1.properties["temp_key_4"] = 0
         dn_1.properties["temp_key_5"] = 0
 
-        dn_1.last_edit_date = new_datetime
-
-        assert len(dn_1.job_ids) == 1
-        assert len(dn_2.job_ids) == 1
-
         with dn_1 as dn:
-            assert dn.config_id == "foo"
-            assert dn.owner_id is None
-            assert dn.scope == Scope.SCENARIO
-            assert dn.last_edit_date == new_datetime
-            assert dn.name == "def"
-            assert dn.edit_in_progress
-            assert dn.validity_period == time_period_2
-            assert len(dn.job_ids) == 1
             assert dn._is_in_context
             assert dn.properties["qux"] == 5
             assert dn.properties["temp_key_3"] == 1
             assert dn.properties["temp_key_4"] == 0
             assert dn.properties["temp_key_5"] == 0
 
-            new_datetime_2 = new_datetime + timedelta(5)
-
-            dn.scope = Scope.CYCLE
-            dn.last_edit_date = new_datetime_2
-            dn.name = "abc"
-            dn.edit_in_progress = False
-            dn.validity_period = None
             dn.properties["qux"] = 9
             dn.properties.pop("temp_key_3")
             dn.properties.pop("temp_key_4")
@@ -621,28 +609,12 @@ class TestDataNode:
             dn.properties.pop("temp_key_5")
             dn.properties.update(dict())
 
-            assert dn.config_id == "foo"
-            assert dn.owner_id is None
-            assert dn.scope == Scope.SCENARIO
-            assert dn.last_edit_date == new_datetime
-            assert dn.name == "def"
-            assert dn.edit_in_progress
-            assert dn.validity_period == time_period_2
-            assert len(dn.job_ids) == 1
             assert dn.properties["qux"] == 5
             assert dn.properties["temp_key_3"] == 1
             assert dn.properties["temp_key_4"] == 0
             assert dn.properties["temp_key_5"] == 0
 
-        assert dn_1.config_id == "foo"
-        assert dn_1.owner_id is None
-        assert dn_1.scope == Scope.CYCLE
-        assert dn_1.last_edit_date == new_datetime_2
-        assert dn_1.name == "abc"
-        assert not dn_1.edit_in_progress
-        assert dn_1.validity_period is None
         assert not dn_1._is_in_context
-        assert len(dn_1.job_ids) == 1
         assert dn_1.properties["qux"] == 9
         assert "temp_key_3" not in dn_1.properties.keys()
         assert dn_1.properties["temp_key_4"] == 1

+ 1 - 0
tests/core/notification/test_events_published.py

@@ -172,6 +172,7 @@ def test_events_published_for_scenario_submission():
     # 1 submission creation event
     # 1 submission update event for jobs
     # 3 submission update events (for status: PENDING, RUNNING and COMPLETED)
+    # 1 submission update event for is_completed
     scenario.submit()
     snapshot = all_evts.capture()
     assert len(snapshot.collected_events) == 17

+ 69 - 49
tests/core/scenario/test_scenario.py

@@ -549,6 +549,75 @@ def test_auto_set_and_reload(cycle, current_datetime, task, data_node):
     assert len(scenario_1.tags) == 1
     assert len(scenario_2.tags) == 1
 
+    with scenario_1 as scenario:
+        assert scenario.config_id == "foo"
+        assert len(scenario.tasks) == 1
+        assert len(scenario.sequences) == 1
+        assert scenario.sequences["sequence_1"] == sequence_1
+        assert scenario.tasks[task.config_id] == task
+        assert len(scenario.additional_data_nodes) == 1
+        assert scenario.additional_data_nodes[additional_dn.config_id] == additional_dn
+        assert scenario.creation_date == new_datetime
+        assert scenario.cycle == cycle
+        assert scenario.is_primary
+        assert len(scenario.subscribers) == 0
+        assert len(scenario.tags) == 1
+        assert scenario._is_in_context
+        assert scenario.name == "baz"
+
+        new_datetime_2 = new_datetime + timedelta(5)
+        scenario._config_id = "foo"
+        scenario.tasks = set()
+        scenario.additional_data_nodes = set()
+        scenario.remove_sequences([sequence_1_name])
+        scenario.creation_date = new_datetime_2
+        scenario.cycle = None
+        scenario.is_primary = False
+        scenario.subscribers = [print]
+        scenario.tags = None
+        scenario.name = "qux"
+
+        assert scenario.config_id == "foo"
+        assert len(scenario.sequences) == 1
+        assert scenario.sequences[sequence_1_name] == sequence_1
+        assert len(scenario.tasks) == 1
+        assert scenario.tasks[task.config_id] == task
+        assert len(scenario.additional_data_nodes) == 1
+        assert scenario.additional_data_nodes[additional_dn.config_id] == additional_dn
+        assert scenario.creation_date == new_datetime
+        assert scenario.cycle == cycle
+        assert scenario.is_primary
+        assert len(scenario.subscribers) == 0
+        assert len(scenario.tags) == 1
+        assert scenario._is_in_context
+        assert scenario.name == "baz"
+
+    assert scenario_1.config_id == "foo"
+    assert len(scenario_1.sequences) == 0
+    assert len(scenario_1.tasks) == 0
+    assert len(scenario_1.additional_data_nodes) == 0
+    assert scenario_1.tasks == {}
+    assert scenario_1.additional_data_nodes == {}
+    assert scenario_1.creation_date == new_datetime_2
+    assert scenario_1.cycle is None
+    assert not scenario_1.is_primary
+    assert len(scenario_1.subscribers) == 1
+    assert len(scenario_1.tags) == 0
+    assert not scenario_1._is_in_context
+
+
+def test_auto_set_and_reload_properties():
+    scenario_1 = Scenario(
+        "foo",
+        set(),
+        {"name": "baz"},
+    )
+
+    scenario_manager = _ScenarioManagerFactory._build_manager()
+    scenario_manager._set(scenario_1)
+
+    scenario_2 = scenario_manager._get(scenario_1)
+
     # auto set & reload on properties attribute
     assert scenario_1.properties == {"name": "baz"}
     assert scenario_2.properties == {"name": "baz"}
@@ -608,36 +677,11 @@ def test_auto_set_and_reload(cycle, current_datetime, task, data_node):
     scenario_1.properties["temp_key_5"] = 0
 
     with scenario_1 as scenario:
-        assert scenario.config_id == "foo"
-        assert len(scenario.tasks) == 1
-        assert len(scenario.sequences) == 1
-        assert scenario.sequences["sequence_1"] == sequence_1
-        assert scenario.tasks[task.config_id] == task
-        assert len(scenario.additional_data_nodes) == 1
-        assert scenario.additional_data_nodes[additional_dn.config_id] == additional_dn
-        assert scenario.creation_date == new_datetime
-        assert scenario.cycle == cycle
-        assert scenario.is_primary
-        assert len(scenario.subscribers) == 0
-        assert len(scenario.tags) == 1
-        assert scenario._is_in_context
-        assert scenario.name == "baz"
         assert scenario.properties["qux"] == 5
         assert scenario.properties["temp_key_3"] == 1
         assert scenario.properties["temp_key_4"] == 0
         assert scenario.properties["temp_key_5"] == 0
 
-        new_datetime_2 = new_datetime + timedelta(5)
-        scenario._config_id = "foo"
-        scenario.tasks = set()
-        scenario.additional_data_nodes = set()
-        scenario.remove_sequences([sequence_1_name])
-        scenario.creation_date = new_datetime_2
-        scenario.cycle = None
-        scenario.is_primary = False
-        scenario.subscribers = [print]
-        scenario.tags = None
-        scenario.name = "qux"
         scenario.properties["qux"] = 9
         scenario.properties.pop("temp_key_3")
         scenario.properties.pop("temp_key_4")
@@ -646,36 +690,12 @@ def test_auto_set_and_reload(cycle, current_datetime, task, data_node):
         scenario.properties.pop("temp_key_5")
         scenario.properties.update(dict())
 
-        assert scenario.config_id == "foo"
-        assert len(scenario.sequences) == 1
-        assert scenario.sequences[sequence_1_name] == sequence_1
-        assert len(scenario.tasks) == 1
-        assert scenario.tasks[task.config_id] == task
-        assert len(scenario.additional_data_nodes) == 1
-        assert scenario.additional_data_nodes[additional_dn.config_id] == additional_dn
-        assert scenario.creation_date == new_datetime
-        assert scenario.cycle == cycle
-        assert scenario.is_primary
-        assert len(scenario.subscribers) == 0
-        assert len(scenario.tags) == 1
         assert scenario._is_in_context
-        assert scenario.name == "baz"
         assert scenario.properties["qux"] == 5
         assert scenario.properties["temp_key_3"] == 1
         assert scenario.properties["temp_key_4"] == 0
         assert scenario.properties["temp_key_5"] == 0
 
-    assert scenario_1.config_id == "foo"
-    assert len(scenario_1.sequences) == 0
-    assert len(scenario_1.tasks) == 0
-    assert len(scenario_1.additional_data_nodes) == 0
-    assert scenario_1.tasks == {}
-    assert scenario_1.additional_data_nodes == {}
-    assert scenario_1.creation_date == new_datetime_2
-    assert scenario_1.cycle is None
-    assert not scenario_1.is_primary
-    assert len(scenario_1.subscribers) == 1
-    assert len(scenario_1.tags) == 0
     assert not scenario_1._is_in_context
     assert scenario_1.properties["qux"] == 9
     assert "temp_key_3" not in scenario_1.properties.keys()

+ 26 - 10
tests/core/sequence/test_sequence.py

@@ -582,6 +582,32 @@ def test_auto_set_and_reload(task):
     assert len(sequence_1.subscribers) == 0
     assert len(sequence_2.subscribers) == 0
 
+    with sequence_1 as sequence:
+        assert len(sequence.tasks) == 1
+        assert sequence.tasks[task.config_id].id == task.id
+        assert len(sequence.subscribers) == 0
+        assert sequence._is_in_context
+
+        sequence.tasks = []
+        sequence.subscribers = [print]
+        assert len(sequence.tasks) == 1
+        assert sequence.tasks[task.config_id].id == task.id
+        assert len(sequence.subscribers) == 0
+        assert sequence._is_in_context
+
+    assert len(sequence_1.tasks) == 0
+    assert len(sequence_1.subscribers) == 1
+    assert not sequence_1._is_in_context
+
+
+def test_auto_set_and_reload_properties():
+    scenario = Scenario("scenario", [], {}, sequences={"foo": {}})
+
+    _ScenarioManager._set(scenario)
+
+    sequence_1 = scenario.sequences["foo"]
+    sequence_2 = _SequenceManager._get(sequence_1)
+
     # auto set & reload on properties attribute
     assert sequence_1.properties == {"name": "foo"}
     assert sequence_2.properties == {"name": "foo"}
@@ -638,17 +664,12 @@ def test_auto_set_and_reload(task):
     sequence_1.properties["temp_key_5"] = 0
 
     with sequence_1 as sequence:
-        assert len(sequence.tasks) == 1
-        assert sequence.tasks[task.config_id].id == task.id
-        assert len(sequence.subscribers) == 0
         assert sequence._is_in_context
         assert sequence.properties["qux"] == 5
         assert sequence.properties["temp_key_3"] == 1
         assert sequence.properties["temp_key_4"] == 0
         assert sequence.properties["temp_key_5"] == 0
 
-        sequence.tasks = []
-        sequence.subscribers = [print]
         sequence.properties["qux"] = 9
         sequence.properties.pop("temp_key_3")
         sequence.properties.pop("temp_key_4")
@@ -657,17 +678,12 @@ def test_auto_set_and_reload(task):
         sequence.properties.pop("temp_key_5")
         sequence.properties.update(dict())
 
-        assert len(sequence.tasks) == 1
-        assert sequence.tasks[task.config_id].id == task.id
-        assert len(sequence.subscribers) == 0
         assert sequence._is_in_context
         assert sequence.properties["qux"] == 5
         assert sequence.properties["temp_key_3"] == 1
         assert sequence.properties["temp_key_4"] == 0
         assert sequence.properties["temp_key_5"] == 0
 
-    assert len(sequence_1.tasks) == 0
-    assert len(sequence_1.subscribers) == 1
     assert not sequence_1._is_in_context
     assert sequence_1.properties["qux"] == 9
     assert "temp_key_3" not in sequence_1.properties.keys()

+ 61 - 28
tests/core/submission/test_submission.py

@@ -297,6 +297,36 @@ def test_auto_set_and_reload():
     assert submission_1.jobs == [job_2, job_1]
     assert submission_2.jobs == [job_2, job_1]
 
+    # auto set & reload on is_canceled attribute
+    assert not submission_1.is_canceled
+    assert not submission_2.is_canceled
+    submission_1.is_canceled = True
+    assert submission_1.is_canceled
+    assert submission_2.is_canceled
+    submission_2.is_canceled = False
+    assert not submission_1.is_canceled
+    assert not submission_2.is_canceled
+
+    # auto set & reload on is_completed attribute
+    assert not submission_1.is_completed
+    assert not submission_2.is_completed
+    submission_1.is_completed = True
+    assert submission_1.is_completed
+    assert submission_2.is_completed
+    submission_2.is_completed = False
+    assert not submission_1.is_completed
+    assert not submission_2.is_completed
+
+    # auto set & reload on is_abandoned attribute
+    assert not submission_1.is_abandoned
+    assert not submission_2.is_abandoned
+    submission_1.is_abandoned = True
+    assert submission_1.is_abandoned
+    assert submission_2.is_abandoned
+    submission_2.is_abandoned = False
+    assert not submission_1.is_abandoned
+    assert not submission_2.is_abandoned
+
     # auto set & reload on submission_status attribute
     assert submission_1.submission_status == SubmissionStatus.SUBMITTED
     assert submission_2.submission_status == SubmissionStatus.SUBMITTED
@@ -307,6 +337,31 @@ def test_auto_set_and_reload():
     assert submission_1.submission_status == SubmissionStatus.COMPLETED
     assert submission_2.submission_status == SubmissionStatus.COMPLETED
 
+    with submission_1 as submission:
+        assert submission.jobs == [job_2, job_1]
+        assert submission.submission_status == SubmissionStatus.COMPLETED
+
+        submission.jobs = [job_1]
+        submission.submission_status = SubmissionStatus.PENDING
+
+        assert submission.jobs == [job_2, job_1]
+        assert submission.submission_status == SubmissionStatus.COMPLETED
+
+    assert submission_1.jobs == [job_1]
+    assert submission_1.submission_status == SubmissionStatus.PENDING
+    assert submission_2.jobs == [job_1]
+    assert submission_2.submission_status == SubmissionStatus.PENDING
+
+
+def test_auto_set_and_reload_properties():
+    task = Task(config_id="name_1", properties={}, function=print, id=TaskId("task_1"))
+    submission_1 = Submission(task.id, task._ID_PREFIX, task.config_id, properties={})
+
+    _TaskManagerFactory._build_manager()._set(task)
+    _SubmissionManagerFactory._build_manager()._set(submission_1)
+
+    submission_2 = _SubmissionManagerFactory._build_manager()._get(submission_1)
+
     # auto set & reload on properties attribute
     assert submission_1.properties == {}
     assert submission_2.properties == {}
@@ -322,27 +377,13 @@ def test_auto_set_and_reload():
 
     submission_1.properties["temp_key_1"] = "temp_value_1"
     submission_1.properties["temp_key_2"] = "temp_value_2"
-    assert submission_1.properties == {
-        "qux": 5,
-        "temp_key_1": "temp_value_1",
-        "temp_key_2": "temp_value_2",
-    }
-    assert submission_2.properties == {
-        "qux": 5,
-        "temp_key_1": "temp_value_1",
-        "temp_key_2": "temp_value_2",
-    }
+    assert submission_1.properties == {"qux": 5, "temp_key_1": "temp_value_1", "temp_key_2": "temp_value_2"}
+    assert submission_2.properties == {"qux": 5, "temp_key_1": "temp_value_1", "temp_key_2": "temp_value_2"}
     submission_1.properties.pop("temp_key_1")
     assert "temp_key_1" not in submission_1.properties.keys()
     assert "temp_key_1" not in submission_1.properties.keys()
-    assert submission_1.properties == {
-        "qux": 5,
-        "temp_key_2": "temp_value_2",
-    }
-    assert submission_2.properties == {
-        "qux": 5,
-        "temp_key_2": "temp_value_2",
-    }
+    assert submission_1.properties == {"qux": 5, "temp_key_2": "temp_value_2"}
+    assert submission_2.properties == {"qux": 5, "temp_key_2": "temp_value_2"}
     submission_2.properties.pop("temp_key_2")
     assert submission_1.properties == {"qux": 5}
     assert submission_2.properties == {"qux": 5}
@@ -362,15 +403,11 @@ def test_auto_set_and_reload():
     submission_1.properties["temp_key_5"] = 0
 
     with submission_1 as submission:
-        assert submission.jobs == [job_2, job_1]
-        assert submission.submission_status == SubmissionStatus.COMPLETED
         assert submission.properties["qux"] == 5
         assert submission.properties["temp_key_3"] == 1
         assert submission.properties["temp_key_4"] == 0
         assert submission.properties["temp_key_5"] == 0
 
-        submission.jobs = [job_1]
-        submission.submission_status = SubmissionStatus.PENDING
         submission.properties["qux"] = 9
         submission.properties.pop("temp_key_3")
         submission.properties.pop("temp_key_4")
@@ -379,17 +416,11 @@ def test_auto_set_and_reload():
         submission.properties.pop("temp_key_5")
         submission.properties.update(dict())
 
-        assert submission.jobs == [job_2, job_1]
-        assert submission.submission_status == SubmissionStatus.COMPLETED
         assert submission.properties["qux"] == 5
         assert submission.properties["temp_key_3"] == 1
         assert submission.properties["temp_key_4"] == 0
         assert submission.properties["temp_key_5"] == 0
 
-    assert submission_1.jobs == [job_1]
-    assert submission_1.submission_status == SubmissionStatus.PENDING
-    assert submission_2.jobs == [job_1]
-    assert submission_2.submission_status == SubmissionStatus.PENDING
     assert submission_1.properties["qux"] == 9
     assert "temp_key_3" not in submission_1.properties.keys()
     assert submission_1.properties["temp_key_4"] == 1
@@ -426,6 +457,7 @@ def test_auto_set_and_reload():
 def test_update_submission_status_with_single_job_completed(job_statuses, expected_submission_statuses):
     job = MockJob("job_id", Status.SUBMITTED)
     submission = Submission("submission_id", "ENTITY_TYPE", "entity_config_id")
+    _SubmissionManagerFactory._build_manager()._set(submission)
 
     assert submission.submission_status == SubmissionStatus.SUBMITTED
 
@@ -438,6 +470,7 @@ def test_update_submission_status_with_single_job_completed(job_statuses, expect
 def __test_update_submission_status_with_two_jobs(job_ids, job_statuses, expected_submission_statuses):
     jobs = {job_id: MockJob(job_id, Status.SUBMITTED) for job_id in job_ids}
     submission = Submission("submission_id", "ENTITY_TYPE", "entity_config_id")
+    _SubmissionManagerFactory._build_manager()._set(submission)
 
     assert submission.submission_status == SubmissionStatus.SUBMITTED
 

+ 32 - 14
tests/core/task/test_task.py

@@ -196,6 +196,38 @@ def test_auto_set_and_reload(data_node):
     assert task_1.parent_ids == {"sc1"}
     assert task_2.parent_ids == {"sc1"}
 
+    with task_1 as task:
+        assert task.config_id == "foo"
+        assert task.owner_id is None
+        assert task.function == mock_func
+        assert not task.skippable
+        assert task._is_in_context
+
+        task.function = print
+        task.skippable = True
+
+        assert task.config_id == "foo"
+        assert task.owner_id is None
+        assert task.function == mock_func
+        assert not task.skippable
+        assert task._is_in_context
+
+    assert task_1.config_id == "foo"
+    assert task_1.owner_id is None
+    assert task_1.function == print
+    assert task.skippable
+    assert not task_1._is_in_context
+
+
+def test_auto_set_and_reload_properties():
+    task_1 = Task(
+        config_id="foo", properties={}, function=print, input=None, output=None, owner_id=None, skippable=False
+    )
+
+    _TaskManager._set(task_1)
+
+    task_2 = _TaskManager._get(task_1)
+
     # auto set & reload on properties attribute
     assert task_1.properties == {}
     assert task_2.properties == {}
@@ -251,18 +283,12 @@ def test_auto_set_and_reload(data_node):
     task_1.properties["temp_key_5"] = 0
 
     with task_1 as task:
-        assert task.config_id == "foo"
-        assert task.owner_id is None
-        assert task.function == mock_func
-        assert not task.skippable
         assert task._is_in_context
         assert task.properties["qux"] == 5
         assert task.properties["temp_key_3"] == 1
         assert task.properties["temp_key_4"] == 0
         assert task.properties["temp_key_5"] == 0
 
-        task.function = print
-        task.skippable = True
         task.properties["qux"] = 9
         task.properties.pop("temp_key_3")
         task.properties.pop("temp_key_4")
@@ -271,20 +297,12 @@ def test_auto_set_and_reload(data_node):
         task.properties.pop("temp_key_5")
         task.properties.update(dict())
 
-        assert task.config_id == "foo"
-        assert task.owner_id is None
-        assert task.function == mock_func
-        assert not task.skippable
         assert task._is_in_context
         assert task.properties["qux"] == 5
         assert task.properties["temp_key_3"] == 1
         assert task.properties["temp_key_4"] == 0
         assert task.properties["temp_key_5"] == 0
 
-    assert task_1.config_id == "foo"
-    assert task_1.owner_id is None
-    assert task_1.function == print
-    assert task.skippable
     assert not task_1._is_in_context
     assert task_1.properties["qux"] == 9
     assert "temp_key_3" not in task_1.properties.keys()

+ 20 - 6
tests/core/test_taipy.py

@@ -58,7 +58,7 @@ def cb(s, j):
 
 
 class TestTaipy:
-    def test_set(self, scenario, cycle, sequence, data_node, task):
+    def test_set(self, scenario, cycle, sequence, data_node, task, submission):
         with mock.patch("taipy.core.data._data_manager._DataManager._set") as mck:
             tp.set(data_node)
             mck.assert_called_once_with(data_node)
@@ -74,6 +74,9 @@ class TestTaipy:
         with mock.patch("taipy.core.cycle._cycle_manager._CycleManager._set") as mck:
             tp.set(cycle)
             mck.assert_called_once_with(cycle)
+        with mock.patch("taipy.core.submission._submission_manager._SubmissionManager._set") as mck:
+            tp.set(submission)
+            mck.assert_called_once_with(submission)
 
     def test_is_editable_is_called(self, cycle, job, data_node):
         with mock.patch("taipy.core.cycle._cycle_manager._CycleManager._is_editable") as mck:
@@ -569,16 +572,27 @@ class TestTaipy:
             tp.get_latest_job(task)
             mck.assert_called_once_with(task)
 
-    def test_get_latest_submission(self, task):
-        with mock.patch("taipy.core.submission._submission_manager._SubmissionManager._get_latest") as mck:
-            tp.get_latest_submission(task)
-            mck.assert_called_once_with(task)
-
     def test_cancel_job(self):
         with mock.patch("taipy.core.job._job_manager._JobManager._cancel") as mck:
             tp.cancel_job("job_id")
             mck.assert_called_once_with("job_id")
 
+    def test_get_submissions(self):
+        with mock.patch("taipy.core.submission._submission_manager._SubmissionManager._get_all") as mck:
+            tp.get_submissions()
+            mck.assert_called_once_with()
+
+    def test_get_submission(self, task):
+        with mock.patch("taipy.core.submission._submission_manager._SubmissionManager._get") as mck:
+            submission_id = SubmissionId("SUBMISSION_id")
+            tp.get(submission_id)
+            mck.assert_called_once_with(submission_id)
+
+    def test_get_latest_submission(self, task):
+        with mock.patch("taipy.core.submission._submission_manager._SubmissionManager._get_latest") as mck:
+            tp.get_latest_submission(task)
+            mck.assert_called_once_with(task)
+
     def test_block_config_when_core_is_running(self):
         Config.configure_job_executions(mode=JobConfig._STANDALONE_MODE)
         input_cfg_1 = Config.configure_data_node(id="i1", storage_type="pickle", scope=Scope.SCENARIO, default_data=1)