trgiangdo 1 rok pred
rodič
commit
63eb6946c2

+ 0 - 2
taipy/_cli/_base_cli/__init__.py

@@ -8,5 +8,3 @@
 # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
 # 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
 # an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
 # specific language governing permissions and limitations under the License.
 # specific language governing permissions and limitations under the License.
-
-from ._cli import _CLI

+ 64 - 0
taipy/_cli/_base_cli/_abstract_cli.py

@@ -0,0 +1,64 @@
+# Copyright 2021-2024 Avaiga Private Limited
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+#        http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+# an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations under the License.
+
+import sys
+from abc import abstractmethod
+from difflib import SequenceMatcher
+from typing import List, Optional
+
+from taipy.logger._taipy_logger import _TaipyLogger
+
+from ._taipy_parser import _TaipyParser
+
+
+class _AbstractCLI:
+    _logger = _TaipyLogger._get_logger()
+
+    _COMMAND_NAME: Optional[str] = None
+    _ARGUMENTS: List[str] = []
+    SIMILARITY_THRESHOLD = 0.8
+
+    @classmethod
+    @abstractmethod
+    def create_parser(cls):
+        raise NotImplementedError
+
+    @classmethod
+    @abstractmethod
+    def handle_command(cls):
+        raise NotImplementedError
+
+    @classmethod
+    def _parse_arguments(cls):
+        args, unknown_args = _TaipyParser._parser.parse_known_args()
+
+        if getattr(args, "which", None) != cls._COMMAND_NAME:
+            return
+
+        if unknown_args:
+            _TaipyParser._sub_taipyparsers.get(cls._COMMAND_NAME).print_help()
+            for unknown_arg in unknown_args:
+                if not unknown_arg.startswith("-"):
+                    continue
+
+                error_message = f"Unknown arguments: {unknown_arg}."
+                similar_args = [
+                    arg
+                    for arg in cls._ARGUMENTS
+                    if SequenceMatcher(None, unknown_arg, arg).ratio() > cls.SIMILARITY_THRESHOLD
+                ]
+                if similar_args:
+                    error_message += f" Did you mean: {' or '.join(similar_args)}?"
+                cls._logger.error(error_message)
+            cls._logger.error("Please refer to the help message above for more information.")
+            sys.exit(1)
+
+        return args

+ 1 - 7
taipy/_cli/_base_cli/_cli.py → taipy/_cli/_base_cli/_taipy_parser.py

@@ -13,7 +13,7 @@ import argparse
 from typing import Dict
 from typing import Dict
 
 
 
 
-class _CLI:
+class _TaipyParser:
     """Argument parser for Taipy application."""
     """Argument parser for Taipy application."""
 
 
     # The conflict_handler is set to "resolve" to override conflict arguments
     # The conflict_handler is set to "resolve" to override conflict arguments
@@ -51,12 +51,6 @@ class _CLI:
         cls._arg_groups[title] = groupparser
         cls._arg_groups[title] = groupparser
         return groupparser
         return groupparser
 
 
-    @classmethod
-    def _parse(cls):
-        """Parse and return only known arguments."""
-        args, _ = cls._parser.parse_known_args()
-        return args
-
     @classmethod
     @classmethod
     def _remove_argument(cls, arg: str):
     def _remove_argument(cls, arg: str):
         """Remove an argument from the parser. Note that the `arg` must be without --.
         """Remove an argument from the parser. Note that the `arg` must be without --.

+ 21 - 16
taipy/_cli/_help_cli.py

@@ -11,31 +11,36 @@
 
 
 import sys
 import sys
 
 
-from taipy._cli._base_cli import _CLI
-from taipy.logger._taipy_logger import _TaipyLogger
+from ._base_cli._abstract_cli import _AbstractCLI
+from ._base_cli._taipy_parser import _TaipyParser
 
 
 
 
-class _HelpCLI:
-    __logger = _TaipyLogger._get_logger()
+class _HelpCLI(_AbstractCLI):
+    _COMMAND_NAME = "help"
 
 
     @classmethod
     @classmethod
     def create_parser(cls):
     def create_parser(cls):
-        create_parser = _CLI._add_subparser("help", help="Show the Taipy help message.", add_help=False)
+        create_parser = _TaipyParser._add_subparser(
+            cls._COMMAND_NAME,
+            help="Show the Taipy help message.",
+            add_help=False,
+        )
         create_parser.add_argument(
         create_parser.add_argument(
             "command", nargs="?", type=str, const="", default="", help="Show the help message of the command."
             "command", nargs="?", type=str, const="", default="", help="Show the help message of the command."
         )
         )
 
 
     @classmethod
     @classmethod
-    def parse_arguments(cls):
-        args = _CLI._parse()
-
-        if getattr(args, "which", None) == "help":
-            if args.command:
-                if args.command in _CLI._sub_taipyparsers.keys():
-                    _CLI._sub_taipyparsers.get(args.command).print_help()
-                else:
-                    cls.__logger.error(f"{args.command} is not a valid command.")
+    def handle_command(cls):
+        args = cls._parse_arguments()
+        if not args:
+            return
+
+        if args.command:
+            if args.command in _TaipyParser._sub_taipyparsers.keys():
+                _TaipyParser._sub_taipyparsers.get(args.command).print_help()
             else:
             else:
-                _CLI._parser.print_help()
+                cls._logger.error(f"{args.command} is not a valid command.")
+        else:
+            _TaipyParser._parser.print_help()
 
 
-            sys.exit(0)
+        sys.exit(0)

+ 31 - 27
taipy/_cli/_run_cli.py

@@ -12,13 +12,16 @@
 import subprocess
 import subprocess
 import sys
 import sys
 
 
-from taipy._cli._base_cli import _CLI
+from ._base_cli._abstract_cli import _AbstractCLI
+from ._base_cli._taipy_parser import _TaipyParser
 
 
 
 
-class _RunCLI:
+class _RunCLI(_AbstractCLI):
+    _COMMAND_NAME = "run"
+
     @classmethod
     @classmethod
     def create_parser(cls):
     def create_parser(cls):
-        run_parser = _CLI._add_subparser("run", help="Run a Taipy application.")
+        run_parser = _TaipyParser._add_subparser(cls._COMMAND_NAME, help="Run a Taipy application.")
         run_parser.add_argument(
         run_parser.add_argument(
             "application_main_file",
             "application_main_file",
         )
         )
@@ -33,27 +36,28 @@ class _RunCLI:
         )
         )
 
 
     @classmethod
     @classmethod
-    def parse_arguments(cls):
-        args = _CLI._parse()
-
-        if getattr(args, "which", None) == "run":
-            all_args = sys.argv[3:]  # First 3 args are always (1) Python executable, (2) run, (3) Python file
-
-            external_args = []
-            try:
-                external_args_index = all_args.index("external-args")
-            except ValueError:
-                pass
-            else:
-                external_args.extend(all_args[external_args_index + 1 :])
-                all_args = all_args[:external_args_index]
-
-            taipy_args = [f"--taipy-{arg[2:]}" if arg.startswith("--") else arg for arg in all_args]
-
-            subprocess.run(
-                [sys.executable, args.application_main_file, *(external_args + taipy_args)],
-                stdout=sys.stdout,
-                stderr=sys.stdout,
-            )
-
-            sys.exit(0)
+    def handle_command(cls):
+        args = cls._parse_arguments()
+        if not args:
+            return
+
+        all_args = sys.argv[3:]  # First 3 args are always (1) Python executable, (2) run, (3) Python file
+
+        external_args = []
+        try:
+            external_args_index = all_args.index("external-args")
+        except ValueError:
+            pass
+        else:
+            external_args.extend(all_args[external_args_index + 1 :])
+            all_args = all_args[:external_args_index]
+
+        taipy_args = [f"--taipy-{arg[2:]}" if arg.startswith("--") else arg for arg in all_args]
+
+        subprocess.run(
+            [sys.executable, args.application_main_file, *(external_args + taipy_args)],
+            stdout=sys.stdout,
+            stderr=sys.stdout,
+        )
+
+        sys.exit(0)

+ 17 - 9
taipy/_cli/_scaffold_cli.py

@@ -15,17 +15,24 @@ import sys
 from cookiecutter.main import cookiecutter
 from cookiecutter.main import cookiecutter
 
 
 import taipy
 import taipy
-from taipy._cli._base_cli import _CLI
 
 
+from ._base_cli._abstract_cli import _AbstractCLI
+from ._base_cli._taipy_parser import _TaipyParser
 
 
-class _ScaffoldCLI:
+
+class _ScaffoldCLI(_AbstractCLI):
     __TAIPY_PATH = pathlib.Path(taipy.__file__).parent.resolve() / "templates"
     __TAIPY_PATH = pathlib.Path(taipy.__file__).parent.resolve() / "templates"
+    _TEMPLATE_MAP = {str(x.name): str(x) for x in __TAIPY_PATH.iterdir() if x.is_dir() and not x.name.startswith("_")}
 
 
-    _TEMPLATE_MAP = {str(x.name): str(x) for x in __TAIPY_PATH.iterdir() if x.is_dir()}
+    _COMMAND_NAME = "create"
+    _ARGUMENTS = ["--template"]
 
 
     @classmethod
     @classmethod
     def create_parser(cls):
     def create_parser(cls):
-        create_parser = _CLI._add_subparser("create", help="Create a new Taipy application.")
+        create_parser = _TaipyParser._add_subparser(
+            cls._COMMAND_NAME,
+            help="Create a new Taipy application using pre-defined templates.",
+        )
         create_parser.add_argument(
         create_parser.add_argument(
             "--template",
             "--template",
             choices=list(cls._TEMPLATE_MAP.keys()),
             choices=list(cls._TEMPLATE_MAP.keys()),
@@ -34,9 +41,10 @@ class _ScaffoldCLI:
         )
         )
 
 
     @classmethod
     @classmethod
-    def parse_arguments(cls):
-        args = _CLI._parse()
+    def handle_command(cls):
+        args = cls._parse_arguments()
+        if not args:
+            return
 
 
-        if getattr(args, "which", None) == "create":
-            cookiecutter(cls._TEMPLATE_MAP[args.template])
-            sys.exit(0)
+        cookiecutter(cls._TEMPLATE_MAP[args.template])
+        sys.exit(0)

+ 15 - 10
taipy/_entrypoint.py

@@ -13,7 +13,7 @@ import os
 import sys
 import sys
 from importlib.util import find_spec
 from importlib.util import find_spec
 
 
-from taipy._cli._base_cli import _CLI
+from taipy._cli._base_cli._taipy_parser import _TaipyParser
 from taipy.core._core_cli import _CoreCLI
 from taipy.core._core_cli import _CoreCLI
 from taipy.core._entity._migrate_cli import _MigrateCLI
 from taipy.core._entity._migrate_cli import _MigrateCLI
 from taipy.core._version._cli._version_cli import _VersionCLI
 from taipy.core._version._cli._version_cli import _VersionCLI
