typeguard
typeguard copied to clipboard
`typechecked` in a Jupyter notebook / IPython always triggers `TypeError: <module '__main__'> is a built-in module`
Things to check first
-
[X] I have searched the existing issues and didn't find my bug already reported there
-
[X] I have checked that my bug is still present in the latest release
Typeguard version
4.0.0
Python version
3.10.10
What happened?
Using typechecked
in a Jupyter notebook or in IPython results in the following exception, as of version 4:
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[12], line 2
1 @typechecked
----> 2 def foo(bar: str):
3 print(bar)
5 foo('Hello world!')
File ~/conda/envs/gravitation/lib/python3.10/site-packages/typeguard/_decorators.py:213, in typechecked(target, forward_ref_policy, typecheck_fail_callback, collection_check_strategy, debug_instrumentation)
210 wrapper_class = target.__class__
211 target = target.__func__
--> 213 retval = instrument(target)
214 if isinstance(retval, str):
215 warn(
216 f"{retval} -- not typechecking {function_name(target)}",
217 InstrumentationWarning,
218 stacklevel=get_stacklevel(),
219 )
File ~/conda/envs/gravitation/lib/python3.10/site-packages/typeguard/_decorators.py:51, in instrument(f)
45 return (
46 "@typechecked only supports instrumenting functions wrapped with "
47 "@classmethod, @staticmethod or @property"
48 )
50 target_path = [item for item in f.__qualname__.split(".") if item != "<locals>"]
---> 51 module_source = inspect.getsource(sys.modules[f.__module__])
52 module_ast = ast.parse(module_source)
53 instrumentor = TypeguardTransformer(target_path)
File ~/conda/envs/gravitation/lib/python3.10/inspect.py:1139, in getsource(object)
1133 def getsource(object):
1134 """Return the text of the source code for an object.
1135
1136 The argument may be a module, class, method, function, traceback, frame,
1137 or code object. The source code is returned as a single string. An
1138 OSError is raised if the source code cannot be retrieved."""
-> 1139 lines, lnum = getsourcelines(object)
1140 return ''.join(lines)
File ~/conda/envs/gravitation/lib/python3.10/inspect.py:1121, in getsourcelines(object)
1113 """Return a list of source lines and starting line number for an object.
1114
1115 The argument may be a module, class, method, function, traceback, frame,
(...)
1118 original source file the first line of code was found. An OSError is
1119 raised if the source code cannot be retrieved."""
1120 object = unwrap(object)
-> 1121 lines, lnum = findsource(object)
1123 if istraceback(object):
1124 object = object.tb_frame
File ~/conda/envs/gravitation/lib/python3.10/inspect.py:940, in findsource(object)
932 def findsource(object):
933 """Return the entire source file and starting line number for an object.
934
935 The argument may be a module, class, method, function, traceback, frame,
936 or code object. The source code is returned as a list of all the lines
937 in the file and the line number indexes a line in that list. An OSError
938 is raised if the source code cannot be retrieved."""
--> 940 file = getsourcefile(object)
941 if file:
942 # Invalidate cache if needed.
943 linecache.checkcache(file)
File ~/conda/envs/gravitation/lib/python3.10/inspect.py:817, in getsourcefile(object)
813 def getsourcefile(object):
814 """Return the filename that can be used to locate an object's source.
815 Return None if no way can be identified to get the source.
816 """
--> 817 filename = getfile(object)
818 all_bytecode_suffixes = importlib.machinery.DEBUG_BYTECODE_SUFFIXES[:]
819 all_bytecode_suffixes += importlib.machinery.OPTIMIZED_BYTECODE_SUFFIXES[:]
File ~/conda/envs/gravitation/lib/python3.10/inspect.py:778, in getfile(object)
776 if getattr(object, '__file__', None):
777 return object.__file__
--> 778 raise TypeError('{!r} is a built-in module'.format(object))
779 if isclass(object):
780 if hasattr(object, '__module__'):
TypeError: <module '__main__'> is a built-in module
How can we reproduce the bug?
Inside a Jupyter notebook or IPython, do:
from typeguard import typechecked
@typechecked
def foo(bar: str):
print(bar)
foo('Hello world!')
Expected outcome: "Hello world" gets printed.
Actual outcome: Exception TypeError: <module '__main__'> is a built-in module
EDIT: Also happens in IPython.
Yes, @typechecked
requires the source code of the module to be available. I admit that in this case, the error message is somewhat misleading and could be improved.
Thanks for the clarification.
I remember doing prototyping in Jupyter notebooks with earlier versions of typeguard
- and it worked, if I recall correctly. Is there a chance / potential way to make this work (again)?
This would require maintaining two completely different modes of operation, and I don't think I want to do that. The biggest intended use case for typeguard is to complement static type checkers in catching type violations that static checkers cannot check for.
What is the intended use case for using @typechecked
in a Jupyter notebook?
This would require maintaining two completely different modes of operation [...]
Just being curious: Could you point me to the relevant sections of code that would need to be extended?
What is the intended use case for using
@typechecked
in a Jupyter notebook?
Prototyping, testing etc. When exploring a new data set for instance, I use Jupyter notebooks plus annotated functions & classes plus typechecked
to see if my assumptions about the data and processing strategy are correct. When I prototype a piece of code, anything literally, I start in notebooks to figure out the different components and their interfaces / APIs. typechecked
is really essential for this kind of workflow as it saves tons of assert isinstance
checks and similar. It does not go as far as pydantic
which can basically also do interval checks etc, but instead it sits exactly at the right level to get interfaces right. The bonus is that when I am transplanting the prototypes into my actual projects, I keep the typechecked
decorators and simply deactivate them via an environment variable for extra performance - or activate them during testing or debugging. I am aware that typechecked
is not the only way I can activate typeguard
in my projects - I prefer it though because I can intentionally remove it from certain functions or classes where it may not be relevant or cause crashes (meta class "magic" plus typeguard
is usually a bad idea but also an edge case anyway).
Some notes:
- https://github.com/ipython/ipython/issues/11249
- https://stackoverflow.com/q/51566497/1672565
- https://bugs.python.org/issue33826
I'm not very comfortable adding a hack that only works for IPython and not the usual Python REPL.
I'm not very comfortable adding a hack that only works for IPython and not the usual Python REPL.
If IPython had a dedicated (and tested / maintained) API for this, it would probably be a big step forward compared to maintaining a hack like this "manually", yep. I pinged folks over there. If there was such an API, would you consider using it?
I think I might rather reintroduce a wrapper decorator.
There are quite a lot of people who encountered this issue when using jaxtyping, which is mostly used in IPython notebooks. For example, this one, or this one.
@agronholm, to me it seems like the best approach would be to address this issue as a hack in the typeguard, since I think it would significantly reduce the number of people having this issue.
Otherwise, maybe you could explicitly state that you don't intend to introduce a fix in the nearest future, so that jaxtyping/other libraries that use IPython with typeguard would know that this is not going to be fixed soon, and would remove the typeguard as the default choice of the typechecker? E.g. currently, jaxtyping just uses an older version of typeguard, which is quite a bit undesirable.
Are you sure you commented on the right issue? This one is about the inability to use @typechecked
in a REPL.
I am quite sure that it is the right issue. For example, you mentioned that somewhen in May:
I'm not very comfortable adding a hack that only works for IPython and not the usual Python REPL.
which is related to the part about "hack" specific for IPython and not REPL.
Also, even the starting comment on this issue mentions that it is related to IPython:
EDIT: Also happens in IPython.
Besides, it is the only open issue that is related to IPython, and it is exactly the issue that jaxtyping is having. For example, you could look at this link.
I could open a new issue though, if you consider this to be significantly different from the topic of this issue.
The jaxtyping
and IPython
issues have nothing whatsoever to do with each other. The jaxtyping
related issue you're probably thinking of is described here: https://github.com/agronholm/typeguard/issues/353. That issue is the bigger one, and has to do with whether quoted type parameters beyond the first one should be considered to be forward references or not. I've made some progress on finding a solution to that, but the work is still very much in an experimental stage.
As far is this particular issue goes, if I were to make @typechecked
wrap the callables again, this would again break the use case with click
(issue here). I went to great pains to make that use case to work, hence my hesitation to backpedal on the logic.
The only thing I can think of that would work in both cases would be making a separate decorator that always wraps the target callable rather than recompiling it.
EDIT: Or alternatively, adding this as an option, and defaulting to wrapping if the source cannot be found.
The
jaxtyping
andIPython
issues have nothing whatsoever to do with each other. Thejaxtyping
related issue you're probably thinking of is described here: #353. That issue is the bigger one, and has to do with whether quoted type parameters beyond the first one should be considered to be forward references or not. I've made some progress on finding a solution to that, but the work is still very much in an experimental stage.As far is this particular issue goes, if I were to make
@typechecked
wrap the callables again, this would again break the use case withclick
(issue here). I went to great pains to make that use case to work, hence my hesitation to backpedal on the logic.The only thing I can think of that would work in both cases would be making a separate decorator that always wraps the target callable rather than recompiling it.
EDIT: Or alternatively, adding this as an option, and defaulting to wrapping if the source cannot be found.
Ah, alright, sorry. Probably I should have dug in deeper. Thanks for your work!