import pathlib  import shutil  from datetime import datetime  from typing import Any, Callable, Dict, List, Optional, Set, Union, overload    from taipy.config.common.scope import Scope  from taipy.logger._taipy_logger import _TaipyLogger    from ._entity._entity import _Entity  from ._version._version_manager_factory import _VersionManagerFactory  from .common._warnings import _warn_no_core_service  from .config.data_node_config import DataNodeConfig  from .config.scenario_config import ScenarioConfig  from .cycle._cycle_manager_factory import _CycleManagerFactory  from .cycle.cycle import Cycle  from .cycle.cycle_id import CycleId  from .data._data_manager_factory import _DataManagerFactory  from .data.data_node import DataNode  from .data.data_node_id import DataNodeId  from .exceptions.exceptions import (      DataNodeConfigIsNotGlobal,      ModelNotFound,      NonExistingVersion,      VersionIsNotProductionVersion,  )  from .job._job_manager_factory import _JobManagerFactory  from .job.job import Job  from .job.job_id import JobId  from .scenario._scenario_manager_factory import _ScenarioManagerFactory  from .scenario.scenario import Scenario  from .scenario.scenario_id import ScenarioId  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 .task._task_manager_factory import _TaskManagerFactory  from .task.task import Task  from .task.task_id import TaskId    __logger = _TaipyLogger._get_logger()      def set(entity: Union[DataNode, Task, Sequence, Scenario, Cycle]):      """Save or update an entity.        This function allows you to save or update an entity in Taipy.        Parameters:          entity (Union[DataNode^, Task^, Sequence^, Scenario^, Cycle^]): The              entity to save or update.      """      if isinstance(entity, Cycle):          return _CycleManagerFactory._build_manager()._set(entity)      if isinstance(entity, Scenario):          return _ScenarioManagerFactory._build_manager()._set(entity)      if isinstance(entity, Sequence):          return _SequenceManagerFactory._build_manager()._set(entity)      if isinstance(entity, Task):          return _TaskManagerFactory._build_manager()._set(entity)      if isinstance(entity, DataNode):          return _DataManagerFactory._build_manager()._set(entity)      def is_submittable(entity: Union[Scenario, ScenarioId, Sequence, SequenceId, Task, TaskId]) -> bool:      """Indicate if an entity can be submitted.        This function checks if the given entity can be submitted for execution.        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      return False      def is_editable(      entity: Union[          DataNode, Task, Job, Sequence, Scenario, Cycle, DataNodeId, TaskId, JobId, SequenceId, ScenarioId, CycleId      ]  ) -> bool:      """Indicate if an entity can be edited.        This function checks if the given entity can be edited.        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      return False      def is_readable(      entity: Union[          DataNode, Task, Job, Sequence, Scenario, Cycle, DataNodeId, TaskId, JobId, SequenceId, ScenarioId, CycleId      ]  ) -> bool:      """Indicate if an entity can be read.        This function checks if the given entity can be read.        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      return False      @_warn_no_core_service()  def submit(      entity: Union[Scenario, Sequence, Task],      force: bool = False,      wait: bool = False,      timeout: Optional[Union[float, int]] = None,  ) -> 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).        If the entity is a sequence or a scenario, all the tasks of the entity are      submitted for execution.        Parameters:          entity (Union[Scenario^, Sequence^, Task^]): The scenario, sequence or task to submit.          force (bool): If True, the execution is forced even if for skippable tasks.          wait (bool): Wait for the orchestrated jobs created from the submission to be finished              in asynchronous mode.          timeout (Union[float, int]): The optional maximum number of seconds to wait              for the jobs to be finished before returning.        Returns:          The created `Job^` or a collection of the created `Job^` depends on the submitted entity.                - If a `Scenario^` or a `Sequence^` is provided, it will return a list of `Job^`.              - If a `Task^` is provided, it will return the created `Job^`.      """      if isinstance(entity, Scenario):          return _ScenarioManagerFactory._build_manager()._submit(entity, force=force, wait=wait, timeout=timeout)      if isinstance(entity, Sequence):          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)      @overload  def exists(entity_id: TaskId) -> bool:      ...      @overload  def exists(entity_id: DataNodeId) -> bool:      ...      @overload  def exists(entity_id: SequenceId) -> bool:      ...      @overload  def exists(entity_id: ScenarioId) -> bool:      ...      @overload  def exists(entity_id: CycleId) -> bool:      ...      @overload  def exists(entity_id: JobId) -> bool:      ...      @overload  def exists(entity_id: str) -> bool:      ...      def exists(entity_id: Union[TaskId, DataNodeId, SequenceId, ScenarioId, JobId, CycleId, 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      representations.        Parameters:          entity_id (Union[DataNodeId^, TaskId^, SequenceId^, ScenarioId^, JobId^, CycleId^]): The              identifier of the entity to check for existence.        Returns:          True if the given entity exists. False otherwise.        Raises:          ModelNotFound: If the entity's type cannot be determined.        Note:          The function performs checks for various entity types          (`Job^`, `Cycle^`, `Scenario^`, `Sequence^`, `Task^`, `DataNode^`)          based on their respective identifier prefixes.      """      if entity_id.startswith(Job._ID_PREFIX):          return _JobManagerFactory._build_manager()._exists(JobId(entity_id))      if entity_id.startswith(Cycle._ID_PREFIX):          return _CycleManagerFactory._build_manager()._exists(CycleId(entity_id))      if entity_id.startswith(Scenario._ID_PREFIX):          return _ScenarioManagerFactory._build_manager()._exists(ScenarioId(entity_id))      if entity_id.startswith(Sequence._ID_PREFIX):          return _SequenceManagerFactory._build_manager()._exists(SequenceId(entity_id))      if entity_id.startswith(Task._ID_PREFIX):          return _TaskManagerFactory._build_manager()._exists(TaskId(entity_id))      if entity_id.startswith(DataNode._ID_PREFIX):          return _DataManagerFactory._build_manager()._exists(DataNodeId(entity_id))      raise ModelNotFound("NOT_DETERMINED", entity_id)      @overload  def get(entity_id: TaskId) -> Task:      ...      @overload  def get(entity_id: DataNodeId) -> DataNode:      ...      @overload  def get(entity_id: SequenceId) -> Sequence:      ...      @overload  def get(entity_id: ScenarioId) -> Scenario:      ...      @overload  def get(entity_id: CycleId) -> Cycle:      ...      @overload  def get(entity_id: JobId) -> Job:      ...      @overload  def get(entity_id: str) -> Union[Task, DataNode, Sequence, Scenario, Job, Cycle]:      ...      def get(      entity_id: Union[TaskId, DataNodeId, SequenceId, ScenarioId, JobId, CycleId, str]  ) -> Union[Task, DataNode, Sequence, Scenario, Job, Cycle]:      """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^.          Parameters:          entity_id (Union[TaskId, DataNodeId, SequenceId, ScenarioId, JobId, CycleId, str]):              The identifier of the entity to retrieve.<br/>              It should conform to the identifier pattern of one of the entities (`Task^`, `DataNode^`,              `Sequence^`, `Job^`, `Cycle^` or `Scenario^`).        Returns:          The entity that corresponds to the provided identifier. Returns None if no matching entity is found.        Raises:          ModelNotFound^: If the provided *entity_id* does not match any known entity pattern.      """      if entity_id.startswith(Job._ID_PREFIX):          return _JobManagerFactory._build_manager()._get(JobId(entity_id))      if entity_id.startswith(Cycle._ID_PREFIX):          return _CycleManagerFactory._build_manager()._get(CycleId(entity_id))      if entity_id.startswith(Scenario._ID_PREFIX):          return _ScenarioManagerFactory._build_manager()._get(ScenarioId(entity_id))      if entity_id.startswith(Sequence._ID_PREFIX):          return _SequenceManagerFactory._build_manager()._get(SequenceId(entity_id))      if entity_id.startswith(Task._ID_PREFIX):          return _TaskManagerFactory._build_manager()._get(TaskId(entity_id))      if entity_id.startswith(DataNode._ID_PREFIX):          return _DataManagerFactory._build_manager()._get(DataNodeId(entity_id))      raise ModelNotFound("NOT_DETERMINED", entity_id)      def get_tasks() -> List[Task]:      """Retrieve a list of all existing tasks.        This function returns a list of all tasks that currently exist.        Returns:          A list containing all the tasks.      """      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.        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.        Returns:          True if the given scenario or job 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      return True      def delete(entity_id: Union[TaskId, DataNodeId, SequenceId, ScenarioId, JobId, CycleId]):      """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 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.        Parameters:          entity_id (Union[TaskId, DataNodeId, SequenceId, ScenarioId, 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):          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 _CycleManagerFactory._build_manager()._hard_delete(CycleId(entity_id))      if entity_id.startswith(Scenario._ID_PREFIX):          return _ScenarioManagerFactory._build_manager()._hard_delete(ScenarioId(entity_id))      if entity_id.startswith(Sequence._ID_PREFIX):          return _SequenceManagerFactory._build_manager()._hard_delete(SequenceId(entity_id))      if entity_id.startswith(Task._ID_PREFIX):          return _TaskManagerFactory._build_manager()._hard_delete(TaskId(entity_id))      if entity_id.startswith(DataNode._ID_PREFIX):          return _DataManagerFactory._build_manager()._delete(DataNodeId(entity_id))      raise ModelNotFound("NOT_DETERMINED", entity_id)      def get_scenarios(cycle: Optional[Cycle] = None, tag: Optional[str] = None) -> List[Scenario]:      """Retrieve a list of existing scenarios filtered by cycle or tag.        This function allows you to retrieve a list of scenarios based on optional      filtering criteria. If both a _cycle_ and a _tag_ are provided, the returned      list contains scenarios that belong to the specified _cycle_ **and** also      have the specified _tag_.        Parameters:           cycle (Optional[Cycle^]): The optional `Cycle^` to filter scenarios by.           tag (Optional[str]): The optional tag to filter scenarios by.        Returns:          The list of scenarios filtered by cycle or tag. If no filtering criteria              are provided, this method returns all existing scenarios.      """      if not cycle and not tag:          return _ScenarioManagerFactory._build_manager()._get_all()      if cycle and not tag:          return _ScenarioManagerFactory._build_manager()._get_all_by_cycle(cycle)      if not cycle and tag:          return _ScenarioManagerFactory._build_manager()._get_all_by_tag(tag)      if cycle and tag:          cycles_scenarios = _ScenarioManagerFactory._build_manager()()._get_all_by_cycle(cycle)          return [scenario for scenario in cycles_scenarios if scenario.has_tag(tag)]      return []      def get_primary(cycle: Cycle) -> Optional[Scenario]:      """Retrieve the primary scenario associated with a cycle.        Parameters:          cycle (Cycle^): The cycle for which to retrieve the primary scenario.        Returns:          The primary scenario of the given _cycle_. If the cycle has no              primary scenario, this method returns None.      """      return _ScenarioManagerFactory._build_manager()._get_primary(cycle)      def get_primary_scenarios() -> List[Scenario]:      """Retrieve a list of all primary scenarios.        Returns:          A list containing all primary scenarios.      """      return _ScenarioManagerFactory._build_manager()._get_primary_scenarios()      def is_promotable(scenario: Union[Scenario, ScenarioId]) -> bool:      """Determine if a scenario can be promoted to become a primary scenario.        This function checks whether the given scenario is eligible to be promoted      as a primary scenario.        Parameters:          scenario (Union[Scenario, ScenarioId]): The scenario to be evaluated for promotability.        Returns:          True if the given scenario can be promoted to be a primary scenario. False otherwise.      """      return _ScenarioManagerFactory._build_manager()._is_promotable_to_primary(scenario)      def set_primary(scenario: Scenario):      """Promote a scenario as the primary scenario of its cycle.        This function promotes the given scenario as the primary scenario of its associated cycle.      If the cycle already has a primary scenario, that scenario is demoted and is      no longer considered the primary scenario for its cycle.        Parameters:          scenario (Scenario^): The scenario to promote as the new _primary_ scenario.      """      return _ScenarioManagerFactory._build_manager()._set_primary(scenario)      def tag(scenario: Scenario, tag: str):      """Add a tag to a scenario.        This function adds a user-defined tag to the specified scenario. If another scenario      within the same cycle already has the same tag applied, the previous scenario is untagged.        Parameters:          scenario (Scenario^): The scenario to which the tag will be added.          tag (str): The tag to apply to the scenario.      """      return _ScenarioManagerFactory._build_manager()._tag(scenario, tag)      def untag(scenario: Scenario, tag: str):      """Remove a tag from a scenario.        This function removes a specified tag from the given scenario. If the scenario does      not have the specified tag, it has no effect.        Parameters:          scenario (Scenario^): The scenario from which the tag will be removed.          tag (str): The tag to remove from the scenario.      """      return _ScenarioManagerFactory._build_manager()._untag(scenario, tag)      def compare_scenarios(*scenarios: Scenario, data_node_config_id: Optional[str] = None) -> Dict[str, Any]:      """Compare the data nodes of several scenarios.        You can specify which data node config identifier should the comparison be performed      on.        Parameters:          *scenarios (*Scenario^): The list of the scenarios to compare.          data_node_config_id (Optional[str]): The config identifier of the DataNode to perform              the comparison on. <br/>                If _data_node_config_id_ is not provided, the scenarios are              compared on all defined comparators.<br/>        Returns:          The comparison results. The key is the data node config identifier used for          comparison.        Raises:          InsufficientScenarioToCompare^: Raised when only one or no scenario for comparison              is provided.          NonExistingComparator^: Raised when the scenario comparator does not exist.          DifferentScenarioConfigs^: Raised when the provided scenarios do not share the              same scenario config.          NonExistingScenarioConfig^: Raised when the scenario config of the provided              scenarios could not be found.      """      return _ScenarioManagerFactory._build_manager()._compare(*scenarios, data_node_config_id=data_node_config_id)      def subscribe_scenario(      callback: Callable[[Scenario, Job], None],      params: Optional[List[Any]] = None,      scenario: Optional[Scenario] = None,  ):      """Subscribe a function to be called on job status change.        The subscription is applied to all jobs created for the execution of _scenario_.      If no scenario is provided, the subscription applies to all scenarios.        Parameters:          callback (Callable[[Scenario^, Job^], None]): The function to be called on              status change.          params (Optional[List[Any]]): The parameters to be passed to the _callback_.          scenario (Optional[Scenario^]): The scenario to which the callback is applied.              If None, the subscription is registered for all scenarios.        Note:          Notifications are applied only for jobs created **after** this subscription.      """      params = [] if params is None else params      return _ScenarioManagerFactory._build_manager()._subscribe(callback, params, scenario)      def unsubscribe_scenario(      callback: Callable[[Scenario, Job], None], params: Optional[List[Any]] = None, scenario: Optional[Scenario] = None  ):      """Unsubscribe a function that is called when the status of a `Job^` changes.        If no scenario is provided, the subscription is removed for all scenarios.        Parameters:          callback (Callable[[Scenario^, Job^], None]): The function to unsubscribe from.          params (Optional[List[Any]]): The parameters to be passed to the callback.          scenario (Optional[Scenario]): The scenario to unsubscribe from. If None, it              applies to all scenarios.        Note:          The callback function will continue to be called for ongoing jobs.      """      return _ScenarioManagerFactory._build_manager()._unsubscribe(callback, params, scenario)      def subscribe_sequence(      callback: Callable[[Sequence, Job], None], params: Optional[List[Any]] = None, sequence: Optional[Sequence] = None  ):      """Subscribe a function to be called on job status change.        The subscription is applied to all jobs created for the execution of _sequence_.        Parameters:          callback (Callable[[Sequence^, Job^], None]): The callable function to be called on              status change.          params (Optional[List[Any]]): The parameters to be passed to the _callback_.          sequence (Optional[Sequence^]): The sequence to subscribe on. If None, the subscription              is applied to all sequences.        Note:          Notifications are applied only for jobs created **after** this subscription.      """      return _SequenceManagerFactory._build_manager()._subscribe(callback, params, sequence)      def unsubscribe_sequence(      callback: Callable[[Sequence, Job], None], params: Optional[List[Any]] = None, sequence: Optional[Sequence] = None  ):      """Unsubscribe a function that is called when the status of a Job changes.        Parameters:          callback (Callable[[Sequence^, Job^], None]): The callable function to be called on              status change.          params (Optional[List[Any]]): The parameters to be passed to the _callback_.          sequence (Optional[Sequence^]): The sequence to unsubscribe to. If None, it applies              to all sequences.        Note:          The function will continue to be called for ongoing jobs.      """      return _SequenceManagerFactory._build_manager()._unsubscribe(callback, params, sequence)      def get_sequences() -> List[Sequence]:      """Return all existing sequences.        Returns:          The list of all sequences.      """      return _SequenceManagerFactory._build_manager()._get_all()      def get_jobs() -> List[Job]:      """Return all the existing jobs.        Returns:          The list of all jobs.      """      return _JobManagerFactory._build_manager()._get_all()      def delete_job(job: Job, force=False):      """Delete a job.        This function deletes the specified job. If the job is not completed and      *force* is not set to True, a `JobNotDeletedException^` may be raised.        Parameters:          job (Job^): The job to delete.          force (Optional[bool]): If True, forces the deletion of _job_, even              if it is not completed yet.      Raises:          JobNotDeletedException^: If the job is not finished.      """      return _JobManagerFactory._build_manager()._delete(job, force)      def delete_jobs():      """Delete all jobs."""      return _JobManagerFactory._build_manager()._delete_all()      def cancel_job(job: Union[str, Job]):      """Cancel a job and set the status of the subsequent jobs to ABANDONED.        This function cancels the specified job and sets the status of any subsequent jobs to ABANDONED.        Parameters:          job (Job^): The job to cancel.      """      _JobManagerFactory._build_manager()._cancel(job)      def get_latest_job(task: Task) -> Optional[Job]:      """Return the latest job of a task.        This function retrieves the latest job associated with a task.        Parameters:          task (Task^): The task to retrieve the latest job from.      Returns:          The latest job created from _task_, or None if no job has been created from _task_.      """      return _JobManagerFactory._build_manager()._get_latest(task)      def get_latest_submission(entity: Union[Scenario, Sequence, Task]) -> Optional[Submission]:      """Return the latest submission of a scenario, sequence or task.        This function retrieves the latest submission associated with a scenario, sequence or task.        Parameters:          entity (Union[Scenario^, Sequence^, Task^]): The scenario, sequence or task to          retrieve the latest submission from.      Returns:          The latest submission created from _scenario_, _sequence_ and _task_, or None          if no submission has been created from _scenario_, _sequence_ and _task_.      """      return _SubmissionManagerFactory._build_manager()._get_latest(entity)      def get_data_nodes() -> List[DataNode]:      """Return all the existing data nodes.        Returns:          The list of all data nodes.      """      return _DataManagerFactory._build_manager()._get_all()      def get_cycles() -> List[Cycle]:      """Return the list of all existing cycles.        Returns:          The list of all cycles.      """      return _CycleManagerFactory._build_manager()._get_all()      def create_scenario(      config: ScenarioConfig,      creation_date: Optional[datetime] = None,      name: Optional[str] = None,  ) -> Scenario:      """Create and return a new scenario based on a scenario configuration.        If the scenario belongs to a cycle, a cycle (corresponding to the _creation_date_      and the configuration frequency attribute) is created if it does not exist yet.        Parameters:          config (ScenarioConfig^): The scenario configuration used to create a new scenario.          creation_date (Optional[datetime.datetime]): The creation date of the scenario.              If None, the current date time is used.          name (Optional[str]): The displayable name of the scenario.      Returns:          The new scenario.      """      return _ScenarioManagerFactory._build_manager()._create(config, creation_date, name)      def create_global_data_node(config: DataNodeConfig) -> DataNode:      """Create and return a new GLOBAL data node from a data node configuration.        Parameters:          config (DataNodeConfig^): The data node configuration. It must have a `GLOBAL` scope.        Returns:          The new global data node.        Raises:          DataNodeConfigIsNotGlobal^: If the data node configuration does not have GLOBAL scope.      """      # Check if the data node config has GLOBAL scope      if config.scope is not Scope.GLOBAL:          raise DataNodeConfigIsNotGlobal(config.id)  # type: ignore        if dns := _DataManagerFactory._build_manager()._get_by_config_id(config.id):  # type: ignore          return dns[0]      return _DataManagerFactory._build_manager()._create_and_set(config, None, None)      def clean_all_entities_by_version(version_number=None) -> bool:      """Delete all entities of a specific version.        This function deletes all entities associated with the specified version.        Parameters:          version_number (optional[str]): The version number of the entities to be deleted.              If None, the default behavior may apply.        Returns:          True if the operation succeeded, False otherwise.        Notes:          - If the specified version does not exist, the operation will be aborted, and False will be returned.          - This function cleans all entities, including jobs, scenarios, sequences, tasks, and data nodes.          - The production version of the specified version is also deleted if it exists.      """      version_manager = _VersionManagerFactory._build_manager()      try:          version_number = version_manager._replace_version_number(version_number)      except NonExistingVersion as e:          __logger.warning(f"{e.message} Abort cleaning the entities of version '{version_number}'.")          return False        _JobManagerFactory._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)      _DataManagerFactory._build_manager()._delete_by_version(version_number)        version_manager._delete(version_number)      try:          version_manager._delete_production_version(version_number)      except VersionIsNotProductionVersion:          pass        return True      def export_scenario(      scenario_id: ScenarioId,      folder_path: Union[str, pathlib.Path],  ):      """Export all related entities of a scenario to a folder.        This function exports all related entities of the specified scenario to the      specified folder.        Parameters:          scenario_id (ScenarioId): The ID of the scenario to export.          folder_path (Union[str, pathlib.Path]): The folder path to export the scenario to.      """        manager = _ScenarioManagerFactory._build_manager()      scenario = manager._get(scenario_id)      entity_ids = manager._get_children_entity_ids(scenario)  # type: ignore      entity_ids.scenario_ids = {scenario_id}      entity_ids.cycle_ids = {scenario.cycle.id}        shutil.rmtree(folder_path, ignore_errors=True)        for data_node_id in entity_ids.data_node_ids:          _DataManagerFactory._build_manager()._export(data_node_id, folder_path)      for task_id in entity_ids.task_ids:          _TaskManagerFactory._build_manager()._export(task_id, folder_path)      for sequence_id in entity_ids.sequence_ids:          _SequenceManagerFactory._build_manager()._export(sequence_id, folder_path)      for cycle_id in entity_ids.cycle_ids:          _CycleManagerFactory._build_manager()._export(cycle_id, folder_path)      for scenario_id in entity_ids.scenario_ids:          _ScenarioManagerFactory._build_manager()._export(scenario_id, folder_path)      for job_id in entity_ids.job_ids:          _JobManagerFactory._build_manager()._export(job_id, folder_path)      def get_parents(      entity: Union[TaskId, DataNodeId, SequenceId, Task, DataNode, Sequence], parent_dict=None  ) -> Dict[str, Set[_Entity]]:      """Get the parents of an entity from itself or its identifier.        Parameters:          entity (Union[TaskId, DataNodeId, SequenceId, Task, DataNode, Sequence]): The entity or its              identifier to get the parents.        Returns:          The dictionary of all parent entities.              They are grouped by their type (Scenario^, Sequences^, or tasks^) so each key corresponds              to a level of the parents and the value is a set of the parent entities.              An empty dictionary is returned if the entity does not have parents.<br/>              Example: The following instruction returns all the scenarios that include the              datanode identified by "my_datanode_id".              `taipy.get_parents("id_of_my_datanode")["scenario"]`        Raises:          ModelNotFound^: If _entity_ does not match a correct entity pattern.      """        def update_parent_dict(parents_set, parent_dict):          for k, value in parents_set.items():              if k in parent_dict.keys():                  parent_dict[k].update(value)              else:                  parent_dict[k] = value        if isinstance(entity, str):          entity = get(entity)  # type: ignore        parent_dict = parent_dict or dict()        if isinstance(entity, (Scenario, Cycle)):          return parent_dict        current_parent_dict: Dict[str, Set] = {}      for parent in entity.parent_ids:          parent_entity = get(parent)          if parent_entity._MANAGER_NAME in current_parent_dict.keys():              current_parent_dict[parent_entity._MANAGER_NAME].add(parent_entity)          else:              current_parent_dict[parent_entity._MANAGER_NAME] = {parent_entity}        if isinstance(entity, Sequence):          update_parent_dict(current_parent_dict, parent_dict)        if isinstance(entity, Task):          parent_entity_key_to_search_next = "scenario"          update_parent_dict(current_parent_dict, parent_dict)          for parent in parent_dict.get(parent_entity_key_to_search_next, []):              get_parents(parent, parent_dict)        if isinstance(entity, DataNode):          parent_entity_key_to_search_next = "task"          update_parent_dict(current_parent_dict, parent_dict)          for parent in parent_dict.get(parent_entity_key_to_search_next, []):              get_parents(parent, parent_dict)        return parent_dict      def get_cycles_scenarios() -> Dict[Optional[Cycle], List[Scenario]]:      """Get the scenarios grouped by cycles.        Returns:          The dictionary of all cycles and their corresponding scenarios.      """        cycles_scenarios: Dict[Optional[Cycle], List[Scenario]] = {}      for scenario in get_scenarios():          if scenario.cycle in cycles_scenarios.keys():              cycles_scenarios[scenario.cycle].append(scenario)          else:              cycles_scenarios[scenario.cycle] = [scenario]      return cycles_scenarios      def get_entities_by_config_id(      config_id: str,  ) -> Union[List, List[Task], List[DataNode], List[Sequence], List[Scenario]]:      """Get the entities by its config id.        Parameters:          config_id (str): The config id of the entities        Returns:          The list of all entities by the config id.      """        entities: List = []        if entities := _ScenarioManagerFactory._build_manager()._get_by_config_id(config_id):          return entities      if entities := _TaskManagerFactory._build_manager()._get_by_config_id(config_id):          return entities      if entities := _DataManagerFactory._build_manager()._get_by_config_id(config_id):          return entities      return entities


from multiprocessing import Lock  from typing import Optional    from taipy.config import Config  from taipy.logger._taipy_logger import _TaipyLogger    from ._backup._backup import _init_backup_file_with_storage_folder  from ._core_cli import _CoreCLI  from ._orchestrator._dispatcher._job_dispatcher import _JobDispatcher  from ._orchestrator._orchestrator import _Orchestrator  from ._orchestrator._orchestrator_factory import _OrchestratorFactory  from ._version._version_manager_factory import _VersionManagerFactory  from .config import CoreSection  from .exceptions.exceptions import CoreServiceIsAlreadyRunning      class Core:      """      Core service      """        _is_running = False      __lock_is_running = Lock()        __logger = _TaipyLogger._get_logger()        _orchestrator: Optional[_Orchestrator] = None      _dispatcher: Optional[_JobDispatcher] = None        def __init__(self):          """          Initialize a Core service.          """          pass        def run(self, force_restart=False):          """          Start a Core service.            This function checks the configuration, manages application's version,          and starts a dispatcher and lock the Config.          """          if self.__class__._is_running:              raise CoreServiceIsAlreadyRunning            with self.__class__.__lock_is_running:              self.__class__._is_running = True            self.__update_core_section()          self.__manage_version()          self.__check_and_block_config()            if self._orchestrator is None:              self._orchestrator = _OrchestratorFactory._build_orchestrator()            self.__start_dispatcher(force_restart)        def stop(self):          """          Stop the Core service.            This function stops the dispatcher and unblock the Config for update.          """          Config.unblock_update()            if self._dispatcher:              self._dispatcher = _OrchestratorFactory._remove_dispatcher()              self.__logger.info("Core service has been stopped.")            with self.__class__.__lock_is_running:              self.__class__._is_running = False        @staticmethod      def __update_core_section():          _CoreCLI.create_parser()          Config._applied_config._unique_sections[CoreSection.name]._update(_CoreCLI.parse_arguments())        @staticmethod      def __manage_version():          _VersionManagerFactory._build_manager()._manage_version()          Config._applied_config._unique_sections[CoreSection.name]._update(              {"version_number": _VersionManagerFactory._build_manager()._get_latest_version()}          )        @staticmethod      def __check_and_block_config():          Config.check()          Config.block_update()          _init_backup_file_with_storage_folder()        def __start_dispatcher(self, force_restart):          if dispatcher := _OrchestratorFactory._build_dispatcher(force_restart=force_restart):              self._dispatcher = dispatcher            if Config.job_config.is_development:              _Orchestrator._check_and_execute_jobs_if_development_mode()


#!/usr/bin/env python    """The setup script."""  import json  import os    from setuptools import find_namespace_packages, find_packages, setup    with open("README.md") as readme_file:      readme = readme_file.read()    with open(f"src{os.sep}taipy{os.sep}core{os.sep}version.json") as version_file:      version = json.load(version_file)      version_string = f'{version.get("major", 0)}.{version.get("minor", 0)}.{version.get("patch", 0)}'      if vext := version.get("ext"):          version_string = f"{version_string}.{vext}"    requirements = [      "pyarrow>=10.0.1,<11.0",      "networkx>=2.6,<3.0",      "openpyxl>=3.1.2,<3.2",      "modin[dask]>=0.23.0,<1.0",      "pymongo[srv]>=4.2.0,<5.0",      "sqlalchemy>=2.0.16,<2.1",      "toml>=0.10,<0.11",      "taipy-config@git+https://git@github.com/Avaiga/taipy-config.git@develop",  ]    test_requirements = ["pytest>=3.8"]    extras_require = {      "fastparquet": ["fastparquet==2022.11.0"],      "mssql": ["pyodbc>=4,<4.1"],      "mysql": ["pymysql>1,<1.1"],      "postgresql": ["psycopg2>2.9,<2.10"],  }    setup(      author="Avaiga",      author_email="dev@taipy.io",      python_requires=">=3.8",      classifiers=[          "Intended Audience :: Developers",          "License :: OSI Approved :: Apache Software License",          "Natural Language :: English",          "Programming Language :: Python :: 3",          "Programming Language :: Python :: 3.8",          "Programming Language :: Python :: 3.9",          "Programming Language :: Python :: 3.10",          "Programming Language :: Python :: 3.11",      ],      description="A Python library to build powerful and customized data-driven back-end applications.",      install_requires=requirements,      long_description=readme,      long_description_content_type="text/markdown",      license="Apache License 2.0",      keywords="taipy-core",      name="taipy-core",      package_dir={"": "src"},      packages=find_namespace_packages(where="src") + find_packages(include=["taipy", "taipy.core", "taipy.core.*"]),      include_package_data=True,      test_suite="tests",      tests_require=test_requirements,      url="https://github.com/avaiga/taipy-core",      version=version_string,      zip_safe=False,      extras_require=extras_require,  )


from typing import Dict    from taipy._cli._base_cli import _CLI    from .config import CoreSection      class _CoreCLI:      """Command-line interface for Taipy Core application."""        __MODE_ARGS: Dict[str, Dict] = {          "--development": {              "action": "store_true",              "dest": "taipy_development",              "help": """                  When execute Taipy application in `development` mode, all entities from the previous development version                  will be deleted before running new Taipy application.              """,          },          "--experiment": {              "dest": "taipy_experiment",              "nargs": "?",              "const": "",              "metavar": "VERSION",              "help": """                  When execute Taipy application in `experiment` mode, the current Taipy application is saved to a new                  version. If version name already exists, check for compatibility with current Python Config and run the                  application. Without being specified, the version number will be a random string.              """,          },          "--production": {              "dest": "taipy_production",              "nargs": "?",              "const": "",              "metavar": "VERSION",              "help": """                  When execute in `production` mode, the current version is used in production. All production versions                  should have the same configuration and share all entities. Without being specified, the latest version                  is used.              """,          },      }        __FORCE_ARGS: Dict[str, Dict] = {          "--force": {              "dest": "taipy_force",              "action": "store_true",              "help": """                  Force override the configuration of the version if existed and run the application. Default to False.              """,          },          "--no-force": {              "dest": "no_taipy_force",              "action": "store_true",              "help": "Stop the application if any Config conflict exists.",          },      }        @classmethod      def create_parser(cls):          core_parser = _CLI._add_groupparser("Taipy Core", "Optional arguments for Taipy Core service")            mode_group = core_parser.add_mutually_exclusive_group()          for mode_arg, mode_arg_dict in cls.__MODE_ARGS.items():              mode_group.add_argument(mode_arg, cls.__add_taipy_prefix(mode_arg), **mode_arg_dict)            force_group = core_parser.add_mutually_exclusive_group()          for force_arg, force_arg_dict in cls.__FORCE_ARGS.items():              force_group.add_argument(cls.__add_taipy_prefix(force_arg), **force_arg_dict)        @classmethod      def create_run_parser(cls):          run_parser = _CLI._add_subparser("run", help="Run a Taipy application.")          mode_group = run_parser.add_mutually_exclusive_group()          for mode_arg, mode_arg_dict in cls.__MODE_ARGS.items():              mode_group.add_argument(mode_arg, **mode_arg_dict)            force_group = run_parser.add_mutually_exclusive_group()          for force_arg, force_arg_dict in cls.__FORCE_ARGS.items():              force_group.add_argument(force_arg, **force_arg_dict)        @classmethod      def parse_arguments(cls):          args = _CLI._parse()          as_dict = {}          if args.taipy_development:              as_dict[CoreSection._MODE_KEY] = CoreSection._DEVELOPMENT_MODE          elif args.taipy_experiment is not None:              as_dict[CoreSection._MODE_KEY] = CoreSection._EXPERIMENT_MODE              as_dict[CoreSection._VERSION_NUMBER_KEY] = args.taipy_experiment          elif args.taipy_production is not None:              as_dict[CoreSection._MODE_KEY] = CoreSection._PRODUCTION_MODE              as_dict[CoreSection._VERSION_NUMBER_KEY] = args.taipy_production            if args.taipy_force:              as_dict[CoreSection._FORCE_KEY] = True          elif args.no_taipy_force:              as_dict[CoreSection._FORCE_KEY] = False            return as_dict        @classmethod      def __add_taipy_prefix(cls, key: str):          if key.startswith("--no-"):              return key[:5] + "taipy-" + key[5:]            return key[:2] + "taipy-" + key[2:]


import copy  import json  import pathlib  import shutil  from typing import Any, Dict, Iterable, Iterator, List, Optional, Type, Union    from taipy.config.config import Config    from ..common._utils import _retry_read_entity  from ..common.typing import Converter, Entity, Json, ModelType  from ..exceptions import FileCannotBeRead, InvalidExportPath, ModelNotFound  from ._abstract_repository import _AbstractRepository  from ._decoder import _Decoder  from ._encoder import _Encoder      class _FileSystemRepository(_AbstractRepository[ModelType, Entity]):      """      Holds common methods to be used and extended when the need for saving      dataclasses as JSON files in local storage emerges.        Some lines have type: ignore because MyPy won't recognize some generic attributes. This      should be revised in the future.        Attributes:          model_type (ModelType): Generic dataclass.          converter: A class that handles conversion to and from a database backend          dir_name (str): Folder that will hold the files for this dataclass model.      """        __EXCEPTIONS_TO_RETRY = (FileCannotBeRead,)        def __init__(self, model_type: Type[ModelType], converter: Type[Converter], dir_name: str):          self.model_type = model_type          self.converter = converter          self._dir_name = dir_name        @property      def dir_path(self):          return self._storage_folder / self._dir_name        @property      def _storage_folder(self) -> pathlib.Path:          return pathlib.Path(Config.core.storage_folder)        ###############################      # ##   Inherited methods   ## #      ###############################        def _save(self, entity: Entity):          self.__create_directory_if_not_exists()          model = self.converter._entity_to_model(entity)  # type: ignore          self.__get_path(model.id).write_text(              json.dumps(model.to_dict(), ensure_ascii=False, indent=0, cls=_Encoder, check_circular=False),              encoding="UTF-8",          )        def _exists(self, entity_id: str) -> bool:          return self.__get_path(entity_id).exists()        def _load(self, entity_id: str) -> Entity:          path = pathlib.Path(self.__get_path(entity_id))            try:              file_content = self.__read_file(path)          except (FileNotFoundError, FileCannotBeRead):              raise ModelNotFound(str(self.dir_path), entity_id)            return self.__file_content_to_entity(file_content)        def _load_all(self, filters: Optional[List[Dict]] = None) -> List[Entity]:          entities = []          try:              for f in self.dir_path.iterdir():                  if data := self.__filter_by(f, filters):                      entities.append(self.__file_content_to_entity(data))          except FileNotFoundError:              pass          return entities        def _delete(self, entity_id: str):          try:              self.__get_path(entity_id).unlink()          except FileNotFoundError:              raise ModelNotFound(str(self.dir_path), entity_id)        def _delete_all(self):          shutil.rmtree(self.dir_path, ignore_errors=True)        def _delete_many(self, ids: Iterable[str]):          for model_id in ids:              self._delete(model_id)        def _delete_by(self, attribute: str, value: str):          filters: List[Dict] = [{}]          for fil in filters:              fil.update({attribute: value})            try:              for f in self.dir_path.iterdir():                  if self.__filter_by(f, filters):                      f.unlink()          except FileNotFoundError:              pass        def _search(self, attribute: str, value: Any, filters: Optional[List[Dict]] = None) -> List[Entity]:          return list(self.__search(attribute, value, filters))        def _export(self, entity_id: str, folder_path: Union[str, pathlib.Path]):          if isinstance(folder_path, str):              folder: pathlib.Path = pathlib.Path(folder_path)          else:              folder = folder_path            if folder.resolve() == self._storage_folder.resolve():              raise InvalidExportPath("The export folder must not be the storage folder.")            export_dir = folder / self._dir_name          if not export_dir.exists():              export_dir.mkdir(parents=True)            export_path = export_dir / f"{entity_id}.json"          # Delete if exists.          if export_path.exists():              export_path.unlink()            shutil.copy2(self.__get_path(entity_id), export_path)        ###########################################      # ##   Specific or optimized methods   ## #      ###########################################      def _get_by_configs_and_owner_ids(self, configs_and_owner_ids, filters: Optional[List[Dict]] = None):          # Design in order to optimize performance on Entity creation.          # Maintainability and readability were impacted.          if not filters:              filters = [{}]          res = {}          configs_and_owner_ids = set(configs_and_owner_ids)            try:              for f in self.dir_path.iterdir():                  config_id, owner_id, entity = self.__match_file_and_get_entity(                      f, configs_and_owner_ids, copy.deepcopy(filters)                  )                    if entity:                      key = config_id, owner_id                      res[key] = entity                      configs_and_owner_ids.remove(key)                        if len(configs_and_owner_ids) == 0:                          return res          except FileNotFoundError:              # Folder with data was not created yet.              return {}            return res        def _get_by_config_and_owner_id(          self, config_id: str, owner_id: Optional[str], filters: Optional[List[Dict]] = None      ) -> Optional[Entity]:          if not filters:              filters = [{}]          else:              filters = copy.deepcopy(filters)            if owner_id is not None:              for fil in filters:                  fil.update({"owner_id": owner_id})          return self.__filter_files_by_config_and_owner_id(config_id, owner_id, filters)        #############################      # ##   Private methods   ## #      #############################        def __filter_files_by_config_and_owner_id(          self, config_id: str, owner_id: Optional[str], filters: Optional[List[Dict]] = None      ):          try:              files = filter(lambda f: config_id in f.name, self.dir_path.iterdir())              entities = map(                  lambda f: self.__file_content_to_entity(self.__filter_by(f, filters)),                  files,              )              corresponding_entities = filter(                  lambda e: e is not None and e.config_id == config_id and e.owner_id == owner_id,  # type: ignore                  entities,              )              return next(corresponding_entities, None)  # type: ignore          except FileNotFoundError:              pass          return None        def __match_file_and_get_entity(self, filepath, config_and_owner_ids, filters):          if match := [(c, p) for c, p in config_and_owner_ids if c.id in filepath.name]:              for config, owner_id in match:                  for fil in filters:                      fil.update({"config_id": config.id, "owner_id": owner_id})                    if data := self.__filter_by(filepath, filters):                      return config, owner_id, self.__file_content_to_entity(data)            return None, None, None        def __create_directory_if_not_exists(self):          self.dir_path.mkdir(parents=True, exist_ok=True)        def __search(self, attribute: str, value: str, filters: Optional[List[Dict]] = None) -> Iterator[Entity]:          return filter(lambda e: getattr(e, attribute, None) == value, self._load_all(filters))        def __get_path(self, model_id) -> pathlib.Path:          return self.dir_path / f"{model_id}.json"        def __file_content_to_entity(self, file_content):          if not file_content:              return None          if isinstance(file_content, str):              file_content = json.loads(file_content, cls=_Decoder)          model = self.model_type.from_dict(file_content)          entity = self.converter._model_to_entity(model)          return entity        def __filter_by(self, filepath: pathlib.Path, filters: Optional[List[Dict]]) -> Optional[Json]:          if not filters:              filters = [{}]            try:              file_content = self.__read_file(filepath)          except (FileNotFoundError, FileCannotBeRead):              return None            for _filter in filters:              conditions = [                  f'"{key}": "{value}"' if value is not None else f'"{key}": null' for key, value in _filter.items()              ]              if all(condition in file_content for condition in conditions):                  return json.loads(file_content, cls=_Decoder)          return None        @_retry_read_entity(__EXCEPTIONS_TO_RETRY)      def __read_file(self, filepath: pathlib.Path) -> str:          if not filepath.is_file():              raise FileNotFoundError            try:              with filepath.open("r", encoding="UTF-8") as f:                  file_content = f.read()              return file_content          except Exception:              raise FileCannotBeRead(str(filepath))


import json  import re  from datetime import datetime, timedelta      class _Decoder(json.JSONDecoder):      def __init__(self, *args, **kwargs):          json.JSONDecoder.__init__(self, object_hook=self.object_hook, *args, **kwargs)        def _str_to_timedelta(self, timedelta_str: str) -> timedelta:          """          Parse a time string e.g. (2h13m) into a timedelta object.            :param timedelta_str: A string identifying a duration.  (eg. 2h13m)          :return datetime.timedelta: A datetime.timedelta object          """          regex = re.compile(              r"^((?P<days>[\.\d]+?)d)? *"              r"((?P<hours>[\.\d]+?)h)? *"              r"((?P<minutes>[\.\d]+?)m)? *"              r"((?P<seconds>[\.\d]+?)s)?$"          )          parts = regex.match(timedelta_str)          if not parts:              raise TypeError("Can not deserialize string into timedelta")          time_params = {name: float(param) for name, param in parts.groupdict().items() if param}          # mypy has an issue with dynamic keyword parameters, hence the type ignore on the line bellow.          return timedelta(**time_params)  # type: ignore        def object_hook(self, source):          if source.get("__type__") == "Datetime":              return datetime.fromisoformat(source.get("__value__"))          if source.get("__type__") == "Timedelta":              return self._str_to_timedelta(source.get("__value__"))          else:              return source      def loads(d):      return json.loads(d, cls=_Decoder)


import pathlib  from abc import abstractmethod  from typing import Any, Dict, Generic, Iterable, List, Optional, TypeVar, Union    ModelType = TypeVar("ModelType")  Entity = TypeVar("Entity")      class _AbstractRepository(Generic[ModelType, Entity]):      @abstractmethod      def _save(self, entity: Entity):          """          Save an entity in the repository.            Parameters:              entity: The data from an object.          """          raise NotImplementedError        @abstractmethod      def _exists(self, entity_id: str) -> bool:          """          Check if an entity with id entity_id exists in the repository.          Parameters:              entity_id: The entity id, i.e., its primary key.            Returns:              True if the entity id exists.          """          raise NotImplementedError        @abstractmethod      def _load(self, entity_id: str) -> Entity:          """          Retrieve the entity data from the repository.          Parameters:              entity_id: The entity id, i.e., its primary key.            Returns:              An entity.          """          raise NotImplementedError        @abstractmethod      def _load_all(self, filters: Optional[List[Dict]] = None) -> List[Entity]:          """          Retrieve all the entities' data from the repository taking any passed filter into account.            Returns:              A list of entities.          """          raise NotImplementedError        @abstractmethod      def _delete(self, entity_id: str):          """          Delete an entity in the repository.            Parameters:              entity_id: The id of the entity to be deleted.          """          raise NotImplementedError        @abstractmethod      def _delete_all(self):          """          Delete all entities from the repository.          """          raise NotImplementedError        @abstractmethod      def _delete_many(self, ids: Iterable[str]):          """          Delete all entities from the list of ids from the repository.            Parameters:              ids: List of ids to be deleted.          """          raise NotImplementedError        @abstractmethod      def _delete_by(self, attribute: str, value: str):          """          Delete all entities from the list of ids from the repository.            Parameters:              attribute: The entity property that is the key to the search.              value: The value of the attribute that are being searched.          """          raise NotImplementedError        @abstractmethod      def _search(self, attribute: str, value: Any, filters: Optional[List[Dict]] = None) -> List[Entity]:          """          Parameters:              attribute: The entity property that is the key to the search.              value: The value of the attribute that are being searched.            Returns:              A list of entities that match the search criteria.          """          raise NotImplementedError        @abstractmethod      def _export(self, entity_id: str, folder_path: Union[str, pathlib.Path]):          """          Export an entity from the repository.            Parameters:              entity_id (str): The id of the entity to be exported.              folder_path (Union[str, pathlib.Path]): The folder path to export the entity to.          """          raise NotImplementedError


import json  import pathlib  from typing import Any, Dict, Iterable, List, Optional, Type, Union    from sqlalchemy.dialects import sqlite  from sqlalchemy.exc import NoResultFound    from .._repository._abstract_repository import _AbstractRepository  from ..common.typing import Converter, Entity, ModelType  from ..exceptions import ModelNotFound  from .db._sql_connection import _SQLConnection      class _SQLRepository(_AbstractRepository[ModelType, Entity]):      def __init__(self, model_type: Type[ModelType], converter: Type[Converter]):          """          Holds common methods to be used and extended when the need for saving          dataclasses in a SqlLite database.            Some lines have type: ignore because MyPy won't recognize some generic attributes. This          should be revised in the future.            Attributes:              model_type: Generic dataclass.              converter: A class that handles conversion to and from a database backend              db: An sqlite3 session object          """          self.db = _SQLConnection.init_db()          self.model_type = model_type          self.converter = converter          self.table = self.model_type.__table__        ###############################      # ##   Inherited methods   ## #      ###############################      def _save(self, entity: Entity):          obj = self.converter._entity_to_model(entity)          if self._exists(entity.id):  # type: ignore              self._update_entry(obj)              return          self.__insert_model(obj)        def _exists(self, entity_id: str):          query = self.table.select().filter_by(id=entity_id)          return bool(self.db.execute(str(query), [entity_id]).fetchone())        def _load(self, entity_id: str) -> Entity:          query = self.table.select().filter_by(id=entity_id)            if entry := self.db.execute(str(query.compile(dialect=sqlite.dialect())), [entity_id]).fetchone():              entry = self.model_type.from_dict(entry)              return self.converter._model_to_entity(entry)          raise ModelNotFound(str(self.model_type.__name__), entity_id)        def _load_all(self, filters: Optional[List[Dict]] = None) -> List[Entity]:          query = self.table.select()          entities: List[Entity] = []            for f in filters or [{}]:              filtered_query = query.filter_by(**f)              try:                  entries = self.db.execute(                      str(filtered_query.compile(dialect=sqlite.dialect())),                      [self.__serialize_filter_values(val) for val in list(f.values())],                  ).fetchall()                    entities.extend([self.converter._model_to_entity(self.model_type.from_dict(m)) for m in entries])              except NoResultFound:                  continue          return entities        def _delete(self, entity_id: str):          delete_query = self.table.delete().filter_by(id=entity_id)          cursor = self.db.execute(str(delete_query.compile(dialect=sqlite.dialect())), [entity_id])            if cursor.rowcount == 0:              raise ModelNotFound(str(self.model_type.__name__), entity_id)            self.db.commit()        def _delete_all(self):          self.db.execute(str(self.table.delete().compile(dialect=sqlite.dialect())))          self.db.commit()        def _delete_many(self, ids: Iterable[str]):          for entity_id in ids:              self._delete(entity_id)        def _delete_by(self, attribute: str, value: str):          delete_by_query = self.table.delete().filter_by(**{attribute: value})            self.db.execute(str(delete_by_query.compile(dialect=sqlite.dialect())), [value])          self.db.commit()        def _search(self, attribute: str, value: Any, filters: Optional[List[Dict]] = None) -> List[Entity]:          query = self.table.select().filter_by(**{attribute: value})            entities: List[Entity] = []          for f in filters or [{}]:              entries = self.db.execute(                  str(query.filter_by(**f).compile(dialect=sqlite.dialect())),                  [value] + [self.__serialize_filter_values(val) for val in list(f.values())],              ).fetchall()              entities.extend([self.converter._model_to_entity(self.model_type.from_dict(m)) for m in entries])            return entities        def _export(self, entity_id: str, folder_path: Union[str, pathlib.Path]):          if isinstance(folder_path, str):              folder: pathlib.Path = pathlib.Path(folder_path)          else:              folder = folder_path            export_dir = folder / self.table.name          if not export_dir.exists():              export_dir.mkdir(parents=True)            export_path = export_dir / f"{entity_id}.json"            query = self.table.select().filter_by(id=entity_id)            if entry := self.db.execute(str(query.compile(dialect=sqlite.dialect())), [entity_id]).fetchone():              with open(export_path, "w", encoding="utf-8") as export_file:                  export_file.write(json.dumps(entry))          else:              raise ModelNotFound(self.model_type, entity_id)  # type: ignore        ###########################################      # ##   Specific or optimized methods   ## #      ###########################################      def _get_multi(self, *, skip: int = 0, limit: int = 100) -> List[ModelType]:          query = self.table.select().offset(skip).limit(limit)          return self.db.execute(str(query.compile(dialect=sqlite.dialect()))).fetchall()        def _get_by_config(self, config_id: Any) -> Optional[ModelType]:          query = self.table.select().filter_by(config_id=config_id)          return self.db.execute(str(query.compile(dialect=sqlite.dialect())), [config_id]).fetchall()        def _get_by_config_and_owner_id(          self, config_id: str, owner_id: Optional[str], filters: Optional[List[Dict]] = None      ) -> Optional[Entity]:          if not filters:              filters = [{}]          if entry := self.__get_entities_by_config_and_owner(config_id, owner_id, filters):              return self.converter._model_to_entity(entry)          return None        def _get_by_configs_and_owner_ids(self, configs_and_owner_ids, filters: Optional[List[Dict]] = None):          # Design in order to optimize performance on Entity creation.          # Maintainability and readability were impacted.          if not filters:              filters = [{}]          res = {}          configs_and_owner_ids = set(configs_and_owner_ids)            for config, owner in configs_and_owner_ids:              entry = self.__get_entities_by_config_and_owner(config.id, owner, filters)              if entry:                  entity = self.converter._model_to_entity(entry)                  key = config, owner                  res[key] = entity            return res        def __get_entities_by_config_and_owner(          self, config_id: str, owner_id: Optional[str] = None, filters: Optional[List[Dict]] = None      ) -> Optional[ModelType]:          if not filters:              filters = []          versions = [item.get("version") for item in filters if item.get("version")]            query = self.table.select().filter_by(config_id=config_id)          parameters: List = [config_id]            if owner_id:              parameters.append(owner_id)          query = query.filter_by(owner_id=owner_id)          query = str(query.compile(dialect=sqlite.dialect()))            if versions:              table_name = self.table.name              query = query + f" AND {table_name}.version IN ({','.join(['?']*len(versions))})"              parameters.extend(versions)            if entry := self.db.execute(query, parameters).fetchone():              return self.model_type.from_dict(entry)          return None        #############################      # ##   Private methods   ## #      #############################      def __insert_model(self, model: ModelType):          query = self.table.insert()          self.db.execute(str(query.compile(dialect=sqlite.dialect())), model.to_list())          self.db.commit()        def _update_entry(self, model):          query = self.table.update().filter_by(id=model.id)          self.db.execute(str(query.compile(dialect=sqlite.dialect())), model.to_list() + [model.id])          self.db.commit()        @staticmethod      def __serialize_filter_values(value):          if isinstance(value, (dict, list)):              return json.dumps(value).replace('"', "'")          return value


#  # 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.


import json  from datetime import datetime, timedelta  from enum import Enum  from typing import Any      class _Encoder(json.JSONEncoder):      def _timedelta_to_str(self, obj: timedelta) -> str:          total_seconds = obj.total_seconds()          return (              f"{int(total_seconds // 86400)}d"              f"{int(total_seconds % 86400 // 3600)}h"              f"{int(total_seconds % 3600 // 60)}m"              f"{int(total_seconds % 60)}s"          )        def default(self, o: Any):          if isinstance(o, Enum):              result = o.value          elif isinstance(o, datetime):              result = {"__type__": "Datetime", "__value__": o.isoformat()}          elif isinstance(o, timedelta):              result = {"__type__": "Timedelta", "__value__": self._timedelta_to_str(o)}          else:              result = json.JSONEncoder.default(self, o)          return result      def dumps(d):      return json.dumps(d, cls=_Encoder)


from abc import ABC, abstractmethod      class _AbstractConverter(ABC):      @classmethod      @abstractmethod      def _entity_to_model(cls, entity):          raise NotImplementedError        @classmethod      @abstractmethod      def _model_to_entity(cls, model):          raise NotImplementedError


import dataclasses  import enum  import json  from typing import Any, Dict    from sqlalchemy import Table    from ._decoder import _Decoder  from ._encoder import _Encoder      class _BaseModel:      __table__: Table        def __iter__(self):          for attr, value in self.__dict__.items():              yield attr, value        def to_dict(self) -> Dict[str, Any]:          model_dict = {**dataclasses.asdict(self)}            for k, v in model_dict.items():              if isinstance(v, enum.Enum):                  model_dict[k] = repr(v)          return model_dict        @staticmethod      def _serialize_attribute(value):          return json.dumps(value, ensure_ascii=False, cls=_Encoder)        @staticmethod      def _deserialize_attribute(value):          if isinstance(value, str):              return json.loads(value.replace("'", '"'), cls=_Decoder)          return value        @staticmethod      def from_dict(data: Dict[str, Any]):          pass        def to_list(self):          pass


import sqlite3  from functools import lru_cache  from sqlite3 import Connection    from sqlalchemy.dialects import sqlite  from sqlalchemy.schema import CreateTable    from taipy.config.config import Config    from ...exceptions import MissingRequiredProperty      def dict_factory(cursor, row):      d = {}      for idx, col in enumerate(cursor.description):          d[col[0]] = row[idx]      return d      class _SQLConnection:      _connection = None        @classmethod      def init_db(cls):          if cls._connection:              return cls._connection            cls._connection = _build_connection()          cls._connection.row_factory = dict_factory            from ..._version._version_model import _VersionModel          from ...cycle._cycle_model import _CycleModel          from ...data._data_model import _DataNodeModel          from ...job._job_model import _JobModel          from ...scenario._scenario_model import _ScenarioModel          from ...submission._submission_model import _SubmissionModel          from ...task._task_model import _TaskModel            cls._connection.execute(              str(CreateTable(_CycleModel.__table__, if_not_exists=True).compile(dialect=sqlite.dialect()))          )          cls._connection.execute(              str(CreateTable(_DataNodeModel.__table__, if_not_exists=True).compile(dialect=sqlite.dialect()))          )          cls._connection.execute(              str(CreateTable(_JobModel.__table__, if_not_exists=True).compile(dialect=sqlite.dialect()))          )          cls._connection.execute(              str(CreateTable(_ScenarioModel.__table__, if_not_exists=True).compile(dialect=sqlite.dialect()))          )          cls._connection.execute(              str(CreateTable(_TaskModel.__table__, if_not_exists=True).compile(dialect=sqlite.dialect()))          )          cls._connection.execute(              str(CreateTable(_VersionModel.__table__, if_not_exists=True).compile(dialect=sqlite.dialect()))          )          cls._connection.execute(              str(CreateTable(_SubmissionModel.__table__, if_not_exists=True).compile(dialect=sqlite.dialect()))          )            return cls._connection      def _build_connection() -> Connection:      # Set SQLite threading mode to Serialized, means that threads may share the module, connections and cursors      sqlite3.threadsafety = 3        properties = Config.core.repository_properties      try:          db_location = properties["db_location"]      except KeyError:          raise MissingRequiredProperty("Missing property db_location.")        return __build_connection(db_location)      @lru_cache  def __build_connection(db_location: str):      return sqlite3.connect(db_location, check_same_thread=False)


from sqlalchemy.orm import declarative_base, registry    _SQLBaseModel = declarative_base()  mapper_registry = registry()


import os    from taipy.config import Config    __BACKUP_FILE_PATH_ENVIRONMENT_VARIABLE_NAME = "TAIPY_BACKUP_FILE_PATH"      def _init_backup_file_with_storage_folder():      if preserve_file_path := os.getenv(__BACKUP_FILE_PATH_ENVIRONMENT_VARIABLE_NAME):          with open(preserve_file_path, "a") as f:              f.write(f"{Config.core.storage_folder}\n")      def _append_to_backup_file(new_file_path: str):      if preserve_file_path := os.getenv(__BACKUP_FILE_PATH_ENVIRONMENT_VARIABLE_NAME):          storage_folder = os.path.abspath(Config.core.storage_folder) + os.sep          if not os.path.abspath(new_file_path).startswith(storage_folder):              with open(preserve_file_path, "a") as f:                  f.write(f"{new_file_path}\n")      def _remove_from_backup_file(to_remove_file_path: str):      if preserve_file_path := os.getenv(__BACKUP_FILE_PATH_ENVIRONMENT_VARIABLE_NAME, None):          storage_folder = os.path.abspath(Config.core.storage_folder) + os.sep          if not os.path.abspath(to_remove_file_path).startswith(storage_folder):              try:                  with open(preserve_file_path, "r+") as f:                      old_backup = f.read()                      to_remove_file_path = to_remove_file_path + "\n"                        # To avoid removing the file path of different data nodes that are pointing                      # to the same file. We will only replace the file path only once.                      if old_backup.startswith(to_remove_file_path):                          new_backup = old_backup.replace(to_remove_file_path, "", 1)                      else:                          new_backup = old_backup.replace("\n" + to_remove_file_path, "\n", 1)                        if new_backup is not old_backup:                          f.seek(0)                          f.write(new_backup)                          f.truncate()              except Exception:                  pass      def _replace_in_backup_file(old_file_path: str, new_file_path: str):      _remove_from_backup_file(old_file_path)      _append_to_backup_file(new_file_path)


import re  from copy import copy  from typing import Any, Dict, Optional, Union    from taipy.config import Config, UniqueSection  from taipy.config._config import _Config  from taipy.config.common._config_blocker import _ConfigBlocker  from taipy.config.common._template_handler import _TemplateHandler as _tpl    from .._init_version import _read_version  from ..exceptions.exceptions import ConfigCoreVersionMismatched      class CoreSection(UniqueSection):      """      Configuration parameters for running the `Core^` service.        Attributes:          root_folder (str): Path of the base folder for the taipy application. The default value is "./taipy/"          storage_folder (str): Folder name used to store Taipy data. The default value is ".data/". It is used in              conjunction with the *root_folder* attribute. That means the storage path is <root_folder><storage_folder>              (The default path is "./taipy/.data/").          repository_type (str): Type of the repository to be used to store Taipy data. The default value is              "filesystem".          repository_properties (Dict[str, Union[str, int]]): A dictionary of additional properties to be used by the              repository.          read_entity_retry (int): Number of retries to read an entity from the repository before return failure.              The default value is 3.          mode (str): The Taipy operating mode. By default, the `Core^` service runs in "development" mode.              An "experiment" and a "production" mode are also available. Please refer to the              [Versioning management](../core/versioning/index.md) documentation page for more details.          version_number (str)): The identifier of the user application version. Please refer to the              [Versioning management](../core/versioning/index.md) documentation page for more details.          force (bool): If True, force the application run even if there are some conflicts in the              configuration.          core_version (str): The Taipy Core package version.          **properties (dict[str, any]): A dictionary of additional properties.      """        name = "CORE"        _ROOT_FOLDER_KEY = "root_folder"      _DEFAULT_ROOT_FOLDER = "./taipy/"        _STORAGE_FOLDER_KEY = "storage_folder"      _DEFAULT_STORAGE_FOLDER = ".data/"        _REPOSITORY_TYPE_KEY = "repository_type"      _DEFAULT_REPOSITORY_TYPE = "filesystem"      _REPOSITORY_PROPERTIES_KEY = "repository_properties"      _DEFAULT_REPOSITORY_PROPERTIES: Dict = dict()        _READ_ENTITY_RETRY_KEY = "read_entity_retry"      _DEFAULT_READ_ENTITY_RETRY = 1        _MODE_KEY = "mode"      _DEVELOPMENT_MODE = "development"      _EXPERIMENT_MODE = "experiment"      _PRODUCTION_MODE = "production"      _DEFAULT_MODE = _DEVELOPMENT_MODE        _VERSION_NUMBER_KEY = "version_number"      _DEFAULT_VERSION_NUMBER = ""        _FORCE_KEY = "force"      _DEFAULT_FORCE = False        _CORE_VERSION_KEY = "core_version"      _CURRENT_CORE_VERSION = _read_version()        def __init__(          self,          root_folder: Optional[str] = None,          storage_folder: Optional[str] = None,          repository_type: Optional[str] = None,          repository_properties: Optional[Dict[str, Union[str, int]]] = None,          read_entity_retry: Optional[int] = None,          mode: Optional[str] = None,          version_number: Optional[str] = None,          force: Optional[bool] = None,          core_version: Optional[str] = None,          **properties,      ):          self._root_folder = root_folder          self._storage_folder = storage_folder          self._repository_type = repository_type          self._repository_properties = repository_properties or {}          self._read_entity_retry = (              read_entity_retry if read_entity_retry is not None else self._DEFAULT_READ_ENTITY_RETRY          )          self.mode = mode or self._DEFAULT_MODE          self.version_number = version_number or self._DEFAULT_VERSION_NUMBER          self.force = force or self._DEFAULT_FORCE            self._check_compatibility(core_version)          self._core_version = core_version            super().__init__(**properties)        def __copy__(self):          return CoreSection(              self.root_folder,              self.storage_folder,              self.repository_type,              self.repository_properties,              self.read_entity_retry,              self.mode,              self.version_number,              self.force,              self._core_version,              **copy(self._properties),          )        @property      def storage_folder(self):          return _tpl._replace_templates(self._storage_folder)        @storage_folder.setter  # type: ignore      @_ConfigBlocker._check()      def storage_folder(self, val):          self._storage_folder = val        @property      def root_folder(self):          return _tpl._replace_templates(self._root_folder)        @root_folder.setter  # type: ignore      @_ConfigBlocker._check()      def root_folder(self, val):          self._root_folder = val        @property      def repository_type(self):          return _tpl._replace_templates(self._repository_type)        @repository_type.setter  # type: ignore      @_ConfigBlocker._check()      def repository_type(self, val):          self._repository_type = val        @property      def repository_properties(self):          return (              {k: _tpl._replace_templates(v) for k, v in self._repository_properties.items()}              if self._repository_properties              else self._DEFAULT_REPOSITORY_PROPERTIES.copy()          )        @repository_properties.setter  # type: ignore      @_ConfigBlocker._check()      def repository_properties(self, val):          self._repository_properties = val        @property      def read_entity_retry(self):          return _tpl._replace_templates(self._read_entity_retry)        @read_entity_retry.setter  # type: ignore      @_ConfigBlocker._check()      def read_entity_retry(self, val):          self._read_entity_retry = val        @classmethod      def default_config(cls):          return CoreSection(              cls._DEFAULT_ROOT_FOLDER,              cls._DEFAULT_STORAGE_FOLDER,              cls._DEFAULT_REPOSITORY_TYPE,              cls._DEFAULT_REPOSITORY_PROPERTIES,              cls._DEFAULT_READ_ENTITY_RETRY,              cls._DEFAULT_MODE,              cls._DEFAULT_VERSION_NUMBER,              cls._DEFAULT_FORCE,              cls._CURRENT_CORE_VERSION,          )        def _clean(self):          self._root_folder = self._DEFAULT_ROOT_FOLDER          self._storage_folder = self._DEFAULT_STORAGE_FOLDER          self._repository_type = self._DEFAULT_REPOSITORY_TYPE          self._repository_properties = self._DEFAULT_REPOSITORY_PROPERTIES.copy()          self._read_entity_retry = self._DEFAULT_READ_ENTITY_RETRY          self.mode = self._DEFAULT_MODE          self.version_number = self._DEFAULT_VERSION_NUMBER          self.force = self._DEFAULT_FORCE          self._core_version = self._CURRENT_CORE_VERSION          self._properties.clear()        def _to_dict(self):          as_dict = {}          if self._root_folder:              as_dict[self._ROOT_FOLDER_KEY] = self._root_folder          if self._storage_folder:              as_dict[self._STORAGE_FOLDER_KEY] = self._storage_folder          if self._repository_type:              as_dict[self._REPOSITORY_TYPE_KEY] = self._repository_type          if self._repository_properties:              as_dict[self._REPOSITORY_PROPERTIES_KEY] = self._repository_properties          if self._read_entity_retry is not None:              as_dict[self._READ_ENTITY_RETRY_KEY] = self._read_entity_retry          if self.mode is not None:              as_dict[self._MODE_KEY] = self.mode          if self.version_number is not None:              as_dict[self._VERSION_NUMBER_KEY] = self.version_number          if self.force is not None:              as_dict[self._FORCE_KEY] = self.force          if self._core_version is not None:              as_dict[self._CORE_VERSION_KEY] = self._core_version          as_dict.update(self._properties)          return as_dict        @classmethod      def _from_dict(cls, as_dict: Dict[str, Any], id=None, config: Optional[_Config] = None):          root_folder = as_dict.pop(cls._ROOT_FOLDER_KEY, None)          storage_folder = as_dict.pop(cls._STORAGE_FOLDER_KEY, None)          repository_type = as_dict.pop(cls._REPOSITORY_TYPE_KEY, None)          repository_properties = as_dict.pop(cls._REPOSITORY_PROPERTIES_KEY, None)          read_entity_retry = as_dict.pop(cls._READ_ENTITY_RETRY_KEY, None)          mode = as_dict.pop(cls._MODE_KEY, None)          version_nb = as_dict.pop(cls._VERSION_NUMBER_KEY, None)          force = as_dict.pop(cls._FORCE_KEY, None)          core_version = as_dict.pop(cls._CORE_VERSION_KEY, None)          return CoreSection(              root_folder,              storage_folder,              repository_type,              repository_properties,              read_entity_retry,              mode,              version_nb,              force,              core_version,              **as_dict,          )        def _update(self, as_dict: Dict[str, Any]):          root_folder = _tpl._replace_templates(as_dict.pop(self._ROOT_FOLDER_KEY, self._root_folder))          if self._root_folder != root_folder:              self._root_folder = root_folder            storage_folder = _tpl._replace_templates(as_dict.pop(self._STORAGE_FOLDER_KEY, self._storage_folder))          if self._storage_folder != storage_folder:              self._storage_folder = storage_folder            repository_type = _tpl._replace_templates(as_dict.pop(self._REPOSITORY_TYPE_KEY, self._repository_type))          if self._repository_type != repository_type:              self._repository_type = repository_type            repository_properties = _tpl._replace_templates(              as_dict.pop(self._REPOSITORY_PROPERTIES_KEY, self._repository_properties)          )          self._repository_properties.update(repository_properties)            read_entity_retry = _tpl._replace_templates(as_dict.pop(self._READ_ENTITY_RETRY_KEY, self._read_entity_retry))          if self._read_entity_retry != read_entity_retry:              self._read_entity_retry = read_entity_retry            mode = _tpl._replace_templates(as_dict.pop(self._MODE_KEY, self.mode))          if self.mode != mode:              self.mode = mode            version_number = _tpl._replace_templates(as_dict.pop(self._VERSION_NUMBER_KEY, self.version_number))          if self.version_number != version_number:              self.version_number = version_number            force = _tpl._replace_templates(as_dict.pop(self._FORCE_KEY, self.force))          if self.force != force:              self.force = force            core_version = as_dict.pop(self._CORE_VERSION_KEY, None)          self._check_compatibility(core_version)            self._properties.update(as_dict)        @classmethod      def _check_compatibility(cls, core_version):          if not core_version:              return            version_pattern = r"^(\d+)\.(\d+)\.(\d+)$"          dev_version_pattern = r"^(\d+)\.(\d+)\.(\d+).(\w*)$"            installed_match = re.match(version_pattern, cls._CURRENT_CORE_VERSION) or re.match(              dev_version_pattern, cls._CURRENT_CORE_VERSION          )          required_match = re.match(version_pattern, core_version) or re.match(dev_version_pattern, core_version)          if required_match and installed_match:              installed_group = installed_match.groups()              required_group = required_match.groups()              installed_major, installed_minor = installed_group[0], installed_group[1]              required_major, required_minor = required_group[0], required_group[1]                if required_major != installed_major or required_minor != installed_minor:                  raise ConfigCoreVersionMismatched(core_version, cls._CURRENT_CORE_VERSION)        @staticmethod      def _configure(          root_folder: Optional[str] = None,          storage_folder: Optional[str] = None,          repository_type: Optional[str] = None,          repository_properties: Optional[Dict[str, Union[str, int]]] = None,          read_entity_retry: Optional[int] = None,          mode: Optional[str] = None,          version_number: Optional[str] = None,          force: Optional[bool] = None,          **properties,      ) -> "CoreSection":          """Configure the Core service.            Parameters:              root_folder (Optional[str]): Path of the base folder for the taipy application.                  The default value is "./taipy/"              storage_folder (Optional[str]): Folder name used to store Taipy data. The default value is ".data/".                  It is used in conjunction with the `root_folder` field. That means the storage path is                  <root_folder><storage_folder> (The default path is "./taipy/.data/").              repository_type (Optional[str]): The type of the repository to be used to store Taipy data.                  The default value is "filesystem".              repository_properties (Optional[Dict[str, Union[str, int]]]): A dictionary of additional properties                  to be used by the repository.              read_entity_retry (Optional[int]): Number of retries to read an entity from the repository                  before return failure. The default value is 3.              mode (Optional[str]): Indicates the mode of the version management system.                  Possible values are *"development"*, *"experiment"*, or *"production"*.              version_number (Optional[str]): The string identifier of the version.                   In development mode, the version number is ignored.              force (Optional[bool]): If True, Taipy will override a version even if the configuration                  has changed and run the application.              **properties (Dict[str, Any]): A keyworded variable length list of additional arguments configure the                  behavior of the `Core^` service.          Returns:              The Core configuration.          """          section = CoreSection(              root_folder=root_folder,              storage_folder=storage_folder,              repository_type=repository_type,              repository_properties=repository_properties,              read_entity_retry=read_entity_retry,              mode=mode,              version_number=version_number,              force=force,              core_version=_read_version(),              **properties,          )          Config._register(section)          return Config.unique_sections[CoreSection.name]