Repository: marcelotduarte/cx_Freeze (python)

Issue:
Why is Executable target_name has to be  A valid identifier?
In https://github.com/marcelotduarte/cx_Freeze/blob/7.2.1/cx_Freeze/executable.py#L234 target_name is required to be a valid identifier.

Is there any reason for that? I removed that condition and it seems to work fine.
my
target_name="6578e4ecf0464d7fb253de58"



Discussion:
Maybe a regression - issue #884 fixed by #889



Patch:
diff --git a/cx_Freeze/executable.py b/cx_Freeze/executable.py
--- a/cx_Freeze/executable.py
+++ b/cx_Freeze/executable.py
@@ -116,7 +116,7 @@ def init_module_name(self) -> str:
         :rtype: str
 
         """
-        return f"{self._internal_name}__init__"
+        return f"__init__{self._internal_name}"
 
     @property
     def init_script(self) -> Path:
@@ -143,7 +143,7 @@ def main_module_name(self) -> str:
         :rtype: str
 
         """
-        return f"{self._internal_name}__main__"
+        return f"__main__{self._internal_name}"
 
     @property
     def main_script(self) -> Path:
@@ -231,10 +231,10 @@ def target_name(self, name: str | None) -> None:
             for invalid in STRINGREPLACE:
                 name = name.replace(invalid, "_")
         name = os.path.normcase(name)
-        if not name.isidentifier():
+        self._internal_name: str = name
+        if not self.init_module_name.isidentifier():
             msg = f"target_name is invalid: {self._name!r}"
             raise OptionError(msg)
-        self._internal_name: str = name
 
 
 def validate_executables(dist: Distribution, attr: str, value) -> None:
diff --git a/cx_Freeze/initscripts/__startup__.py b/cx_Freeze/initscripts/__startup__.py
--- a/cx_Freeze/initscripts/__startup__.py
+++ b/cx_Freeze/initscripts/__startup__.py
@@ -124,8 +124,8 @@ def run() -> None:
     """Determines the name of the initscript and execute it."""
     name = get_name(sys.executable)
     try:
-        # basically, the basename of the executable plus __init__
-        module_init = __import__(name + "__init__")
+        # basically is __init__ plus the basename of the executable
+        module_init = __import__(f"__init__{name}")
     except ModuleNotFoundError:
         # but can be renamed when only one executable exists
         num = BUILD_CONSTANTS._EXECUTABLES_NUMBER  # noqa: SLF001
@@ -137,5 +137,5 @@ def run() -> None:
             )
             raise RuntimeError(msg) from None
         name = get_name(BUILD_CONSTANTS._EXECUTABLE_NAME_0)  # noqa: SLF001
-        module_init = __import__(name + "__init__")
-    module_init.run(name + "__main__")
+        module_init = __import__(f"__init__{name}")
+    module_init.run(f"__main__{name}")


Test patch:
diff --git a/tests/test_cli.py b/tests/test_cli.py
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -10,16 +10,16 @@
 import pytest
 from generate_samples import create_package, run_command
 
+from cx_Freeze._compat import BUILD_EXE_DIR, EXE_SUFFIX
+
 if TYPE_CHECKING:
     from pathlib import Path
 
-SUFFIX = ".exe" if sys.platform == "win32" else ""
-
 SOURCE = """
 test.py
     print("Hello from cx_Freeze")
 command
-    cxfreeze test.py --target-dir=dist --excludes=tkinter
+    cxfreeze --script test.py --target-dir=dist --excludes=tkinter
 """
 
 
@@ -28,7 +28,7 @@ def test_cxfreeze(tmp_path: Path) -> None:
     create_package(tmp_path, SOURCE)
     output = run_command(tmp_path)
 
-    file_created = tmp_path / "dist" / f"test{SUFFIX}"
+    file_created = tmp_path / "dist" / f"test{EXE_SUFFIX}"
     assert file_created.is_file(), f"file not found: {file_created}"
 
     output = run_command(tmp_path, file_created, timeout=10)
@@ -49,15 +49,30 @@ def test_cxfreeze_additional_help(tmp_path: Path) -> None:
     assert "usage: " in output
 
 
