Repository: marcelotduarte/cx_Freeze (python)

Issue:
argon2-cffi metadata is missing on 7.2.4+
**Describe the bug**
argon2-cffi metadata is missing

```
  File "permissions.py", line 104, in authenticate
  File "passlib\context.py", line 2347, in verify
  File "passlib\handlers\argon2.py", line 669, in verify
  File "passlib\utils\handlers.py", line 2254, in _stub_requires_backend
  File "passlib\utils\handlers.py", line 2156, in set_backend
  File "passlib\utils\handlers.py", line 2163, in set_backend
  File "passlib\utils\handlers.py", line 2188, in set_backend
  File "passlib\utils\handlers.py", line 2311, in _set_backend
  File "passlib\utils\handlers.py", line 2224, in _set_backend
  File "passlib\handlers\argon2.py", line 716, in _load_backend_mixin
  File "argon2\__init__.py", line 75, in __getattr__
  File "importlib\metadata\__init__.py", line 998, in metadata
  File "importlib\metadata\__init__.py", line 565, in from_name
importlib.metadata.PackageNotFoundError: No package metadata was found for argon2-cffi

```
Line 104 in permissions is effectively this:
```
from passlib.context import CryptContext
CryptContext(schemes=["argon2"]).verify(string1, string2)
```


**To Reproduce**
Hopefully the above is enough, this is a closed source application so I'd like to keep it to what's necessary.

**Expected behavior**
For the indirect import of argon2 via passlib to not fail when frozen.

**Screenshots**
Don't think that helps.

**Desktop (please complete the following information):**
 - Platform information: Win10 and Win11
 - OS architecture (e.g. amd64): amd64
 - cx_Freeze version [e.g. 6.11]: 7.2.4 onwards. It works on 7.2.3. Still broken on 8.0.0
 - Python version [e.g. 3.10]: 3.11

**Additional context**
Pinned cx-Freeze to 7.2.3 for now, tried some typical troubleshooting like putting argon2 into always include, as well as zip-exclude, to no success.



Discussion:

Patch:
diff --git a/cx_Freeze/hooks/__init__.py b/cx_Freeze/hooks/__init__.py
--- a/cx_Freeze/hooks/__init__.py
+++ b/cx_Freeze/hooks/__init__.py
@@ -34,6 +34,15 @@ def load_aiofiles(finder: ModuleFinder, module: Module) -> None:
     finder.include_package("aiofiles")
 
 
+def load_argon2(finder: ModuleFinder, module: Module) -> None:
+    """The argon2-cffi package requires the _cffi_backend module
+    (loaded implicitly).
+    """
+    if module.distribution is None:
+        module.update_distribution("argon2-cffi")
+        finder.include_module("_cffi_backend")
+
+
 def load_babel(finder: ModuleFinder, module: Module) -> None:
     """The babel must be loaded as a package, and has pickeable data."""
     finder.include_package("babel")


Test patch:
diff --git a/tests/test_hooks_argon2.py b/tests/test_hooks_argon2.py
new file mode 100644
--- /dev/null
+++ b/tests/test_hooks_argon2.py
@@ -0,0 +1,37 @@
+"""Tests for some cx_Freeze.hooks."""
+
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+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
+
+pytest.importorskip("argon2", reason="Depends on extra package: argon2-cffi")
+
+SOURCE = """
+test_argon2.py
+    import argon2
+    from importlib.metadata import distribution, version
+
+    print("Hello from cx_Freeze")
+    print(argon2.__name__, version("argon2-cffi"))
+command
+    cxfreeze --script test_argon2.py build_exe
+"""
+
+
+def test_argon2(tmp_path: Path) -> None:
+    """Test if argon2-cffi is working correctly."""
+    create_package(tmp_path, SOURCE)
+    output = run_command(tmp_path)
+    executable = tmp_path / BUILD_EXE_DIR / f"test_argon2{EXE_SUFFIX}"
+    assert executable.is_file()
+    output = run_command(tmp_path, executable, timeout=10)
+    assert output.splitlines()[0] == "Hello from cx_Freeze"
+    assert output.splitlines()[1].startswith("argon2")