Thanks to visit codestin.com
Credit goes to github.com

Skip to content

__globals__ and AST parsing exception handling#123

Merged
ngoldbaum merged 11 commits into
Quansight-Labs:mainfrom
bwhitt7:iteration2
Sep 16, 2025
Merged

__globals__ and AST parsing exception handling#123
ngoldbaum merged 11 commits into
Quansight-Labs:mainfrom
bwhitt7:iteration2

Conversation

@bwhitt7
Copy link
Copy Markdown
Contributor

@bwhitt7 bwhitt7 commented Sep 10, 2025

This PR hopes to fix #121, addressing errors when running tests that have __globals__ that are not iterable (such as some tests in SQLAlchemy, as shown in the issue).

This PR wraps the __globals__ in a try-except that will mark the test at thread-unsafe and print a message about the test's __globals__ not being iterable (I'm open to suggestions for a more user-friendly message haha). This stops AST parsing and runs the test as normal without parallel threads. I also put a try-except around the entire AST parsing loop, so that if we run into any other issues like this in the future, pytest-run-parallel can handle the error and recommend that the user report the bug.

An example when running the tests in sqlalchemy/test/base/test_typing_utils.py (link)

test/base/test_typing_utils.py::TestTestingThings::test_make_fw_ref PASSED [thread-unsafe]: test __globals__ not iterable: 'member_descriptor' object is not iterable                    [  5%]
test/base/test_typing_utils.py::TestTestingThings::test_make_type_alias_type PARALLEL PASSED                                                                                             [ 11%]
test/base/test_typing_utils.py::TestTestingThings::test_unions_are_the_same PARALLEL PASSED                                                                                              [ 17%]
test/base/test_typing_utils.py::TestTyping::test_TypingInstances PARALLEL PASSED                                                                                                         [ 23%]
test/base/test_typing_utils.py::TestTyping::test_de_optionalize_union_types PASSED [thread-unsafe]: test __globals__ not iterable: 'member_descriptor' object is not iterable            [ 29%]
test/base/test_typing_utils.py::TestTyping::test_includes_none PASSED [thread-unsafe]: test __globals__ not iterable: 'member_descriptor' object is not iterable                         [ 35%]
test/base/test_typing_utils.py::TestTyping::test_includes_none_generics PARALLEL PASSED                                                                                                  [ 41%]
test/base/test_typing_utils.py::TestTyping::test_is_fwd_ref PARALLEL PASSED                                                                                                              [ 47%]
test/base/test_typing_utils.py::TestTyping::test_is_generic PARALLEL PASSED                                                                                                              [ 52%]
test/base/test_typing_utils.py::TestTyping::test_is_literal PARALLEL PASSED                                                                                                              [ 58%]
test/base/test_typing_utils.py::TestTyping::test_is_newtype PARALLEL PASSED                                                                                                              [ 64%]
test/base/test_typing_utils.py::TestTyping::test_is_pep593 PARALLEL PASSED                                                                                                               [ 70%]
test/base/test_typing_utils.py::TestTyping::test_is_pep695 PARALLEL PASSED                                                                                                               [ 76%]
test/base/test_typing_utils.py::TestTyping::test_is_union PARALLEL PASSED                                                                                                                [ 82%]
test/base/test_typing_utils.py::TestTyping::test_make_union_type PARALLEL PASSED                                                                                                         [ 88%]
test/base/test_typing_utils.py::TestTyping::test_pep695_value PASSED [thread-unsafe]: test __globals__ not iterable: 'member_descriptor' object is not iterable                          [ 94%]
test/base/test_typing_utils.py::TestTyping::test_pep695_value_generics PARALLEL PASSED                                                                                                   [100%]
********************************************************************************** pytest-run-parallel report **********************************************************************************
test/base/test_typing_utils.py::TestTestingThings::test_make_fw_ref was not run in parallel because it test __globals__ not iterable: 'member_descriptor' object is not iterable
test/base/test_typing_utils.py::TestTyping::test_pep695_value was not run in parallel because it test __globals__ not iterable: 'member_descriptor' object is not iterable
test/base/test_typing_utils.py::TestTyping::test_de_optionalize_union_types was not run in parallel because it test __globals__ not iterable: 'member_descriptor' object is not iterable
test/base/test_typing_utils.py::TestTyping::test_includes_none was not run in parallel because it test __globals__ not iterable: 'member_descriptor' object is not iterable

