Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

typechecked in a Jupyter notebook / IPython always triggers TypeError: <module '__main__'> is a built-in module #364

Open
2 tasks done
s-m-e opened this issue May 28, 2023 · 13 comments
Labels

Comments

@s-m-e
Copy link

s-m-e commented May 28, 2023

Things to check first

  • I have searched the existing issues and didn't find my bug already reported there

  • 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.

@s-m-e s-m-e added the bug label May 28, 2023
@s-m-e s-m-e changed the title typechecked in a Jupyter notebook always triggers TypeError: <module '__main__'> is a built-in module typechecked in a Jupyter notebook / IPython always triggers TypeError: <module '__main__'> is a built-in module May 28, 2023
@agronholm
Copy link
Owner

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.

@s-m-e
Copy link
Author

s-m-e commented May 29, 2023

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)?

@agronholm
Copy link
Owner

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?

@s-m-e
Copy link
Author

s-m-e commented May 29, 2023

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).

@agronholm
Copy link
Owner

I'm not very comfortable adding a hack that only works for IPython and not the usual Python REPL.

@s-m-e
Copy link
Author

s-m-e commented May 29, 2023

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?

@agronholm
Copy link
Owner

I think I might rather reintroduce a wrapper decorator.

EliahKagan added a commit to EliahKagan/codegraph that referenced this issue Jun 2, 2023
This seems to have stopped working in major version 3 (based on
my testing in this project). The current major version of typeguard
is 4. The issue looks like:

agronholm/typeguard#364

This should probably only be a temporary solution. There are other
libraries I could use. But also, it's not obvious that the added
clarity of presentation from defining the top-level functions in
the notebooks that use them, rather than in modules as I would
ordinarily do, is justified, even in the absence of this issue. So
it may be worthwhile to revisit that design decision for codegraph.
But for now I'm keeping that as it is and holding back typeguard.

This commit also updates typing-extensions to the next patch
version.
EliahKagan added a commit to EliahKagan/codegraph that referenced this issue Jun 2, 2023
This seems to have stopped working in major version 3 (based on
my testing in this project). The current major version of typeguard
is 4. The issue looks like:

agronholm/typeguard#364

This should probably only be a temporary solution. There are other
libraries I could use. But also, it's not obvious that the added
clarity of presentation from defining the top-level functions in
the notebooks that use them, rather than in modules as I would
ordinarily do, is justified, even in the absence of this issue. So
it may be worthwhile to revisit that design decision for codegraph.
But for now I'm keeping that as it is and holding back typeguard.

This commit also updates typing-extensions to the next patch
version.
EliahKagan added a commit to EliahKagan/codegraph that referenced this issue Jun 2, 2023
But I'm keeping typeguard back at major version 2 for now, as
detailed below.

* Update dependencies

* Keep typeguard back so typechecked works in notebooks

This seems to have stopped working in major version 3 (based on
my testing in this project). The current major version of typeguard
is 4. The issue looks like:

agronholm/typeguard#364

This should probably only be a temporary solution. There are other
libraries I could use. But also, it's not obvious that the added
clarity of presentation from defining the top-level functions in
the notebooks that use them, rather than in modules as I would
ordinarily do, is justified, even in the absence of this issue. So
it may be worthwhile to revisit that design decision for codegraph.
But for now I'm keeping that as it is and holding back typeguard.

This commit also updates typing-extensions to the next patch
version.

* Re-run notebooks
ddorn added a commit to EffiSciencesResearch/ML4G that referenced this issue Aug 28, 2023
It should be used once the newest version of torchtyping works with
jupyter, because the error reporting in the old versions for jaxtyping
are bad (don't tell the shape).

See:
- agronholm/typeguard#364
- patrick-kidger/jaxtyping#6
@knyazer
Copy link

knyazer commented Sep 20, 2023

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.

@agronholm
Copy link
Owner

Are you sure you commented on the right issue? This one is about the inability to use @typechecked in a REPL.

@knyazer
Copy link

knyazer commented Sep 20, 2023

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.

@agronholm
Copy link
Owner

agronholm commented Sep 20, 2023

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: #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.

@knyazer
Copy link

knyazer commented Sep 20, 2023

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: #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.

Ah, alright, sorry. Probably I should have dug in deeper. Thanks for your work!

ddorn added a commit to EffiSciencesResearch/ML4G that referenced this issue Mar 7, 2024
It should be used once the newest version of torchtyping works with
jupyter, because the error reporting in the old versions for jaxtyping
are bad (don't tell the shape).

See:
- agronholm/typeguard#364
- patrick-kidger/jaxtyping#6

Former-commit-id: aab201f
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants