Repository: HypothesisWorks/hypothesis

Issue:
`extract_lambda_source` fails in Pyodide
If you paste the following code into [my online ghostwriter demo](https://zhd.dev/ghostwriter/), you get an error:

```python
from hypothesis import given, strategies as st

@given(st.just(lambda: None))
def test_extract_lambda_source(obj):
    assert False
```
```python-traceback
Traceback (most recent call last):
  File "test_code.py", line 6, in test_extract_lambda_source
    #
  File "/lib/python3.10/site-packages/hypothesis/core.py", line 1257, in wrapped_test
    raise the_error_hypothesis_found
  File "/lib/python3.10/site-packages/hypothesis/core.py", line 1224, in wrapped_test
    state.run_engine()
  File "/lib/python3.10/site-packages/hypothesis/core.py", line 903, in run_engine
    _raise_to_user(errors_to_report, self.settings, report_lines)
  File "/lib/python3.10/site-packages/hypothesis/core.py", line 934, in _raise_to_user
    raise the_error_hypothesis_found
  File "/lib/python3.10/site-packages/hypothesis/core.py", line 866, in run_engine
    self.execute_once(
  File "/lib/python3.10/site-packages/hypothesis/core.py", line 688, in execute_once
    result = self.test_runner(data, run)
  File "/lib/python3.10/site-packages/hypothesis/executors.py", line 47, in default_new_style_executor
    return function(data)
  File "/lib/python3.10/site-packages/hypothesis/core.py", line 649, in run
    text_repr = repr_call(test, args, kwargs)
  File "/lib/python3.10/site-packages/hypothesis/internal/reflection.py", line 421, in repr_call
    bits.append(f"{p.name}={nicerepr(kwargs.pop(p.name))}")
  File "/lib/python3.10/site-packages/hypothesis/internal/reflection.py", line 405, in nicerepr
    return get_pretty_function_description(v)
  File "/lib/python3.10/site-packages/hypothesis/internal/reflection.py", line 390, in get_pretty_function_description
    return extract_lambda_source(f)
  File "/lib/python3.10/site-packages/hypothesis/internal/reflection.py", line 279, in extract_lambda_source
    assert "lambda" in source
AssertionError
```

I'm not sure exactly why, but it would be nice to fix this (and use #3437 to prevent a regression).


Discussion:

Resolving patch:
diff --git a/hypothesis-python/src/hypothesis/extra/ghostwriter.py b/hypothesis-python/src/hypothesis/extra/ghostwriter.py
--- a/hypothesis-python/src/hypothesis/extra/ghostwriter.py
+++ b/hypothesis-python/src/hypothesis/extra/ghostwriter.py
@@ -673,11 +673,25 @@ def _valid_syntax_repr(strategy):
 KNOWN_FUNCTION_LOCATIONS: Dict[object, str] = {}
 
 
+def _get_module_helper(obj):
+    # Get the __module__ attribute of the object, and return the first ancestor module
+    # which contains the object; falling back to the literal __module__ if none do.
+    # The goal is to show location from which obj should usually be accessed, rather
+    # than what we assume is an internal submodule which defined it.
+    module_name = obj.__module__
+    dots = [i for i, c in enumerate(module_name) if c == "."] + [None]
+    for idx in dots:
+        if getattr(sys.modules.get(module_name[:idx]), obj.__name__, None) is obj:
+            KNOWN_FUNCTION_LOCATIONS[obj] = module_name[:idx]
+            return module_name[:idx]
+    return module_name
+
+
 def _get_module(obj):
     if obj in KNOWN_FUNCTION_LOCATIONS:
         return KNOWN_FUNCTION_LOCATIONS[obj]
     try:
-        return obj.__module__
+        return _get_module_helper(obj)
     except AttributeError:
         if not _is_probably_ufunc(obj):
             raise
@@ -936,7 +950,7 @@ def magic(
                     functions.add(f)
                     if getattr(thing, "__name__", None):
                         if inspect.isclass(thing):
-                            KNOWN_FUNCTION_LOCATIONS[f] = thing.__module__
+                            KNOWN_FUNCTION_LOCATIONS[f] = _get_module_helper(thing)
                         else:
                             KNOWN_FUNCTION_LOCATIONS[f] = thing.__name__
             except (TypeError, ValueError):
diff --git a/hypothesis-python/src/hypothesis/internal/reflection.py b/hypothesis-python/src/hypothesis/internal/reflection.py
--- a/hypothesis-python/src/hypothesis/internal/reflection.py
+++ b/hypothesis-python/src/hypothesis/internal/reflection.py
@@ -276,6 +276,8 @@ def extract_lambda_source(f):
     source = LINE_CONTINUATION.sub(" ", source)
     source = WHITESPACE.sub(" ", source)
     source = source.strip()
+    if "lambda" not in source and sys.platform == "emscripten":  # pragma: no cover
+        return if_confused  # work around Pyodide bug in inspect.getsource()
     assert "lambda" in source
 
     tree = None


Test patch:
diff --git a/hypothesis-python/tests/ghostwriter/test_ghostwriter.py b/hypothesis-python/tests/ghostwriter/test_ghostwriter.py
--- a/hypothesis-python/tests/ghostwriter/test_ghostwriter.py
+++ b/hypothesis-python/tests/ghostwriter/test_ghostwriter.py
@@ -37,6 +37,7 @@
 import click
 import pytest
 
+from hypothesis import assume
 from hypothesis.errors import InvalidArgument, Unsatisfiable
 from hypothesis.extra import cli, ghostwriter
 from hypothesis.internal.compat import BaseExceptionGroup
@@ -501,3 +502,7 @@ def test_obj_name(temp_script_file, temp_script_file_with_py_function):
     assert isinstance(
         cli.obj_name(str(temp_script_file_with_py_function)), FunctionType
     )
+
+
+def test_gets_public_location_not_impl_location():
+    assert ghostwriter._get_module(assume) == "hypothesis"  # not "hypothesis.control"