Let’s say I have a attribute of a module that I deprecated and have removed. While the attribute was deprecated, the __getattr__
implementation looks like what’s in PEP 562:
from warnings import warn
bar = "hello_v2"
def __getattr__(name):
if name == "foo":
warn("foo is deprecated, use bar instead", DeprecationWarning)
return "hello"
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
Users who access the attribute or import it see a deprecation warning:
In [1]: from mymodule import foo
/home/nathan/Documents/test/mymodule.py:6: DeprecationWarning: foo is deprecated, use bar instead
warn("foo is deprecated, use bar instead", DeprecationWarning)
In [2]: import mymodule
In [3]: mymodule.foo
/home/nathan/Documents/test/mymodule.py:6: DeprecationWarning: foo is deprecated, use bar instead
warn("foo is deprecated, use bar instead", DeprecationWarning)
Out[3]: 'hello'
But what about after the deprecation has expired?
In that case, I want an AttributeError, but I still want my custom message to include a migration path for the deprecated item, in case users missed or ignored the warning:
from warnings import warn
bar = "hello_v2"
def __getattr__(name):
if name == "foo":
raise AttributeError("foo has been removed, use bar instead")
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
In this case, I get the AttributeError
I want when I do module-level access, but the import mechanism swallows the custom AttributeError
message if I import the symbol using a from
import (I checked and __getattr__
is still called and the error is raised, but the import
internals discard my custom error message):
In [1]: import mymodule
In [2]: mymodule.foo
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
Cell In[2], line 1
----> 1 mymodule.foo
File ~/Documents/test/mymodule.py:8, in __getattr__(name)
6 def __getattr__(name):
7 if name == "foo":
----> 8 raise AttributeError("foo has been removed, use bar instead")
9 raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
AttributeError: foo has been removed, use bar instead
In [3]: from mymodule import foo
---------------------------------------------------------------------------
ImportError Traceback (most recent call last)
Cell In[3], line 1
----> 1 from mymodule import foo
ImportError: cannot import name 'foo' from 'mymodule' (/home/nathan/Documents/test/mymodule.py)
This leads me to two questions: is there a way in Python right now to get a nice error message for both styles of accessing the foo
symbol? If there isn’t a way to do this yet, would it be a reasonable change to Python to detect when an AttributeError has a custom error message and include that in the ImportError?