@@ -29,7 +29,12 @@ def _entrypoint():
     # Add the current working directory to path to execute version command on FS repo
     # Add the current working directory to path to execute version command on FS repo
     sys.path.append(os.path.normpath(os.getcwd()))
     sys.path.append(os.path.normpath(os.getcwd()))
 
 
-    _CLI._parser.add_argument("-v", "--version", action="store_true", help="Print the current Taipy version and exit.")
+    _TaipyParser._parser.add_argument(
+        "-v",
+        "--version",
+        action="store_true",
+        help="Print the current Taipy version and exit.",
+    )
 
 
     _RunCLI.create_parser()
     _RunCLI.create_parser()
     _GuiCLI.create_run_parser()
     _GuiCLI.create_run_parser()
@@ -45,16 +50,16 @@ def _entrypoint():
 
 
         _enterprise_entrypoint()
         _enterprise_entrypoint()
 
 
-    args = _CLI._parse()
+    args, _ = _TaipyParser._parser.parse_known_args()
     if args.version:
     if args.version:
         print(f"Taipy {_get_version()}")  # noqa: T201
         print(f"Taipy {_get_version()}")  # noqa: T201
         sys.exit(0)
         sys.exit(0)
 
 
-    _RunCLI.parse_arguments()
-    _HelpCLI.parse_arguments()
-    _VersionCLI.parse_arguments()
-    _MigrateCLI.parse_arguments()
-    _ScaffoldCLI.parse_arguments()
+    _RunCLI.handle_command()
+    _HelpCLI.handle_command()
+    _VersionCLI.handle_command()
+    _MigrateCLI.handle_command()
+    _ScaffoldCLI.handle_command()
 
 
-    _CLI._remove_argument("help")
-    _CLI._parser.print_help()
+    _TaipyParser._remove_argument("help")
+    _TaipyParser._parser.print_help()

+ 1 - 1
taipy/core/_core.py

@@ -103,7 +103,7 @@ class Core:
     def __update_core_section(cls):
     def __update_core_section(cls):
         cls.__logger.info("Updating configuration with command-line arguments...")
         cls.__logger.info("Updating configuration with command-line arguments...")
         _CoreCLI.create_parser()
         _CoreCLI.create_parser()
-        Config._applied_config._unique_sections[CoreSection.name]._update(_CoreCLI.parse_arguments())
+        Config._applied_config._unique_sections[CoreSection.name]._update(_CoreCLI.handle_command())
 
 
     @classmethod
     @classmethod
     def __manage_version(cls):
     def __manage_version(cls):

+ 8 - 6
taipy/core/_core_cli.py

@@ -11,12 +11,13 @@
 
 
 from typing import Dict
 from typing import Dict
 
 
-from taipy._cli._base_cli import _CLI
+from taipy._cli._base_cli._abstract_cli import _AbstractCLI
+from taipy._cli._base_cli._taipy_parser import _TaipyParser
 
 
 from .config import CoreSection
 from .config import CoreSection
 
 
 
 
