module: honor module-level __dir__ (PEP 562) (#8271)

* module: honor module-level __dir__ (PEP 562)

dir(module) ignored a module-level __dir__ function and returned the raw
__dict__ keys. Look up __dir__ in the module dict and, when present, call
it and return its result — matching CPython's module___dir___impl. The
existing expectedFailure markers on test_module_dir/test_module_dir_errors
are removed since both now pass.

Fixes #8231

Assisted-by: Claude Code:claude-opus-4-8

* test: unmark zoneinfo test_dir_contains_all (PEP 562 __dir__)

Honoring a module-level __dir__ makes dir(zoneinfo) contain everything
in __all__, so test_dir_contains_all now passes. Remove its stale
expectedFailure marker to avoid an unexpected-success CI failure.

Assisted-by: Claude Code:claude-opus-4-8

* test: unmark test_support.test_check__all__ (PEP 562 __dir__)

check__all__ iterates dir(module); honoring a module-level __dir__ fixes
dir(unittest), so test_check__all__ now passes. Remove its stale
expectedFailure marker to avoid an unexpected-success CI failure.

Assisted-by: Claude Code:claude-opus-4-8
This commit is contained in:
정지웅 authored and GitHub committed 2026-07-15 23:36:43 +09:00
1 parent f03618e952
commit a777208f08
4 files changed
+4 -4

No files matched your search

-2
View File
@@ -151,13 +151,11 @@ a = A(destroyed)"""
if 'test.test_module.bad_getattr2' in sys.modules:
del sys.modules['test.test_module.bad_getattr2']
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_module_dir(self):
import test.test_module.good_getattr as gga
self.assertEqual(dir(gga), ['a', 'b', 'c'])
del sys.modules['test.test_module.good_getattr']
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_module_dir_errors(self):
import test.test_module.bad_getattr as bga
from test.test_module import bad_getattr2
-1
View File
@@ -458,7 +458,6 @@ class TestSupport(unittest.TestCase):
self.OtherClass, self.RefClass, ignore=ignore)
self.assertEqual(set(), missing_items)
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_check__all__(self):
extra = {'tempdir'}
not_exported = {'template'}
-1
View File
@@ -1970,7 +1970,6 @@ class TestModule(ZoneInfoTestBase):
with self.assertRaises(AttributeError):
self.module.NOATTRIBUTE
@unittest.expectedFailure # TODO: RUSTPYTHON; dir(self.module) should at least contain everything in __all__.
def test_dir_contains_all(self):
"""dir(self.module) should at least contain everything in __all__."""
module_all_set = set(self.module.__all__)
+4
View File
@@ -303,6 +303,10 @@ impl PyModule {
let dict = dict_attr
.downcast::<PyDict>()
.map_err(|_| vm.new_type_error("<module>.__dict__ is not a dictionary"))?;
// PEP 562: honor a module-level __dir__ if one is defined
if let Some(dir_func) = dict.get_item_opt(identifier!(vm, __dir__), vm)? {
return dir_func.call((), vm)?.try_to_value(vm);
}
let attrs = dict.into_iter().map(|(k, _v)| k).collect();
Ok(attrs)
}