Comment thread src/pytest_run_parallel/thread_unsafe_detection.py Outdated
@bwhitt7
Copy link
Copy Markdown
Contributor Author

bwhitt7 commented Sep 10, 2025

@ngoldbaum Pushed a commit that makes it so it quietly handles __globals__ that aren't iterable and doesn't mark it as thread-unsafe. Introduced a globals var alongside the iter_globals var that _recursive_analyze_attribute and _recursive_analyze_name will check.

@ngoldbaum
Copy link
Copy Markdown
Collaborator

Maybe @lysnikolaou can suggest a reasonable way to add a test and confirm that this fixes sqlalchemy.

Comment thread src/pytest_run_parallel/thread_unsafe_detection.py Outdated
Comment thread src/pytest_run_parallel/thread_unsafe_detection.py Outdated
@lysnikolaou
Copy link
Copy Markdown
Contributor

Thanks for the PR @bwhitt7! While the PR looks good, I think we should go with what @ngoldbaum proposed in #121 (comment) and wrap the whole thing (probably these two lines) in a try/except.

@bwhitt7
Copy link
Copy Markdown
Contributor Author

bwhitt7 commented Sep 11, 2025

@lysnikolaou Gotchu! Wrapped those lines with a try/except. I noticed that other try/excepts in the _identify_thread_unsafe_nodes function just say that the nodes are thread-safe (with return False, None). For this PR I was interested in explicitly printing a message for users to see that something weird happened so they can report any similar bugs (for now the __globals__ issue is handled in the init function still, so that won't be reported). An easy way to do this is to mark the thread as thread-unsafe and print a message. However if this isn't the best approach, I can definitely make it return False, None as well.

except Exception as e:
return (
True,
f"caught {type(e).__name__} exception while parsing AST, please report bug to pytest-run-parallel: {e}",
Copy link
Copy Markdown
Collaborator

@ngoldbaum ngoldbaum Sep 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
f"caught {type(e).__name__} exception while parsing AST, please report bug to pytest-run-parallel: {e}",
f"caught {type(e).__name__} exception with content {repr(e)} while parsing the AST of test function {repr(fn)}, please report a bug to pytest-run-parallel at https://github.com/Quansight-Labs/pytest-run-parallel/issues/new including this error and accompanying traceback.",

This probably makes the line really long, you might need to reformat it too.

It might also be useful if this message included the accompanying traceback too. You can get the traceback from an exception like this:

>>> import traceback
>>> try:
...     raise RuntimeError
... except Exception as e:
...     tb = traceback.format_exc()
...
>>> tb
'Traceback (most recent call last):\n  File "<python-input-0>", line 2, in <module>\n    raise RuntimeError\nRuntimeError\n'
>>> print(tb)
Traceback (most recent call last):
  File "<python-input-0>", line 2, in <module>
    raise RuntimeError
RuntimeError

Also I think this is getting complicated enough it probably deserves a test. You could maybe directly import identify_thread_unsafe_nodes from the pytest-run-parallel implementation and pass it a test function that you know will raise an exception during AST parsing. Maybe it could override __getattribute__ so if someone tries to access __globals__ on it, it raises a RuntimeError? Hopefully that idea works but if it doesn't maybe we can come up with something else...

I don't think there's any need to try to use the pytest testing utilities for this and make sure the pytest output is correct - just write a "normal" pytest test specifically for this error.

@ngoldbaum ngoldbaum changed the title __globals__ and AST pasing exception handling __globals__ and AST parsing exception handling Sep 12, 2025
@bwhitt7
Copy link
Copy Markdown
Contributor Author

bwhitt7 commented Sep 12, 2025

This last push adds more specific exception handling to _identify_thread_unsafe_nodes. It also moves the error message for the last exception handling for visitor out of thread-unsafe message, and instead uses warnings.

Copy link
Copy Markdown
Contributor

@lysnikolaou lysnikolaou left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! Good work!

@ngoldbaum Would you mind giving this another look?

Copy link
Copy Markdown
Collaborator

@ngoldbaum ngoldbaum left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just one small suggestion to simplify the try/except. Untested but I think it should work.

Comment thread src/pytest_run_parallel/thread_unsafe_detection.py Outdated
@ngoldbaum
Copy link
Copy Markdown
Collaborator

Thanks for taking this on @bwhitt7!

@ngoldbaum ngoldbaum merged commit f88fe37 into Quansight-Labs:main Sep 16, 2025
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

'member_descriptor' object is not iterable when trying to iterate over __globals__

3 participants