-class _CoreCLI:
+class _CoreCLI(_AbstractCLI):
     """Command-line interface for Taipy Core application."""
     """Command-line interface for Taipy Core application."""
 
 
     __MODE_ARGS: Dict[str, Dict] = {
     __MODE_ARGS: Dict[str, Dict] = {
@@ -69,7 +70,7 @@ class _CoreCLI:
 
 
     @classmethod
     @classmethod
     def create_parser(cls):
     def create_parser(cls):
-        core_parser = _CLI._add_groupparser("Taipy Core", "Optional arguments for Taipy Core service")
+        core_parser = _TaipyParser._add_groupparser("Taipy Core", "Optional arguments for Taipy Core service")
 
 
         mode_group = core_parser.add_mutually_exclusive_group()
         mode_group = core_parser.add_mutually_exclusive_group()
         for mode_arg, mode_arg_dict in cls.__MODE_ARGS.items():
         for mode_arg, mode_arg_dict in cls.__MODE_ARGS.items():
@@ -81,7 +82,7 @@ class _CoreCLI:
 
 
     @classmethod
     @classmethod
     def create_run_parser(cls):
     def create_run_parser(cls):
-        run_parser = _CLI._add_subparser("run", help="Run a Taipy application.")
+        run_parser = _TaipyParser._add_subparser("run", help="Run a Taipy application.")
         mode_group = run_parser.add_mutually_exclusive_group()
         mode_group = run_parser.add_mutually_exclusive_group()
         for mode_arg, mode_arg_dict in cls.__MODE_ARGS.items():
         for mode_arg, mode_arg_dict in cls.__MODE_ARGS.items():
             mode_group.add_argument(mode_arg, **mode_arg_dict)
             mode_group.add_argument(mode_arg, **mode_arg_dict)
@@ -91,8 +92,9 @@ class _CoreCLI:
             force_group.add_argument(force_arg, **force_arg_dict)
             force_group.add_argument(force_arg, **force_arg_dict)
 
 
     @classmethod
     @classmethod
-    def parse_arguments(cls):
-        args = _CLI._parse()
+    def handle_command(cls):
+        args, _ = _TaipyParser._parser.parse_known_args()
+
         as_dict = {}
         as_dict = {}
         if args.taipy_development:
         if args.taipy_development:
             as_dict[CoreSection._MODE_KEY] = CoreSection._DEVELOPMENT_MODE
             as_dict[CoreSection._MODE_KEY] = CoreSection._DEVELOPMENT_MODE

+ 19 - 15
taipy/core/_entity/_migrate_cli.py

@@ -12,9 +12,9 @@
 import sys
 import sys
 from typing import List
 from typing import List
 
 
-from taipy._cli._base_cli import _CLI
+from taipy._cli._base_cli._abstract_cli import _AbstractCLI
+from taipy._cli._base_cli._taipy_parser import _TaipyParser
 from taipy.config.config import Config
 from taipy.config.config import Config
-from taipy.logger._taipy_logger import _TaipyLogger
 
 
 from ._migrate import (
 from ._migrate import (
     _migrate_fs_entities,
     _migrate_fs_entities,
@@ -29,19 +29,19 @@ from ._migrate import (
 )
 )
 
 
 
 
-class _MigrateCLI:
-    __logger = _TaipyLogger._get_logger()
+class _MigrateCLI(_AbstractCLI):
+    _COMMAND_NAME = "migrate"
+    _ARGUMENTS = ["--repository-type", "--skip-backup", "--restore", "--remove-backup"]
 
 
     @classmethod
     @classmethod
     def create_parser(cls):
     def create_parser(cls):
-        migrate_parser = _CLI._add_subparser(
-            "migrate",
+        migrate_parser = _TaipyParser._add_subparser(
+            cls._COMMAND_NAME,
             help="Migrate entities created from old taipy versions to be compatible with the current taipy version. "
             help="Migrate entities created from old taipy versions to be compatible with the current taipy version. "
             " The entity migration should be performed only after updating taipy code to the current version.",
             " The entity migration should be performed only after updating taipy code to the current version.",
         )
         )
         migrate_parser.add_argument(
         migrate_parser.add_argument(
             "--repository-type",
             "--repository-type",
-            required=True,
             nargs="+",
             nargs="+",
             help="The type of repository to migrate. If filesystem or sql, a path to the database folder/.sqlite file "
             help="The type of repository to migrate. If filesystem or sql, a path to the database folder/.sqlite file "
             "should be informed. In case of mongo host, port, user and password must be informed, if left empty it "
             "should be informed. In case of mongo host, port, user and password must be informed, if left empty it "
@@ -64,12 +64,16 @@ class _MigrateCLI:
         )
         )
 
 
     @classmethod
     @classmethod
-    def parse_arguments(cls):
-        args = _CLI._parse()
-
-        if getattr(args, "which", None) != "migrate":
+    def handle_command(cls):
+        args = cls._parse_arguments()
+        if not args:
             return
             return
 
 
+        if not args.repository_type:
+            _TaipyParser._sub_taipyparsers.get(cls._COMMAND_NAME).print_help()
+            cls._logger.error("The following arguments are required: --repository-type")
+            sys.exit(1)
+
         repository_type = args.repository_type[0]
         repository_type = args.repository_type[0]
         repository_args = args.repository_type[1:] if len(args.repository_type) > 1 else [None]
         repository_args = args.repository_type[1:] if len(args.repository_type) > 1 else [None]
 
 
@@ -78,7 +82,7 @@ class _MigrateCLI:
         if args.remove_backup:
         if args.remove_backup:
             cls.__handle_remove_backup(repository_type, repository_args)
             cls.__handle_remove_backup(repository_type, repository_args)
 
 
-        do_backup =  not args.skip_backup
+        do_backup = not args.skip_backup
         cls.__migrate_entities(repository_type, repository_args, do_backup)
         cls.__migrate_entities(repository_type, repository_args, do_backup)
         sys.exit(0)
         sys.exit(0)
 
 
@@ -95,7 +99,7 @@ class _MigrateCLI:
             if not _remove_backup_mongo_entities():
             if not _remove_backup_mongo_entities():
                 sys.exit(1)
                 sys.exit(1)
         else:
         else:
-            cls.__logger.error(f"Unknown repository type {repository_type}")
+            cls._logger.error(f"Unknown repository type {repository_type}")
             sys.exit(1)
             sys.exit(1)
 
 
         sys.exit(0)
         sys.exit(0)
@@ -114,7 +118,7 @@ class _MigrateCLI:
             if not _restore_migrate_mongo_entities(*mongo_args):
             if not _restore_migrate_mongo_entities(*mongo_args):
                 sys.exit(1)
                 sys.exit(1)
         else:
         else:
-            cls.__logger.error(f"Unknown repository type {repository_type}")
+            cls._logger.error(f"Unknown repository type {repository_type}")
             sys.exit(1)
             sys.exit(1)
         sys.exit(0)
         sys.exit(0)
 
 
@@ -134,5 +138,5 @@ class _MigrateCLI:
             _migrate_mongo_entities(*mongo_args, backup=do_backup)  # type: ignore
             _migrate_mongo_entities(*mongo_args, backup=do_backup)  # type: ignore
 
 
         else:
         else:
-            cls.__logger.error(f"Unknown repository type {repository_type}")
+            cls._logger.error(f"Unknown repository type {repository_type}")
             sys.exit(1)
             sys.exit(1)

+ 17 - 17
taipy/core/_version/_cli/_version_cli.py

@@ -11,10 +11,10 @@
 
 
 import sys
 import sys
 
 
-from taipy._cli._base_cli import _CLI
+from taipy._cli._base_cli._abstract_cli import _AbstractCLI
+from taipy._cli._base_cli._taipy_parser import _TaipyParser
 from taipy.config import Config
 from taipy.config import Config
 from taipy.config.exceptions.exceptions import InconsistentEnvVariableError
 from taipy.config.exceptions.exceptions import InconsistentEnvVariableError
-from taipy.logger._taipy_logger import _TaipyLogger
 
 
 from ...data._data_manager_factory import _DataManagerFactory
 from ...data._data_manager_factory import _DataManagerFactory
 from ...exceptions.exceptions import VersionIsNotProductionVersion
 from ...exceptions.exceptions import VersionIsNotProductionVersion
@@ -27,14 +27,15 @@ from .._version_manager_factory import _VersionManagerFactory
 from ._bcolor import _Bcolors
 from ._bcolor import _Bcolors
 
 
 
 
-class _VersionCLI:
+class _VersionCLI(_AbstractCLI):
     """Command-line interface of the versioning system."""
     """Command-line interface of the versioning system."""
 
 
-    __logger = _TaipyLogger._get_logger()
+    _COMMAND_NAME = "manage-versions"
+    _ARGUMENTS = ["-l", "--list", "--rename", "--compare-config", "-d", "--delete", "-dp", "--delete-production"]
 
 
     @classmethod
     @classmethod
     def create_parser(cls):
     def create_parser(cls):
-        version_parser = _CLI._add_subparser("manage-versions", help="Taipy version control system.")
+        version_parser = _TaipyParser._add_subparser(cls._COMMAND_NAME, help="Taipy version control system.")
 
 
         version_parser.add_argument(
         version_parser.add_argument(
             "-l", "--list", action="store_true", help="List all existing versions of the Taipy application."
             "-l", "--list", action="store_true", help="List all existing versions of the Taipy application."
@@ -64,10 +65,9 @@ class _VersionCLI:
         )
         )
 
 
     @classmethod
     @classmethod
-    def parse_arguments(cls):
-        args = _CLI._parse()
-
-        if getattr(args, "which", None) != "manage-versions":
+    def handle_command(cls):
+        args = cls._parse_arguments()
+        if not args:
             return
             return
 
 
         if args.list:
         if args.list:
@@ -78,13 +78,13 @@ class _VersionCLI:
             try:
             try:
                 cls.__rename_version(args.rename[0], args.rename[1])
                 cls.__rename_version(args.rename[0], args.rename[1])
             except InconsistentEnvVariableError as error:
             except InconsistentEnvVariableError as error:
-                cls.__logger.error(
+                cls._logger.error(
                     f"Fail to rename version {args.rename[0]} to {args.rename[1]} due to outdated Configuration."
                     f"Fail to rename version {args.rename[0]} to {args.rename[1]} due to outdated Configuration."
                     f"Detail: {str(error)}"
                     f"Detail: {str(error)}"
                 )
                 )
                 sys.exit(1)
                 sys.exit(1)
 
 
-            cls.__logger.info(f"Successfully renamed version '{args.rename[0]}' to '{args.rename[1]}'.")
+            cls._logger.info(f"Successfully renamed version '{args.rename[0]}' to '{args.rename[1]}'.")
             sys.exit(0)
             sys.exit(0)
 
 
         if args.compare_config:
         if args.compare_config:
@@ -94,7 +94,7 @@ class _VersionCLI:
         if args.delete_production:
         if args.delete_production:
             try:
             try:
                 _VersionManagerFactory._build_manager()._delete_production_version(args.delete_production)
                 _VersionManagerFactory._build_manager()._delete_production_version(args.delete_production)
-                cls.__logger.info(
+                cls._logger.info(
                     f"Successfully delete version {args.delete_production} from the production version list."
                     f"Successfully delete version {args.delete_production} from the production version list."
                 )
                 )
                 sys.exit(0)
                 sys.exit(0)
@@ -103,7 +103,7 @@ class _VersionCLI:
 
 
         if args.delete:
         if args.delete:
             if clean_all_entities(args.delete):
             if clean_all_entities(args.delete):
-                cls.__logger.info(f"Successfully delete version {args.delete}.")
+                cls._logger.info(f"Successfully delete version {args.delete}.")
             else:
             else:
                 sys.exit(1)
                 sys.exit(1)
 
 
@@ -154,13 +154,13 @@ class _VersionCLI:
 
 
         # Check if the new version already exists, return an error
         # Check if the new version already exists, return an error
         if _version_manager._get(new_version):
         if _version_manager._get(new_version):
-            cls.__logger.error(f"Version name '{new_version}' is already used.")
+            cls._logger.error(f"Version name '{new_version}' is already used.")
             sys.exit(1)
             sys.exit(1)
 
 
         # Make sure that all entities of the old version are exists and loadable
         # Make sure that all entities of the old version are exists and loadable
         version_entity = _version_manager._get(old_version)
         version_entity = _version_manager._get(old_version)
         if version_entity is None:
         if version_entity is None:
-            cls.__logger.error(f"Version '{old_version}' does not exist.")
+            cls._logger.error(f"Version '{old_version}' does not exist.")
             sys.exit(1)
             sys.exit(1)
 
 
         jobs = _JobManagerFactory._build_manager()._get_all(version_number=old_version)
         jobs = _JobManagerFactory._build_manager()._get_all(version_number=old_version)
@@ -208,12 +208,12 @@ class _VersionCLI:
     def __compare_version_config(cls, version_1: str, version_2: str):
     def __compare_version_config(cls, version_1: str, version_2: str):
         version_entity_1 = _VersionManagerFactory._build_manager()._get(version_1)
         version_entity_1 = _VersionManagerFactory._build_manager()._get(version_1)
         if version_entity_1 is None:
         if version_entity_1 is None:
-            cls.__logger.error(f"Version '{version_1}' does not exist.")
+            cls._logger.error(f"Version '{version_1}' does not exist.")
             sys.exit(1)
             sys.exit(1)
 
 
         version_entity_2 = _VersionManagerFactory._build_manager()._get(version_2)
         version_entity_2 = _VersionManagerFactory._build_manager()._get(version_2)
         if version_entity_2 is None:
         if version_entity_2 is None:
-            cls.__logger.error(f"Version '{version_2}' does not exist.")
+            cls._logger.error(f"Version '{version_2}' does not exist.")
             sys.exit(1)
             sys.exit(1)
 
 
         Config._comparator._compare(  # type: ignore[attr-defined]
         Config._comparator._compare(  # type: ignore[attr-defined]

+ 8 - 6
taipy/gui/_gui_cli.py

@@ -11,10 +11,11 @@
 
 
 from typing import Dict, Tuple
 from typing import Dict, Tuple
 
 
-from taipy._cli._base_cli import _CLI
+from taipy._cli._base_cli._abstract_cli import _AbstractCLI
+from taipy._cli._base_cli._taipy_parser import _TaipyParser
 
 
 
 
-class _GuiCLI:
+class _GuiCLI(_AbstractCLI):
     """Command-line interface of GUI."""
     """Command-line interface of GUI."""
 
 
     __GUI_ARGS: Dict[Tuple, Dict] = {
     __GUI_ARGS: Dict[Tuple, Dict] = {
@@ -72,7 +73,7 @@ class _GuiCLI:
 
 
     @classmethod
     @classmethod
     def create_parser(cls):
     def create_parser(cls):
-        gui_parser = _CLI._add_groupparser("Taipy GUI", "Optional arguments for Taipy GUI service")
+        gui_parser = _TaipyParser._add_groupparser("Taipy GUI", "Optional arguments for Taipy GUI service")
 
 
         for args, arg_dict in cls.__GUI_ARGS.items():
         for args, arg_dict in cls.__GUI_ARGS.items():
             taipy_arg = (args[0], cls.__add_taipy_prefix(args[0]), *args[1:])
             taipy_arg = (args[0], cls.__add_taipy_prefix(args[0]), *args[1:])
@@ -88,7 +89,7 @@ class _GuiCLI:
 
 
     @classmethod
     @classmethod
     def create_run_parser(cls):
     def create_run_parser(cls):
-        run_parser = _CLI._add_subparser("run", help="Run a Taipy application.")
+        run_parser = _TaipyParser._add_subparser("run", help="Run a Taipy application.")
         for args, arg_dict in cls.__GUI_ARGS.items():
         for args, arg_dict in cls.__GUI_ARGS.items():
             run_parser.add_argument(*args, **arg_dict)
             run_parser.add_argument(*args, **arg_dict)
 
 
@@ -101,8 +102,9 @@ class _GuiCLI:
             reloader_group.add_argument(reloader_arg, **reloader_arg_dict)
             reloader_group.add_argument(reloader_arg, **reloader_arg_dict)
 
 
     @classmethod
     @classmethod
-    def parse_arguments(cls):
-        return _CLI._parse()
+    def handle_command(cls):
+        args, _ = _TaipyParser._parser.parse_known_args()
+        return args
 
 
     @classmethod
     @classmethod
     def __add_taipy_prefix(cls, key: str):
     def __add_taipy_prefix(cls, key: str):

+ 1 - 1
taipy/gui/config.py

@@ -185,7 +185,7 @@ class _Config(object):
 
 
     def _handle_argparse(self):
     def _handle_argparse(self):
         _GuiCLI.create_parser()
         _GuiCLI.create_parser()
-        args = _GuiCLI.parse_arguments()
+        args = _GuiCLI.handle_command()
 
 
         config = self.config
         config = self.config
         if args.taipy_port:
         if args.taipy_port:

+ 21 - 0
tests/cli/conftest.py

@@ -0,0 +1,21 @@
+# Copyright 2021-2024 Avaiga Private Limited
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+#        http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+# an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations under the License.
+
+import pytest
+
+
+@pytest.fixture(scope="function", autouse=True)
+def clean_repository(clean_argparser):
+    clean_argparser()
+
+    yield
+
+    clean_argparser()

+ 12 - 41
tests/config/common/test_argparser.py → tests/cli/test_argparser.py

@@ -9,13 +9,10 @@
 # an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
 # an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
 # specific language governing permissions and limitations under the License.
 # specific language governing permissions and limitations under the License.
 
 
-import argparse
 import re
 import re
 import sys
 import sys
 
 
-import pytest
-
-from taipy._cli._base_cli import _CLI
+from taipy._cli._base_cli._taipy_parser import _TaipyParser
 
 
 if sys.version_info >= (3, 10):
 if sys.version_info >= (3, 10):
     argparse_options_str = "options:"
     argparse_options_str = "options:"
@@ -28,38 +25,12 @@ def preprocess_stdout(stdout):
     return re.sub(" +", " ", stdout)
     return re.sub(" +", " ", stdout)
 
 
 
 
-def remove_subparser(name: str):
-    """Remove a subparser from argparse."""
-    _CLI._sub_taipyparsers.pop(name, None)
-
-    if _CLI._subparser_action:
-        _CLI._subparser_action._name_parser_map.pop(name, None)
-
-        for action in _CLI._subparser_action._choices_actions:
-            if action.dest == name:
-                _CLI._subparser_action._choices_actions.remove(action)
-
-
-@pytest.fixture(autouse=True, scope="function")
-def clean_argparser():
-    _CLI._parser = argparse.ArgumentParser(conflict_handler="resolve")
-    _CLI._arg_groups = {}
-    subcommands = list(_CLI._sub_taipyparsers.keys())
-    for subcommand in subcommands:
-        remove_subparser(subcommand)
-
-    yield
-
-    _CLI._subparser_action = None
-    _CLI._sub_taipyparsers = {}
-
-
 def test_subparser(capfd):
 def test_subparser(capfd):
-    subcommand_1 = _CLI._add_subparser("subcommand_1", help="subcommand_1 help")
+    subcommand_1 = _TaipyParser._add_subparser("subcommand_1", help="subcommand_1 help")
     subcommand_1.add_argument("--foo", "-f", help="foo help")
     subcommand_1.add_argument("--foo", "-f", help="foo help")
     subcommand_1.add_argument("--bar", "-b", help="bar help")
     subcommand_1.add_argument("--bar", "-b", help="bar help")
 
 
-    subcommand_2 = _CLI._add_subparser("subcommand_2", help="subcommand_2 help")
+    subcommand_2 = _TaipyParser._add_subparser("subcommand_2", help="subcommand_2 help")
     subcommand_2.add_argument("--doo", "-d", help="doo help")
     subcommand_2.add_argument("--doo", "-d", help="doo help")
     subcommand_2.add_argument("--baz", "-z", help="baz help")
     subcommand_2.add_argument("--baz", "-z", help="baz help")
 
 
@@ -89,23 +60,23 @@ def test_subparser(capfd):
 
 
 
 
 def test_duplicate_subcommand():
 def test_duplicate_subcommand():
-    subcommand_1 = _CLI._add_subparser("subcommand_1", help="subcommand_1 help")
+    subcommand_1 = _TaipyParser._add_subparser("subcommand_1", help="subcommand_1 help")
     subcommand_1.add_argument("--foo", "-f", help="foo help")
     subcommand_1.add_argument("--foo", "-f", help="foo help")
 
 
-    subcommand_2 = _CLI._add_subparser("subcommand_1", help="subcommand_2 help")
+    subcommand_2 = _TaipyParser._add_subparser("subcommand_1", help="subcommand_2 help")
     subcommand_2.add_argument("--bar", "-b", help="bar help")
     subcommand_2.add_argument("--bar", "-b", help="bar help")
 
 
     # The title of subcommand_2 is duplicated with  subcommand_1, and therefore
     # The title of subcommand_2 is duplicated with  subcommand_1, and therefore
     # there will be no new subcommand created
     # there will be no new subcommand created
-    assert len(_CLI._sub_taipyparsers) == 1
+    assert len(_TaipyParser._sub_taipyparsers) == 1
 
 
 
 
 def test_groupparser(capfd):
 def test_groupparser(capfd):
-    group_1 = _CLI._add_groupparser("group_1", "group_1 desc")
+    group_1 = _TaipyParser._add_groupparser("group_1", "group_1 desc")
     group_1.add_argument("--foo", "-f", help="foo help")
     group_1.add_argument("--foo", "-f", help="foo help")
     group_1.add_argument("--bar", "-b", help="bar help")
     group_1.add_argument("--bar", "-b", help="bar help")
 
 
-    group_2 = _CLI._add_groupparser("group_2", "group_2 desc")
+    group_2 = _TaipyParser._add_groupparser("group_2", "group_2 desc")
     group_2.add_argument("--doo", "-d", help="doo help")
     group_2.add_argument("--doo", "-d", help="doo help")
     group_2.add_argument("--baz", "-z", help="baz help")
     group_2.add_argument("--baz", "-z", help="baz help")
 
 
@@ -123,19 +94,19 @@ group_2:
   --baz BAZ, -z BAZ  baz help
   --baz BAZ, -z BAZ  baz help
     """.strip()
     """.strip()
 
 
-    _CLI._parser.print_help()
+    _TaipyParser._parser.print_help()
     stdout, _ = capfd.readouterr()
     stdout, _ = capfd.readouterr()
 
 
     assert expected_help_message in stdout
     assert expected_help_message in stdout
 
 
 
 
 def test_duplicate_group():
 def test_duplicate_group():
-    group_1 = _CLI._add_groupparser("group_1", "group_1 desc")
+    group_1 = _TaipyParser._add_groupparser("group_1", "group_1 desc")
     group_1.add_argument("--foo", "-f", help="foo help")
     group_1.add_argument("--foo", "-f", help="foo help")
 
 
-    group_2 = _CLI._add_groupparser("group_1", "group_2 desc")
+    group_2 = _TaipyParser._add_groupparser("group_1", "group_2 desc")
     group_2.add_argument("--bar", "-b", help="bar help")
     group_2.add_argument("--bar", "-b", help="bar help")
 
 
     # The title of group_2 is duplicated with  group_1, and therefore
     # The title of group_2 is duplicated with  group_1, and therefore
     # there will be no new group created
     # there will be no new group created
-    assert len(_CLI._arg_groups) == 1
+    assert len(_TaipyParser._arg_groups) == 1

+ 1 - 30
tests/cli/test_cli.py → tests/cli/test_help_cli.py

@@ -9,13 +9,11 @@
 # an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
 # an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
 # specific language governing permissions and limitations under the License.
 # specific language governing permissions and limitations under the License.
 
 
-import argparse
 import re
 import re
 from unittest.mock import patch
 from unittest.mock import patch
 
 
 import pytest
 import pytest
 
 
-from taipy._cli._base_cli import _CLI
 from taipy._entrypoint import _entrypoint
 from taipy._entrypoint import _entrypoint
 
 
 
 
@@ -24,40 +22,13 @@ def preprocess_stdout(stdout):
     return re.sub(" +", " ", stdout)
     return re.sub(" +", " ", stdout)
 
 
 
 
-def remove_subparser(name: str):
-    """Remove a subparser from the _CLI class."""
-    _CLI._sub_taipyparsers.pop(name, None)
-
-    if _CLI._subparser_action:
-        _CLI._subparser_action._name_parser_map.pop(name, None)
-
-        for action in _CLI._subparser_action._choices_actions:
-            if action.dest == name:
-                _CLI._subparser_action._choices_actions.remove(action)
-
-
-@pytest.fixture(autouse=True, scope="function")
-def clean_argparser():
-    _CLI._parser = argparse.ArgumentParser(conflict_handler="resolve")
-    _CLI._subparser_action = None
-    _CLI._arg_groups = {}
-    subcommands = list(_CLI._sub_taipyparsers.keys())
-    for subcommand in subcommands:
-        remove_subparser(subcommand)
-
-    yield
-
-    _CLI._subparser_action = None
-    _CLI._sub_taipyparsers = {}
-
-
 expected_help = """{run,manage-versions,create,migrate,help} ...
 expected_help = """{run,manage-versions,create,migrate,help} ...
 
 
 positional arguments:
 positional arguments:
   {run,manage-versions,create,migrate,help}
   {run,manage-versions,create,migrate,help}
     run                 Run a Taipy application.
     run                 Run a Taipy application.
     manage-versions     Taipy version control system.
     manage-versions     Taipy version control system.
-    create              Create a new Taipy application.
+    create              Create a new Taipy application using pre-defined templates.
     migrate             Migrate entities created from old taipy versions to be compatible with the current taipy
     migrate             Migrate entities created from old taipy versions to be compatible with the current taipy
     version. The entity migration should be performed only after updating taipy code to the current version.
     version. The entity migration should be performed only after updating taipy code to the current version.
     help                Show the Taipy help message.
     help                Show the Taipy help message.

+ 28 - 0
tests/conftest.py

@@ -9,8 +9,11 @@
 # an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
 # an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
 # specific language governing permissions and limitations under the License.
 # specific language governing permissions and limitations under the License.
 
 
+import argparse
+
 import pytest
 import pytest
 
 
+from taipy._cli._base_cli._taipy_parser import _TaipyParser
 from taipy.config import _inject_section
 from taipy.config import _inject_section
 from taipy.config._config import _Config
 from taipy.config._config import _Config
 from taipy.config._config_comparator._config_comparator import _ConfigComparator
 from taipy.config._config_comparator._config_comparator import _ConfigComparator
@@ -36,6 +39,31 @@ def e2e_port(request):
     return request.config.getoption("--e2e-port")
     return request.config.getoption("--e2e-port")
 
 
 
 
+def remove_subparser(name: str):
+    """Remove a subparser from argparse."""
+    _TaipyParser._sub_taipyparsers.pop(name, None)
+
+    if _TaipyParser._subparser_action:
+        _TaipyParser._subparser_action._name_parser_map.pop(name, None)
+
+        for action in _TaipyParser._subparser_action._choices_actions:
+            if action.dest == name:
+                _TaipyParser._subparser_action._choices_actions.remove(action)
+
+
+@pytest.fixture
+def clean_argparser():
+    def _clean_argparser():
+        _TaipyParser._parser = argparse.ArgumentParser(conflict_handler="resolve")
+        _TaipyParser._subparser_action = None
+        _TaipyParser._arg_groups = {}
+        subcommands = list(_TaipyParser._sub_taipyparsers.keys())
+        for subcommand in subcommands:
+            remove_subparser(subcommand)
+
+    return _clean_argparser
+
+
 @pytest.fixture
 @pytest.fixture
 def reset_configuration_singleton():
 def reset_configuration_singleton():
     def _reset_configuration_singleton():
     def _reset_configuration_singleton():

+ 44 - 29
tests/core/_entity/test_migrate_cli.py

@@ -19,9 +19,24 @@ from unittest.mock import patch
 import mongomock
 import mongomock
 import pytest
 import pytest
 
 
+from taipy._entrypoint import _entrypoint
 from taipy.core._entity._migrate_cli import _MigrateCLI
 from taipy.core._entity._migrate_cli import _MigrateCLI
 
 
 
 
+def test_migrate_cli_with_wrong_repository_type_arguments(caplog):
+    with patch("sys.argv", ["prog", "migrate", "--reposiory-tyep", "filesystem"]):
+        with pytest.raises(SystemExit):
+            _entrypoint()
+        assert "Unknown arguments: --reposiory-tyep. Did you mean: --repository-type?" in caplog.text
+
+
+def test_migrate_cli_with_wrong_skip_backup_arguments(caplog):
+    with patch("sys.argv", ["prog", "migrate", "--repository-type", "filesystem", "--slip-backup"]):
+        with pytest.raises(SystemExit):
+            _entrypoint()
+        assert "Unknown arguments: --slip-backup. Did you mean: --skip-backup?" in caplog.text
+
+
 @pytest.fixture(scope="function", autouse=True)
 @pytest.fixture(scope="function", autouse=True)
 def clean_data_folder():
 def clean_data_folder():
     if os.path.exists("tests/core/_entity/.data"):
     if os.path.exists("tests/core/_entity/.data"):
@@ -37,7 +52,7 @@ def test_migrate_fs_default(caplog):
     # Test migrate with default .data folder
     # Test migrate with default .data folder
     with pytest.raises(SystemExit):
     with pytest.raises(SystemExit):
         with patch("sys.argv", ["prog", "migrate", "--repository-type", "filesystem", "--skip-backup"]):
         with patch("sys.argv", ["prog", "migrate", "--repository-type", "filesystem", "--skip-backup"]):
-            _MigrateCLI.parse_arguments()
+            _MigrateCLI.handle_command()
     assert "Starting entity migration from '.taipy/' folder" in caplog.text
     assert "Starting entity migration from '.taipy/' folder" in caplog.text
 
 
 
 
@@ -53,7 +68,7 @@ def test_migrate_fs_specified_folder(caplog, mocker):
     # Run with --skip-backup to only test the migration
     # Run with --skip-backup to only test the migration
     with pytest.raises(SystemExit):
     with pytest.raises(SystemExit):
         with patch("sys.argv", ["prog", "migrate", "--repository-type", "filesystem", data_path, "--skip-backup"]):
         with patch("sys.argv", ["prog", "migrate", "--repository-type", "filesystem", data_path, "--skip-backup"]):
-            _MigrateCLI.parse_arguments()
+            _MigrateCLI.handle_command()
     assert f"Starting entity migration from '{data_path}' folder" in caplog.text
     assert f"Starting entity migration from '{data_path}' folder" in caplog.text
 
 
     # Compare migrated .data folder with data_sample_migrated
     # Compare migrated .data folder with data_sample_migrated
@@ -77,7 +92,7 @@ def test_migrate_fs_backup_and_remove(caplog, mocker):
     # Remove backup when it does not exist should raise an error
     # Remove backup when it does not exist should raise an error
     with pytest.raises(SystemExit) as err:
     with pytest.raises(SystemExit) as err:
         with patch("sys.argv", ["prog", "migrate", "--repository-type", "filesystem", data_path, "--remove-backup"]):
         with patch("sys.argv", ["prog", "migrate", "--repository-type", "filesystem", data_path, "--remove-backup"]):
-            _MigrateCLI.parse_arguments()
+            _MigrateCLI.handle_command()
     assert err.value.code == 1
     assert err.value.code == 1
     assert f"The backup folder '{backup_path}' does not exist." in caplog.text
     assert f"The backup folder '{backup_path}' does not exist." in caplog.text
     assert not os.path.exists(backup_path)
     assert not os.path.exists(backup_path)
@@ -85,7 +100,7 @@ def test_migrate_fs_backup_and_remove(caplog, mocker):
     # Run without --skip-backup to create the backup folder
     # Run without --skip-backup to create the backup folder
     with pytest.raises(SystemExit):
     with pytest.raises(SystemExit):
         with patch("sys.argv", ["prog", "migrate", "--repository-type", "filesystem", data_path]):
         with patch("sys.argv", ["prog", "migrate", "--repository-type", "filesystem", data_path]):
-            _MigrateCLI.parse_arguments()
+            _MigrateCLI.handle_command()
     assert f"Backed up entities from '{data_path}' to '{backup_path}' folder before migration." in caplog.text
     assert f"Backed up entities from '{data_path}' to '{backup_path}' folder before migration." in caplog.text
 
 
     assert os.path.exists(backup_path)
     assert os.path.exists(backup_path)
@@ -93,7 +108,7 @@ def test_migrate_fs_backup_and_remove(caplog, mocker):
     # Remove backup
     # Remove backup
     with pytest.raises(SystemExit):
     with pytest.raises(SystemExit):
         with patch("sys.argv", ["prog", "migrate", "--repository-type", "filesystem", data_path, "--remove-backup"]):
         with patch("sys.argv", ["prog", "migrate", "--repository-type", "filesystem", data_path, "--remove-backup"]):
-            _MigrateCLI.parse_arguments()
+            _MigrateCLI.handle_command()
     assert f"Removed backup entities from the backup folder '{backup_path}'." in caplog.text
     assert f"Removed backup entities from the backup folder '{backup_path}'." in caplog.text
     assert not os.path.exists(backup_path)
     assert not os.path.exists(backup_path)
 
 
@@ -111,7 +126,7 @@ def test_migrate_fs_backup_and_restore(caplog, mocker):
     # Restore backup when it does not exist should raise an error
     # Restore backup when it does not exist should raise an error
     with pytest.raises(SystemExit) as err:
     with pytest.raises(SystemExit) as err:
         with patch("sys.argv", ["prog", "migrate", "--repository-type", "filesystem", data_path, "--restore"]):
         with patch("sys.argv", ["prog", "migrate", "--repository-type", "filesystem", data_path, "--restore"]):
-            _MigrateCLI.parse_arguments()
+            _MigrateCLI.handle_command()
     assert err.value.code == 1
     assert err.value.code == 1
     assert f"The backup folder '{backup_path}' does not exist." in caplog.text
     assert f"The backup folder '{backup_path}' does not exist." in caplog.text
     assert not os.path.exists(backup_path)
     assert not os.path.exists(backup_path)
@@ -119,14 +134,14 @@ def test_migrate_fs_backup_and_restore(caplog, mocker):
     # Run without --skip-backup to create the backup folder
     # Run without --skip-backup to create the backup folder
     with pytest.raises(SystemExit):
     with pytest.raises(SystemExit):
         with patch("sys.argv", ["prog", "migrate", "--repository-type", "filesystem", data_path]):
         with patch("sys.argv", ["prog", "migrate", "--repository-type", "filesystem", data_path]):
-            _MigrateCLI.parse_arguments()
+            _MigrateCLI.handle_command()
 
 
     assert os.path.exists(backup_path)
     assert os.path.exists(backup_path)
 
 
     # restore the backup
     # restore the backup
     with pytest.raises(SystemExit):
     with pytest.raises(SystemExit):
         with patch("sys.argv", ["prog", "migrate", "--repository-type", "filesystem", data_path, "--restore"]):
         with patch("sys.argv", ["prog", "migrate", "--repository-type", "filesystem", data_path, "--restore"]):
-            _MigrateCLI.parse_arguments()
+            _MigrateCLI.handle_command()
     assert f"Restored entities from the backup folder '{backup_path}' to '{data_path}'." in caplog.text
     assert f"Restored entities from the backup folder '{backup_path}' to '{data_path}'." in caplog.text
     assert not os.path.exists(backup_path)
     assert not os.path.exists(backup_path)
 
 
@@ -143,7 +158,7 @@ def test_migrate_fs_non_existing_folder(caplog):
     # Test migrate with a non-existing folder
     # Test migrate with a non-existing folder
     with pytest.raises(SystemExit) as err:
     with pytest.raises(SystemExit) as err:
         with patch("sys.argv", ["prog", "migrate", "--repository-type", "filesystem", "non-existing-folder"]):
         with patch("sys.argv", ["prog", "migrate", "--repository-type", "filesystem", "non-existing-folder"]):
-            _MigrateCLI.parse_arguments()
+            _MigrateCLI.handle_command()
     assert err.value.code == 1
     assert err.value.code == 1
     assert "Folder 'non-existing-folder' does not exist." in caplog.text
     assert "Folder 'non-existing-folder' does not exist." in caplog.text
 
 
@@ -155,7 +170,7 @@ def test_migrate_sql_specified_path(_migrate_sql_entities_mock, tmp_sqlite):
     # Test the _migrate_sql_entities is called once with the correct path
     # Test the _migrate_sql_entities is called once with the correct path
     with pytest.raises(SystemExit):
     with pytest.raises(SystemExit):
         with patch("sys.argv", ["prog", "migrate", "--repository-type", "sql", tmp_sqlite, "--skip-backup"]):
         with patch("sys.argv", ["prog", "migrate", "--repository-type", "sql", tmp_sqlite, "--skip-backup"]):
-            _MigrateCLI.parse_arguments()
+            _MigrateCLI.handle_command()
             assert _migrate_sql_entities_mock.assert_called_once_with(path=tmp_sqlite)
             assert _migrate_sql_entities_mock.assert_called_once_with(path=tmp_sqlite)
 
 
 
 
@@ -172,7 +187,7 @@ def test_migrate_sql_backup_and_remove(caplog, tmp_sqlite):
     # Remove backup when it does not exist should raise an error
     # Remove backup when it does not exist should raise an error
     with pytest.raises(SystemExit) as err:
     with pytest.raises(SystemExit) as err:
         with patch("sys.argv", ["prog", "migrate", "--repository-type", "sql", tmp_sqlite, "--remove-backup"]):
         with patch("sys.argv", ["prog", "migrate", "--repository-type", "sql", tmp_sqlite, "--remove-backup"]):
-            _MigrateCLI.parse_arguments()
+            _MigrateCLI.handle_command()
     assert err.value.code == 1
     assert err.value.code == 1
     assert f"The backup database '{backup_sqlite}' does not exist." in caplog.text
     assert f"The backup database '{backup_sqlite}' does not exist." in caplog.text
     assert not os.path.exists(backup_sqlite)
     assert not os.path.exists(backup_sqlite)
@@ -180,14 +195,14 @@ def test_migrate_sql_backup_and_remove(caplog, tmp_sqlite):
     # Run without --skip-backup to create the backup database
     # Run without --skip-backup to create the backup database
     with pytest.raises((SystemExit, OperationalError)):
     with pytest.raises((SystemExit, OperationalError)):
         with patch("sys.argv", ["prog", "migrate", "--repository-type", "sql", tmp_sqlite]):
         with patch("sys.argv", ["prog", "migrate", "--repository-type", "sql", tmp_sqlite]):
-            _MigrateCLI.parse_arguments()
+            _MigrateCLI.handle_command()
 
 
     assert os.path.exists(backup_sqlite)
     assert os.path.exists(backup_sqlite)
 
 
     # Remove backup
     # Remove backup
     with pytest.raises(SystemExit):
     with pytest.raises(SystemExit):
         with patch("sys.argv", ["prog", "migrate", "--repository-type", "sql", tmp_sqlite, "--remove-backup"]):
         with patch("sys.argv", ["prog", "migrate", "--repository-type", "sql", tmp_sqlite, "--remove-backup"]):
-            _MigrateCLI.parse_arguments()
+            _MigrateCLI.handle_command()
     assert f"Removed backup entities from the backup database '{backup_sqlite}'." in caplog.text
     assert f"Removed backup entities from the backup database '{backup_sqlite}'." in caplog.text
     assert not os.path.exists(backup_sqlite)
     assert not os.path.exists(backup_sqlite)
 
 
@@ -206,7 +221,7 @@ def test_migrate_sql_backup_and_restore(caplog, tmp_sqlite):
     # Restore backup when it does not exist should raise an error
     # Restore backup when it does not exist should raise an error
     with pytest.raises(SystemExit) as err:
     with pytest.raises(SystemExit) as err:
         with patch("sys.argv", ["prog", "migrate", "--repository-type", "sql", tmp_sqlite, "--restore"]):
         with patch("sys.argv", ["prog", "migrate", "--repository-type", "sql", tmp_sqlite, "--restore"]):
-            _MigrateCLI.parse_arguments()
+            _MigrateCLI.handle_command()
     assert err.value.code == 1
     assert err.value.code == 1
     assert f"The backup database '{backup_sqlite}' does not exist." in caplog.text
     assert f"The backup database '{backup_sqlite}' does not exist." in caplog.text
     assert not os.path.exists(backup_sqlite)
     assert not os.path.exists(backup_sqlite)
@@ -214,14 +229,14 @@ def test_migrate_sql_backup_and_restore(caplog, tmp_sqlite):
     # Run without --skip-backup to create the backup database
     # Run without --skip-backup to create the backup database
     with pytest.raises((SystemExit, OperationalError)):
     with pytest.raises((SystemExit, OperationalError)):
         with patch("sys.argv", ["prog", "migrate", "--repository-type", "sql", tmp_sqlite]):
         with patch("sys.argv", ["prog", "migrate", "--repository-type", "sql", tmp_sqlite]):
-            _MigrateCLI.parse_arguments()
+            _MigrateCLI.handle_command()
 
 
     assert os.path.exists(backup_sqlite)
     assert os.path.exists(backup_sqlite)
 
 
     # Restore the backup
     # Restore the backup
     with pytest.raises(SystemExit):
     with pytest.raises(SystemExit):
         with patch("sys.argv", ["prog", "migrate", "--repository-type", "sql", tmp_sqlite, "--restore"]):
         with patch("sys.argv", ["prog", "migrate", "--repository-type", "sql", tmp_sqlite, "--restore"]):
-            _MigrateCLI.parse_arguments()
+            _MigrateCLI.handle_command()
     assert f"Restored entities from the backup database '{backup_sqlite}' to '{tmp_sqlite}'." in caplog.text
     assert f"Restored entities from the backup database '{backup_sqlite}' to '{tmp_sqlite}'." in caplog.text
     assert not os.path.exists(backup_sqlite)
     assert not os.path.exists(backup_sqlite)
 
 
@@ -232,7 +247,7 @@ def test_migrate_sql_non_existing_path(caplog):
     # Test migrate without providing a path
     # Test migrate without providing a path
     with pytest.raises(SystemExit) as err:
     with pytest.raises(SystemExit) as err:
         with patch("sys.argv", ["prog", "migrate", "--repository-type", "sql"]):
         with patch("sys.argv", ["prog", "migrate", "--repository-type", "sql"]):
-            _MigrateCLI.parse_arguments()
+            _MigrateCLI.handle_command()
 
 
     assert err.value.code == 1
     assert err.value.code == 1
     assert "Missing the required sqlite path." in caplog.text
     assert "Missing the required sqlite path." in caplog.text
@@ -242,7 +257,7 @@ def test_migrate_sql_non_existing_path(caplog):
     # Test migrate with a non-existing-path.sqlite file
     # Test migrate with a non-existing-path.sqlite file
     with pytest.raises(SystemExit) as err:
     with pytest.raises(SystemExit) as err:
         with patch("sys.argv", ["prog", "migrate", "--repository-type", "sql", "non-existing-path.sqlite"]):
         with patch("sys.argv", ["prog", "migrate", "--repository-type", "sql", "non-existing-path.sqlite"]):
-            _MigrateCLI.parse_arguments()
+            _MigrateCLI.handle_command()
     assert err.value.code == 1
     assert err.value.code == 1
     assert "File 'non-existing-path.sqlite' does not exist." in caplog.text
     assert "File 'non-existing-path.sqlite' does not exist." in caplog.text
 
 
@@ -253,12 +268,12 @@ def test_call_to_migrate_mongo(_migrate_mongo_entities_mock):
 
 
     with pytest.raises(SystemExit):
     with pytest.raises(SystemExit):
         with patch("sys.argv", ["prog", "migrate", "--repository-type", "mongo"]):
         with patch("sys.argv", ["prog", "migrate", "--repository-type", "mongo"]):
-            _MigrateCLI.parse_arguments()
+            _MigrateCLI.handle_command()
             assert _migrate_mongo_entities_mock.assert_called_once_with()
             assert _migrate_mongo_entities_mock.assert_called_once_with()
 
 
     with pytest.raises(SystemExit):
     with pytest.raises(SystemExit):
         with patch("sys.argv", ["prog", "migrate", "--repository-type", "mongo", "host", "port", "user", "password"]):
         with patch("sys.argv", ["prog", "migrate", "--repository-type", "mongo", "host", "port", "user", "password"]):
-            _MigrateCLI.parse_arguments()
+            _MigrateCLI.handle_command()
             assert _migrate_mongo_entities_mock.assert_called_once_with("host", "port", "user", "password")
             assert _migrate_mongo_entities_mock.assert_called_once_with("host", "port", "user", "password")
 
 
 
 
@@ -271,7 +286,7 @@ def test_migrate_mongo_backup_and_remove(caplog):
     # Remove backup when it does not exist should raise an error
     # Remove backup when it does not exist should raise an error
     with pytest.raises(SystemExit) as err:
     with pytest.raises(SystemExit) as err:
         with patch("sys.argv", ["prog", "migrate", "--repository-type", "mongo", "--remove-backup"]):
         with patch("sys.argv", ["prog", "migrate", "--repository-type", "mongo", "--remove-backup"]):
-            _MigrateCLI.parse_arguments()
+            _MigrateCLI.handle_command()
     assert err.value.code == 1
     assert err.value.code == 1
     assert f"The backup folder '{mongo_backup_path}' does not exist." in caplog.text
     assert f"The backup folder '{mongo_backup_path}' does not exist." in caplog.text
     assert not os.path.exists(mongo_backup_path)
     assert not os.path.exists(mongo_backup_path)
@@ -279,14 +294,14 @@ def test_migrate_mongo_backup_and_remove(caplog):
     # Run without --skip-backup to create the backup database
     # Run without --skip-backup to create the backup database
     with pytest.raises(SystemExit):
     with pytest.raises(SystemExit):
         with patch("sys.argv", ["prog", "migrate", "--repository-type", "mongo"]):
         with patch("sys.argv", ["prog", "migrate", "--repository-type", "mongo"]):
-            _MigrateCLI.parse_arguments()
+            _MigrateCLI.handle_command()
 
 
     assert os.path.exists(mongo_backup_path)
     assert os.path.exists(mongo_backup_path)
 
 
     # Remove backup
     # Remove backup
     with pytest.raises(SystemExit):
     with pytest.raises(SystemExit):
         with patch("sys.argv", ["prog", "migrate", "--repository-type", "mongo", "--remove-backup"]):
         with patch("sys.argv", ["prog", "migrate", "--repository-type", "mongo", "--remove-backup"]):
-            _MigrateCLI.parse_arguments()
+            _MigrateCLI.handle_command()
     assert f"Removed backup entities from the backup folder '{mongo_backup_path}'." in caplog.text
     assert f"Removed backup entities from the backup folder '{mongo_backup_path}'." in caplog.text
     assert not os.path.exists(mongo_backup_path)
     assert not os.path.exists(mongo_backup_path)
 
 
@@ -300,7 +315,7 @@ def test_migrate_mongo_backup_and_restore(caplog):
     # Restore backup when it does not exist should raise an error
     # Restore backup when it does not exist should raise an error
     with pytest.raises(SystemExit) as err:
     with pytest.raises(SystemExit) as err:
         with patch("sys.argv", ["prog", "migrate", "--repository-type", "mongo", "--restore"]):
         with patch("sys.argv", ["prog", "migrate", "--repository-type", "mongo", "--restore"]):
-            _MigrateCLI.parse_arguments()
+            _MigrateCLI.handle_command()
     assert err.value.code == 1
     assert err.value.code == 1
     assert f"The backup folder '{mongo_backup_path}' does not exist." in caplog.text
     assert f"The backup folder '{mongo_backup_path}' does not exist." in caplog.text
     assert not os.path.exists(mongo_backup_path)
     assert not os.path.exists(mongo_backup_path)
@@ -308,14 +323,14 @@ def test_migrate_mongo_backup_and_restore(caplog):
     # Run without --skip-backup to create the backup database
     # Run without --skip-backup to create the backup database
     with pytest.raises(SystemExit):
     with pytest.raises(SystemExit):
         with patch("sys.argv", ["prog", "migrate", "--repository-type", "mongo"]):
         with patch("sys.argv", ["prog", "migrate", "--repository-type", "mongo"]):
-            _MigrateCLI.parse_arguments()
+            _MigrateCLI.handle_command()
 
 
     assert os.path.exists(mongo_backup_path)
     assert os.path.exists(mongo_backup_path)
 
 
     # Restore the backup
     # Restore the backup
     with pytest.raises(SystemExit):
     with pytest.raises(SystemExit):
         with patch("sys.argv", ["prog", "migrate", "--repository-type", "mongo", "--restore"]):
         with patch("sys.argv", ["prog", "migrate", "--repository-type", "mongo", "--restore"]):
-            _MigrateCLI.parse_arguments()
+            _MigrateCLI.handle_command()
     assert f"Restored entities from the backup folder '{mongo_backup_path}'." in caplog.text
     assert f"Restored entities from the backup folder '{mongo_backup_path}'." in caplog.text
     assert not os.path.exists(mongo_backup_path)
     assert not os.path.exists(mongo_backup_path)
 
 
@@ -325,15 +340,15 @@ def test_not_provide_valid_repository_type(caplog):
 
 
     with pytest.raises(SystemExit):
     with pytest.raises(SystemExit):
         with patch("sys.argv", ["prog", "migrate"]):
         with patch("sys.argv", ["prog", "migrate"]):
-            _MigrateCLI.parse_arguments()
+            _MigrateCLI.handle_command()
             assert "the following arguments are required: --repository-type" in caplog.text
             assert "the following arguments are required: --repository-type" in caplog.text
 
 
     with pytest.raises(SystemExit):
     with pytest.raises(SystemExit):
         with patch("sys.argv", ["prog", "migrate", "--repository-type"]):
         with patch("sys.argv", ["prog", "migrate", "--repository-type"]):
-            _MigrateCLI.parse_arguments()
+            _MigrateCLI.handle_command()
             assert "argument --repository-type: expected at least one argument" in caplog.text
             assert "argument --repository-type: expected at least one argument" in caplog.text
 
 
     with pytest.raises(SystemExit):
     with pytest.raises(SystemExit):
         with patch("sys.argv", ["prog", "migrate", "--repository-type", "invalid-repository-type"]):
         with patch("sys.argv", ["prog", "migrate", "--repository-type", "invalid-repository-type"]):
-            _MigrateCLI.parse_arguments()
+            _MigrateCLI.handle_command()
             assert "Unknown repository type invalid-repository-type" in caplog.text
             assert "Unknown repository type invalid-repository-type" in caplog.text

+ 3 - 1
tests/core/conftest.py

@@ -315,7 +315,8 @@ def tmp_sqlite(tmpdir_factory):
 
 
 
 
 @pytest.fixture(scope="function", autouse=True)
 @pytest.fixture(scope="function", autouse=True)
-def clean_repository(init_config, init_managers, init_orchestrator, init_notifier):
+def clean_repository(init_config, init_managers, init_orchestrator, init_notifier, clean_argparser):
+    clean_argparser()
     close_all_sessions()
     close_all_sessions()
     init_config()
     init_config()
     init_orchestrator()
     init_orchestrator()
@@ -326,6 +327,7 @@ def clean_repository(init_config, init_managers, init_orchestrator, init_notifie
     with patch("sys.argv", ["prog"]):
     with patch("sys.argv", ["prog"]):
         yield
         yield
 
 
+    clean_argparser()
     close_all_sessions()
     close_all_sessions()
     init_orchestrator()
     init_orchestrator()
     init_managers()
     init_managers()

+ 21 - 13
tests/core/version/test_version_cli.py

@@ -14,6 +14,7 @@ from unittest.mock import patch
 
 
 import pytest
 import pytest
 
 
+from taipy._entrypoint import _entrypoint
 from taipy.config.common.frequency import Frequency
 from taipy.config.common.frequency import Frequency
 from taipy.config.common.scope import Scope
 from taipy.config.common.scope import Scope
 from taipy.config.config import Config
 from taipy.config.config import Config
@@ -27,6 +28,13 @@ from taipy.core.sequence._sequence_manager import _SequenceManager
 from taipy.core.task._task_manager import _TaskManager
 from taipy.core.task._task_manager import _TaskManager
 
 
 
 
+def test_version_cli_with_wrong_arguments(caplog):
+    with patch("sys.argv", ["prog", "manage-versions", "--lits"]):
+        with pytest.raises(SystemExit):
+            _entrypoint()
+        assert "Unknown arguments: --lits. Did you mean: --list?" in caplog.text
+
+
 def test_delete_version(caplog):
 def test_delete_version(caplog):
     scenario_config = config_scenario()
     scenario_config = config_scenario()
 
 
@@ -87,7 +95,7 @@ def test_delete_version(caplog):
     _VersionCLI.create_parser()
     _VersionCLI.create_parser()
     with pytest.raises(SystemExit):
     with pytest.raises(SystemExit):
         with patch("sys.argv", ["prog", "manage-versions", "--delete", "1.0"]):
         with patch("sys.argv", ["prog", "manage-versions", "--delete", "1.0"]):
-            _VersionCLI.parse_arguments()
+            _VersionCLI.handle_command()
 
 
     assert "Successfully delete version 1.0." in caplog.text
     assert "Successfully delete version 1.0." in caplog.text
     all_versions = [version.id for version in _VersionManager._get_all()]
     all_versions = [version.id for version in _VersionManager._get_all()]
@@ -97,13 +105,13 @@ def test_delete_version(caplog):
     # Test delete a non-existed version
     # Test delete a non-existed version
     with pytest.raises(SystemExit):
     with pytest.raises(SystemExit):
         with patch("sys.argv", ["prog", "manage-versions", "--delete", "non_exist_version"]):
         with patch("sys.argv", ["prog", "manage-versions", "--delete", "non_exist_version"]):
-            _VersionCLI.parse_arguments()
+            _VersionCLI.handle_command()
     assert "Version 'non_exist_version' does not exist." in caplog.text
     assert "Version 'non_exist_version' does not exist." in caplog.text
 
 
     # Test delete production version will change the version from production to experiment
     # Test delete production version will change the version from production to experiment
     with pytest.raises(SystemExit):
     with pytest.raises(SystemExit):
         with patch("sys.argv", ["prog", "manage-versions", "--delete-production", "1.1"]):
         with patch("sys.argv", ["prog", "manage-versions", "--delete-production", "1.1"]):
-            _VersionCLI.parse_arguments()
+            _VersionCLI.handle_command()
 
 
     assert "Successfully delete version 1.1 from the production version list." in caplog.text
     assert "Successfully delete version 1.1 from the production version list." in caplog.text
     all_versions = [version.id for version in _VersionManager._get_all()]
     all_versions = [version.id for version in _VersionManager._get_all()]
@@ -114,7 +122,7 @@ def test_delete_version(caplog):
     # Test delete a non-existed production version
     # Test delete a non-existed production version
     with pytest.raises(SystemExit) as e:
     with pytest.raises(SystemExit) as e:
         with patch("sys.argv", ["prog", "manage-versions", "--delete-production", "non_exist_version"]):
         with patch("sys.argv", ["prog", "manage-versions", "--delete-production", "non_exist_version"]):
-            _VersionCLI.parse_arguments()
+            _VersionCLI.handle_command()
 
 
     assert str(e.value) == "Version 'non_exist_version' is not a production version."
     assert str(e.value) == "Version 'non_exist_version' is not a production version."
 
 
@@ -158,7 +166,7 @@ def test_list_versions(capsys):
     _VersionCLI.create_parser()
     _VersionCLI.create_parser()
     with pytest.raises(SystemExit):
     with pytest.raises(SystemExit):
         with patch("sys.argv", ["prog", "manage-versions", "--list"]):
         with patch("sys.argv", ["prog", "manage-versions", "--list"]):
-            _VersionCLI.parse_arguments()
+            _VersionCLI.handle_command()
 
 
     out, _ = capsys.readouterr()
     out, _ = capsys.readouterr()
     version_list = str(out).strip().split("\n")
     version_list = str(out).strip().split("\n")
@@ -194,20 +202,20 @@ def test_rename_version(caplog):
     with pytest.raises(SystemExit):
     with pytest.raises(SystemExit):
         with patch("sys.argv", ["prog", "manage-versions", "--rename", "non_exist_version", "1.1"]):
         with patch("sys.argv", ["prog", "manage-versions", "--rename", "non_exist_version", "1.1"]):
             # This should raise an exception since version "non_exist_version" does not exist
             # This should raise an exception since version "non_exist_version" does not exist
-            _VersionCLI.parse_arguments()
+            _VersionCLI.handle_command()
     assert "Version 'non_exist_version' does not exist." in caplog.text
     assert "Version 'non_exist_version' does not exist." in caplog.text
 
 
     _VersionCLI.create_parser()
     _VersionCLI.create_parser()
     with pytest.raises(SystemExit):
     with pytest.raises(SystemExit):
         with patch("sys.argv", ["prog", "manage-versions", "--rename", "1.0", "2.0"]):
         with patch("sys.argv", ["prog", "manage-versions", "--rename", "1.0", "2.0"]):
             # This should raise an exception since 2.0 already exists
             # This should raise an exception since 2.0 already exists
-            _VersionCLI.parse_arguments()
+            _VersionCLI.handle_command()
     assert "Version name '2.0' is already used." in caplog.text
     assert "Version name '2.0' is already used." in caplog.text
 
 
     _VersionCLI.create_parser()
     _VersionCLI.create_parser()
     with pytest.raises(SystemExit):
     with pytest.raises(SystemExit):
         with patch("sys.argv", ["prog", "manage-versions", "--rename", "1.0", "1.1"]):
         with patch("sys.argv", ["prog", "manage-versions", "--rename", "1.0", "1.1"]):
-            _VersionCLI.parse_arguments()
+            _VersionCLI.handle_command()
     assert _VersionManager._get("1.0") is None
     assert _VersionManager._get("1.0") is None
     assert [version.id for version in _VersionManager._get_all()].sort() == [dev_ver, "1.1", "2.0"].sort()
     assert [version.id for version in _VersionManager._get_all()].sort() == [dev_ver, "1.1", "2.0"].sort()
     # All entities are assigned to the new version
     # All entities are assigned to the new version
@@ -220,7 +228,7 @@ def test_rename_version(caplog):
     _VersionCLI.create_parser()
     _VersionCLI.create_parser()
     with pytest.raises(SystemExit):
     with pytest.raises(SystemExit):
         with patch("sys.argv", ["prog", "manage-versions", "--rename", "2.0", "2.1"]):
         with patch("sys.argv", ["prog", "manage-versions", "--rename", "2.0", "2.1"]):
-            _VersionCLI.parse_arguments()
+            _VersionCLI.handle_command()
     assert _VersionManager._get("2.0") is None
     assert _VersionManager._get("2.0") is None
     assert [version.id for version in _VersionManager._get_all()].sort() == [dev_ver, "1.1", "2.1"].sort()
     assert [version.id for version in _VersionManager._get_all()].sort() == [dev_ver, "1.1", "2.1"].sort()
     assert _VersionManager._get_production_versions() == ["2.1"]
     assert _VersionManager._get_production_versions() == ["2.1"]
@@ -258,23 +266,23 @@ def test_compare_version_config(caplog, init_config):
     with pytest.raises(SystemExit):
     with pytest.raises(SystemExit):
         with patch("sys.argv", ["prog", "manage-versions", "--compare-config", "non_exist_version", "2.0"]):
         with patch("sys.argv", ["prog", "manage-versions", "--compare-config", "non_exist_version", "2.0"]):
             # This should raise an exception since version "non_exist_version" does not exist
             # This should raise an exception since version "non_exist_version" does not exist
-            _VersionCLI.parse_arguments()
+            _VersionCLI.handle_command()
     assert "Version 'non_exist_version' does not exist." in caplog.text
     assert "Version 'non_exist_version' does not exist." in caplog.text
 
 
     with pytest.raises(SystemExit):
     with pytest.raises(SystemExit):
         with patch("sys.argv", ["prog", "manage-versions", "--compare-config", "1.0", "non_exist_version"]):
         with patch("sys.argv", ["prog", "manage-versions", "--compare-config", "1.0", "non_exist_version"]):
             # This should raise an exception since 2.0 already exists
             # This should raise an exception since 2.0 already exists
-            _VersionCLI.parse_arguments()
+            _VersionCLI.handle_command()
     assert "Version 'non_exist_version' does not exist." in caplog.text
     assert "Version 'non_exist_version' does not exist." in caplog.text
 
 
     with pytest.raises(SystemExit):
     with pytest.raises(SystemExit):
         with patch("sys.argv", ["prog", "manage-versions", "--compare-config", "1.0", "1.0"]):
         with patch("sys.argv", ["prog", "manage-versions", "--compare-config", "1.0", "1.0"]):
-            _VersionCLI.parse_arguments()
+            _VersionCLI.handle_command()
     assert "There is no difference between version 1.0 Configuration and version 1.0 Configuration." in caplog.text
     assert "There is no difference between version 1.0 Configuration and version 1.0 Configuration." in caplog.text
 
 
     with pytest.raises(SystemExit):
     with pytest.raises(SystemExit):
         with patch("sys.argv", ["prog", "manage-versions", "--compare-config", "1.0", "2.0"]):
         with patch("sys.argv", ["prog", "manage-versions", "--compare-config", "1.0", "2.0"]):
-            _VersionCLI.parse_arguments()
+            _VersionCLI.handle_command()
     expected_message = """Differences between version 1.0 Configuration and version 2.0 Configuration:
     expected_message = """Differences between version 1.0 Configuration and version 2.0 Configuration:
 \tDATA_NODE "d2" has attribute "default_path" modified: foo.csv -> bar.csv"""
 \tDATA_NODE "d2" has attribute "default_path" modified: foo.csv -> bar.csv"""
     assert expected_message in caplog.text
     assert expected_message in caplog.text

+ 13 - 13
tests/core/version/test_version_cli_with_sql_repo.py

@@ -90,7 +90,7 @@ def test_delete_version(caplog, init_sql_repo):
     _VersionCLI.create_parser()
     _VersionCLI.create_parser()
     with pytest.raises(SystemExit):
     with pytest.raises(SystemExit):
         with patch("sys.argv", ["prog", "manage-versions", "--delete", "1.0"]):
         with patch("sys.argv", ["prog", "manage-versions", "--delete", "1.0"]):
-            _VersionCLI.parse_arguments()
+            _VersionCLI.handle_command()
 
 
     assert "Successfully delete version 1.0." in caplog.text
     assert "Successfully delete version 1.0." in caplog.text
     all_versions = [version.id for version in _VersionManager._get_all()]
     all_versions = [version.id for version in _VersionManager._get_all()]
@@ -100,13 +100,13 @@ def test_delete_version(caplog, init_sql_repo):
     # Test delete a non-existed version
     # Test delete a non-existed version
     with pytest.raises(SystemExit):
     with pytest.raises(SystemExit):
         with patch("sys.argv", ["prog", "manage-versions", "--delete", "non_exist_version"]):
         with patch("sys.argv", ["prog", "manage-versions", "--delete", "non_exist_version"]):
-            _VersionCLI.parse_arguments()
+            _VersionCLI.handle_command()
     assert "Version 'non_exist_version' does not exist." in caplog.text
     assert "Version 'non_exist_version' does not exist." in caplog.text
 
 
     # Test delete production version will change the version from production to experiment
     # Test delete production version will change the version from production to experiment
     with pytest.raises(SystemExit):
     with pytest.raises(SystemExit):
         with patch("sys.argv", ["prog", "manage-versions", "--delete-production", "1.1"]):
         with patch("sys.argv", ["prog", "manage-versions", "--delete-production", "1.1"]):
-            _VersionCLI.parse_arguments()
+            _VersionCLI.handle_command()
 
 
     assert "Successfully delete version 1.1 from the production version list." in caplog.text
     assert "Successfully delete version 1.1 from the production version list." in caplog.text
     all_versions = [version.id for version in _VersionManager._get_all()]
     all_versions = [version.id for version in _VersionManager._get_all()]
@@ -117,7 +117,7 @@ def test_delete_version(caplog, init_sql_repo):
     # Test delete a non-existed production version
     # Test delete a non-existed production version
     with pytest.raises(SystemExit) as e:
     with pytest.raises(SystemExit) as e:
         with patch("sys.argv", ["prog", "manage-versions", "--delete-production", "non_exist_version"]):
         with patch("sys.argv", ["prog", "manage-versions", "--delete-production", "non_exist_version"]):
-            _VersionCLI.parse_arguments()
+            _VersionCLI.handle_command()
 
 
     assert str(e.value) == "Version 'non_exist_version' is not a production version."
     assert str(e.value) == "Version 'non_exist_version' is not a production version."
 
 
@@ -163,7 +163,7 @@ def test_list_versions(capsys, init_sql_repo):
     _VersionCLI.create_parser()
     _VersionCLI.create_parser()
     with pytest.raises(SystemExit):
     with pytest.raises(SystemExit):
         with patch("sys.argv", ["prog", "manage-versions", "--list"]):
         with patch("sys.argv", ["prog", "manage-versions", "--list"]):
-            _VersionCLI.parse_arguments()
+            _VersionCLI.handle_command()
 
 
     out, _ = capsys.readouterr()
     out, _ = capsys.readouterr()
     version_list = str(out).strip().split("\n")
     version_list = str(out).strip().split("\n")
@@ -201,20 +201,20 @@ def test_rename_version(caplog, init_sql_repo):
     with pytest.raises(SystemExit):
     with pytest.raises(SystemExit):
         with patch("sys.argv", ["prog", "manage-versions", "--rename", "non_exist_version", "1.1"]):
         with patch("sys.argv", ["prog", "manage-versions", "--rename", "non_exist_version", "1.1"]):
             # This should raise an exception since version "non_exist_version" does not exist
             # This should raise an exception since version "non_exist_version" does not exist
-            _VersionCLI.parse_arguments()
+            _VersionCLI.handle_command()
     assert "Version 'non_exist_version' does not exist." in caplog.text
     assert "Version 'non_exist_version' does not exist." in caplog.text
 
 
     _VersionCLI.create_parser()
     _VersionCLI.create_parser()
     with pytest.raises(SystemExit):
     with pytest.raises(SystemExit):
         with patch("sys.argv", ["prog", "manage-versions", "--rename", "1.0", "2.0"]):
         with patch("sys.argv", ["prog", "manage-versions", "--rename", "1.0", "2.0"]):
             # This should raise an exception since 2.0 already exists
             # This should raise an exception since 2.0 already exists
-            _VersionCLI.parse_arguments()
+            _VersionCLI.handle_command()
     assert "Version name '2.0' is already used." in caplog.text
     assert "Version name '2.0' is already used." in caplog.text
 
 
     _VersionCLI.create_parser()
     _VersionCLI.create_parser()
     with pytest.raises(SystemExit):
     with pytest.raises(SystemExit):
         with patch("sys.argv", ["prog", "manage-versions", "--rename", "1.0", "1.1"]):
         with patch("sys.argv", ["prog", "manage-versions", "--rename", "1.0", "1.1"]):
-            _VersionCLI.parse_arguments()
+            _VersionCLI.handle_command()
     assert _VersionManager._get("1.0") is None
     assert _VersionManager._get("1.0") is None
     assert [version.id for version in _VersionManager._get_all()].sort() == [dev_ver, "1.1", "2.0"].sort()
     assert [version.id for version in _VersionManager._get_all()].sort() == [dev_ver, "1.1", "2.0"].sort()
     # All entities are assigned to the new version
     # All entities are assigned to the new version
@@ -227,7 +227,7 @@ def test_rename_version(caplog, init_sql_repo):
     _VersionCLI.create_parser()
     _VersionCLI.create_parser()
     with pytest.raises(SystemExit):
     with pytest.raises(SystemExit):
         with patch("sys.argv", ["prog", "manage-versions", "--rename", "2.0", "2.1"]):
         with patch("sys.argv", ["prog", "manage-versions", "--rename", "2.0", "2.1"]):
-            _VersionCLI.parse_arguments()
+            _VersionCLI.handle_command()
     assert _VersionManager._get("2.0") is None
     assert _VersionManager._get("2.0") is None
     assert [version.id for version in _VersionManager._get_all()].sort() == [dev_ver, "1.1", "2.1"].sort()
     assert [version.id for version in _VersionManager._get_all()].sort() == [dev_ver, "1.1", "2.1"].sort()
     assert _VersionManager._get_production_versions() == ["2.1"]
     assert _VersionManager._get_production_versions() == ["2.1"]
@@ -269,23 +269,23 @@ def test_compare_version_config(caplog, init_sql_repo, init_config):
     with pytest.raises(SystemExit):
     with pytest.raises(SystemExit):
         with patch("sys.argv", ["prog", "manage-versions", "--compare-config", "non_exist_version", "2.0"]):
         with patch("sys.argv", ["prog", "manage-versions", "--compare-config", "non_exist_version", "2.0"]):
             # This should raise an exception since version "non_exist_version" does not exist
             # This should raise an exception since version "non_exist_version" does not exist
-            _VersionCLI.parse_arguments()
+            _VersionCLI.handle_command()
     assert "Version 'non_exist_version' does not exist." in caplog.text
     assert "Version 'non_exist_version' does not exist." in caplog.text
 
 
     with pytest.raises(SystemExit):
     with pytest.raises(SystemExit):
         with patch("sys.argv", ["prog", "manage-versions", "--compare-config", "1.0", "non_exist_version"]):
         with patch("sys.argv", ["prog", "manage-versions", "--compare-config", "1.0", "non_exist_version"]):
             # This should raise an exception since 2.0 already exists
             # This should raise an exception since 2.0 already exists
-            _VersionCLI.parse_arguments()
+            _VersionCLI.handle_command()
     assert "Version 'non_exist_version' does not exist." in caplog.text
     assert "Version 'non_exist_version' does not exist." in caplog.text
 
 
     with pytest.raises(SystemExit):
     with pytest.raises(SystemExit):
         with patch("sys.argv", ["prog", "manage-versions", "--compare-config", "1.0", "1.0"]):
         with patch("sys.argv", ["prog", "manage-versions", "--compare-config", "1.0", "1.0"]):
-            _VersionCLI.parse_arguments()
+            _VersionCLI.handle_command()
     assert "There is no difference between version 1.0 Configuration and version 1.0 Configuration." in caplog.text
     assert "There is no difference between version 1.0 Configuration and version 1.0 Configuration." in caplog.text
 
 
     with pytest.raises(SystemExit):
     with pytest.raises(SystemExit):
         with patch("sys.argv", ["prog", "manage-versions", "--compare-config", "1.0", "2.0"]):
         with patch("sys.argv", ["prog", "manage-versions", "--compare-config", "1.0", "2.0"]):
-            _VersionCLI.parse_arguments()
+            _VersionCLI.handle_command()
     expected_message = """Differences between version 1.0 Configuration and version 2.0 Configuration:
     expected_message = """Differences between version 1.0 Configuration and version 2.0 Configuration:
 \tDATA_NODE "d2" has attribute "default_path" modified: foo.csv -> bar.csv"""
 \tDATA_NODE "d2" has attribute "default_path" modified: foo.csv -> bar.csv"""
     assert expected_message in caplog.text
     assert expected_message in caplog.text

+ 23 - 0
tests/templates/test_template_cli.py

@@ -0,0 +1,23 @@
+# Copyright 2021-2024 Avaiga Private Limited
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+#        http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+# an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations under the License.
+
+from unittest.mock import patch
+
+import pytest
+
+from taipy._entrypoint import _entrypoint
+
+
+def test_create_cli_with_wrong_arguments(caplog):
+    with patch("sys.argv", ["prog", "create", "--teamplaet", "default"]):
+        with pytest.raises(SystemExit):
+            _entrypoint()
+        assert "Unknown arguments: --teamplaet. Did you mean: --template?" in caplog.text