bpo-32225: Implementation of PEP 562 (#4731) · pythoncapi/cpython@5364b5c · GitHub
Skip to content

Commit 5364b5c

Browse files
authored
bpo-32225: Implementation of PEP 562 (python#4731)
Implement PEP 562: module __getattr__ and __dir__. The implementation simply updates module_getattro and module_dir.
1 parent 9e7c136 commit 5364b5c

9 files changed

Lines changed: 161 additions & 4 deletions

File tree

Doc/reference/datamodel.rst

Lines changed: 45 additions & 0 deletions

Doc/whatsnew/3.7.rst

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,24 @@ effort will be made to add such support.
159159
PEP written by Erik M. Bray; implementation by Masayuki Yamamoto.
160160

161161

162+
PEP 562: Customization of access to module attributes
163+
-----------------------------------------------------
164+
165+
It is sometimes convenient to customize or otherwise have control over access
166+
to module attributes. A typical example is managing deprecation warnings.
167+
Typical workarounds are assigning ``__class__`` of a module object to
168+
a custom subclass of :class:`types.ModuleType` or replacing the ``sys.modules``
169+
item with a custom wrapper instance. This procedure is now simplified by
170+
recognizing ``__getattr__`` defined directly in a module that would act like
171+
a normal ``__getattr__`` method, except that it will be defined on module
172+
*instances*.
173+
174+
.. seealso::
175+
176+
:pep:`562` -- Module ``__getattr__`` and ``__dir__``
177+
PEP written and implemented by Ivan Levkivskyi
178+
179+
162180
PEP 564: Add new time functions with nanosecond resolution
163181
----------------------------------------------------------
164182

Lib/test/bad_getattr.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
x = 1
2+
3+
__getattr__ = "Surprise!"
4+
__dir__ = "Surprise again!"

Lib/test/bad_getattr2.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
def __getattr__():
2+
"Bad one"
3+
4+
x = 1
5+
6+
def __dir__(bad_sig):
7+
return []

Lib/test/bad_getattr3.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
def __getattr__(name):
2+
if name != 'delgetattr':
3+
raise AttributeError
4+
del globals()['__getattr__']
5+
raise AttributeError

Lib/test/good_getattr.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
x = 1
2+
3+
def __dir__():
4+
return ['a', 'b', 'c']
5+
6+
def __getattr__(name):
7+
if name == "yolo":
8+
raise AttributeError("Deprecated, use whatever instead")
9+
return f"There is {name}"
10+
11+
y = 2

Lib/test/test_module.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,57 @@ def test_weakref(self):
125125
gc_collect()
126126
self.assertIs(wr(), None)
127127

128+
def test_module_getattr(self):
129+
import test.good_getattr as gga
130+
from test.good_getattr import test
131+
self.assertEqual(test, "There is test")
132+
self.assertEqual(gga.x, 1)
133+
self.assertEqual(gga.y, 2)
134+
with self.assertRaisesRegex(AttributeError,
135+
"Deprecated, use whatever instead"):
136+
gga.yolo
137+
self.assertEqual(gga.whatever, "There is whatever")
138+
del sys.modules['test.good_getattr']
139+
140+
def test_module_getattr_errors(self):
141+
import test.bad_getattr as bga
142+
from test import bad_getattr2
143+
self.assertEqual(bga.x, 1)
144+
self.assertEqual(bad_getattr2.x, 1)
145+
with self.assertRaises(TypeError):
146+
bga.nope
147+
with self.assertRaises(TypeError):
148+
bad_getattr2.nope
149+
del sys.modules['test.bad_getattr']
150+
if 'test.bad_getattr2' in sys.modules:
151+
del sys.modules['test.bad_getattr2']
152+
153+
def test_module_dir(self):
154+
import test.good_getattr as gga
155+
self.assertEqual(dir(gga), ['a', 'b', 'c'])
156+
del sys.modules['test.good_getattr']
157+
158+
def test_module_dir_errors(self):
159+
import test.bad_getattr as bga
160+
from test import bad_getattr2
161+
with self.assertRaises(TypeError):
162+
dir(bga)
163+
with self.assertRaises(TypeError):
164+
dir(bad_getattr2)
165+
del sys.modules['test.bad_getattr']
166+
if 'test.bad_getattr2' in sys.modules:
167+
del sys.modules['test.bad_getattr2']
168+
169+
def test_module_getattr_tricky(self):
170+
from test import bad_getattr3
171+
# these lookups should not crash
172+
with self.assertRaises(AttributeError):
173+
bad_getattr3.one
174+
with self.assertRaises(AttributeError):
175+
bad_getattr3.delgetattr
176+
if 'test.bad_getattr3' in sys.modules:
177+
del sys.modules['test.bad_getattr3']
178+
128179
def test_module_repr_minimal(self):
129180
# reprs when modules have no __file__, __name__, or __loader__
130181
m = ModuleType('foo')
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
PEP 562: Add support for module ``__getattr__`` and ``__dir__``. Implemented by Ivan
2+
Levkivskyi.

Objects/moduleobject.c

Lines changed: 18 additions & 4 deletions

0 commit comments

Comments
 (0)