+def test_cxfreeze_target_name_not_isidentifier(tmp_path: Path) -> None:
+    """Test cxfreeze --target-name not isidentifier, but valid filename."""
+    create_package(tmp_path, SOURCE)
+    output = run_command(
+        tmp_path,
+        "cxfreeze --script test.py --target-name=12345 --excludes=tkinter",
+    )
+
+    file_created = tmp_path / BUILD_EXE_DIR / f"12345{EXE_SUFFIX}"
+    assert file_created.is_file(), f"file not found: {file_created}"
+
+    output = run_command(tmp_path, file_created, timeout=10)
+    assert output.startswith("Hello from cx_Freeze")
+
+
 def test_cxfreeze_deprecated_behavior(tmp_path: Path) -> None:
     """Test cxfreeze deprecated behavior."""
     create_package(tmp_path, SOURCE)
     tmp_path.joinpath("test.py").rename(tmp_path / "test2")
     output = run_command(
-        tmp_path, "cxfreeze --target-dir=dist --excludes=tkinter test2"
+        tmp_path, "cxfreeze --install-dir=dist --excludes=tkinter test2"
     )
 
-    file_created = tmp_path / "dist" / f"test2{SUFFIX}"
+    file_created = tmp_path / "dist" / f"test2{EXE_SUFFIX}"
     assert file_created.is_file(), f"file not found: {file_created}"
 
     output = run_command(tmp_path, file_created, timeout=10)
@@ -73,7 +88,7 @@ def test_cxfreeze_deprecated_option(tmp_path: Path) -> None:
     )
     assert "WARNING: deprecated" in output
 
-    file_created = tmp_path / "dist" / f"test{SUFFIX}"
+    file_created = tmp_path / "dist" / f"test{EXE_SUFFIX}"
     assert file_created.is_file(), f"file not found: {file_created}"
 
     output = run_command(tmp_path, file_created, timeout=10)
@@ -127,12 +142,12 @@ def test_cxfreeze_include_path(tmp_path: Path) -> None:
     create_package(tmp_path, SOURCE_TEST_PATH)
     output = run_command(tmp_path)
 
-    executable = tmp_path / "dist" / f"advanced_1{SUFFIX}"
+    executable = tmp_path / "dist" / f"advanced_1{EXE_SUFFIX}"
     assert executable.is_file()
     output = run_command(tmp_path, executable, timeout=10)
     assert output == OUTPUT1
 
-    executable = tmp_path / "dist" / f"advanced_2{SUFFIX}"
+    executable = tmp_path / "dist" / f"advanced_2{EXE_SUFFIX}"
     assert executable.is_file()
     output = run_command(tmp_path, executable, timeout=10)
     assert output == OUTPUT2
diff --git a/tests/test_executables.py b/tests/test_executables.py
--- a/tests/test_executables.py
+++ b/tests/test_executables.py
@@ -232,6 +232,7 @@ def test_executables(
         ("init_script", "console", "console.py"),
         ("target_name", None, f"test{EXE_SUFFIX}"),
         ("target_name", "test1", f"test1{EXE_SUFFIX}"),
+        ("target_name", "12345", f"12345{EXE_SUFFIX}"),
         ("target_name", "test-0.1", f"test-0.1{EXE_SUFFIX}"),
         ("target_name", "test.exe", "test.exe"),
         ("icon", "icon", ("icon.ico", "icon.icns", "icon.png", "icon.svg")),
@@ -279,12 +280,6 @@ def test_valid(option, value, result) -> None:
             OptionError,
             "target_name cannot contain the path, only the filename: ",
         ),
-        (
-            Executable,
-            {"script": "test.py", "target_name": "0test"},
-            OptionError,
-            "target_name is invalid: ",
-        ),
     ],
     ids=[
         "executables-invalid-empty",
@@ -292,7 +287,6 @@ def test_valid(option, value, result) -> None:
         "executable-invalid-base",
         "executable-invalid-init_script",
         "executable-invalid-target_name",
-        "executable-invalid-target_name-isidentifier",
     ],
 )
 def test_invalid(