Repository: cogent3/cogent3
Pull request #1996 (2024-08-21 22:24:20)

ENH: improve type-hint stripping in app_help()
[FIXED] now covers cases that use the pipe character. Also removed
    spaces around "=" character and replaced double with single quotes
    in app vignette for consistency with repr(obj) style


Patch:
diff --git a/src/cogent3/app/__init__.py b/src/cogent3/app/__init__.py
index 163938f52..c8ff44175 100644
--- a/src/cogent3/app/__init__.py
+++ b/src/cogent3/app/__init__.py
@@ -95,7 +95,7 @@ def available_apps(name_filter: str | None = None) -> Table:
 
 
 _get_param = re.compile('(?<=").+(?=")')
-_type_hint = re.compile(r":\s*\S+\s*?=")
+_type_hint = re.compile(r":.+?=\s*")
 
 
 def _make_signature(app: type) -> str:
@@ -110,7 +110,7 @@ def _make_signature(app: type) -> str:
 
     init_sig = inspect.signature(app.__init__)
     app_name = app.__name__
-    params = [f'"{app_name}"']
+    params = [f"{app_name!r}"]
     empty_default = inspect._empty
     for k, v in init_sig.parameters.items():
         if k == "self":
@@ -121,7 +121,7 @@ def _make_signature(app: type) -> str:
         txt = txt.replace("<built-in function callable>", "callable")
 
         val = _get_param.findall(txt)[0]
-        val = _type_hint.sub(" =", val)
+        val = _type_hint.sub("=", val)
         if v.default is not empty_default and callable(v.default):
             val = val.split("=", maxsplit=1)
             if hasattr(v.default, "app_type"):


Test patch:
diff --git a/tests/test_app/test_init.py b/tests/test_app/test_init.py
index 4edfb10ba..9b3738cc9 100644
--- a/tests/test_app/test_init.py
+++ b/tests/test_app/test_init.py
@@ -149,7 +149,7 @@ def test_app_help_signature(capsys, app_name):
 
     got = _make_signature(_get_app_matching_name(app_name))
     # app name is in quotes
-    assert f'"{app_name}"' in got
+    assert f"{app_name!r}" in got
     # check split across multiple lines if long signature
     if len(got) > 70:
         assert got.count("\n") > 1
diff --git a/tests/test_app/test_plugins.py b/tests/test_app/test_plugins.py
index 84c619e42..980426d15 100644
--- a/tests/test_app/test_plugins.py
+++ b/tests/test_app/test_plugins.py
@@ -1,5 +1,6 @@
 import random
 import string
+import sys
 from importlib.metadata import EntryPoint
 from unittest.mock import patch
 
@@ -257,6 +258,26 @@ def test_app_with_app_as_default(mock_extension_manager):
     assert app_with_custom_addapp(5) == 15
 
 
+@pytest.mark.skipif(
+    sys.version_info[:2] == (3, 9), reason="Skipping test for Python 3.9"
+)
+def test_app_help_mixed_type_hinting(mock_extension_manager):
+    """apps can be initialized with other apps as arguments"""
+
+    @define_app
+    class MyApp:
+        def __init__(self, seed: int | None = None, b=2, c: str = ""):
+            self.seed = seed
+
+        def main(self, data: int) -> int:
+            return data + self.seed
+
+    mock_extension_manager([create_extension(MyApp)])
+    ds = _make_apphelp_docstring(MyApp)
+    # successfully stripped type-hints
+    assert ":" not in ds
+
+
 def test_app_help_from_function(mock_extension_manager):
     """_make_apphelp_docstring on a decorated function should return help"""