Repository: microsoft/pybryt

Issue:
PyBryt and ipykernel>=6.0 issue
PyBryt did not work with ipykernel>=6.0. As a temporary fix, I fixed ipykernel to 5.5.5 in `requirements.txt` before one of the releases a few months ago. I hoped this problem could resolve itself on its own when ipykernel matures, but it did not.

Discussion:

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a33c66f..ec76914 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,7 @@ All notable changes to this project will be documented in this file, and this pr
 * Added custom equivalence functions for value annotations per [#113](https://github.com/microsoft/pybryt/issues/113)
 * Removed the messages section from reports with no messages
 * Added debug mode per [#116](https://github.com/microsoft/pybryt/issues/116)
+* Fixed ipykernel v6 issue and unpinned ipykernel per [#114](https://github.com/microsoft/pybryt/issues/114)
 
 ## 0.2.0 2021-09-01
 
diff --git a/pybryt/execution/tracing.py b/pybryt/execution/tracing.py
index 03b44f3..13cb51c 100644
--- a/pybryt/execution/tracing.py
+++ b/pybryt/execution/tracing.py
@@ -8,6 +8,8 @@ from copy import copy
 from types import FrameType, FunctionType, ModuleType
 from typing import Any, List, Tuple, Callable
 
+from .utils import is_ipython_frame
+
 from ..utils import make_secret, pickle_and_hash
 
 
@@ -85,7 +87,7 @@ def create_collector(skip_types: List[type] = [type, type(len), ModuleType, Func
         """
         Trace function for PyBryt.
         """
-        if frame.f_code.co_filename.startswith("<ipython") or frame.f_code.co_filename in addl_filenames:
+        if is_ipython_frame(frame) or frame.f_code.co_filename in addl_filenames:
             counter[0] += 1 # increment student code step counter
 
         if event == "call":
@@ -99,7 +101,7 @@ def create_collector(skip_types: List[type] = [type, type(len), ModuleType, Func
 
         name = frame.f_code.co_filename + frame.f_code.co_name
 
-        if frame.f_code.co_filename.startswith("<ipython") or frame.f_code.co_filename in addl_filenames:
+        if is_ipython_frame(frame) or frame.f_code.co_filename in addl_filenames:
             if event == "line" or event == "return":
 
                 line = linecache.getline(frame.f_code.co_filename, frame.f_lineno)
@@ -135,8 +137,8 @@ def create_collector(skip_types: List[type] = [type, type(len), ModuleType, Func
             if event == "return":
                 track_value(arg)
 
-        elif (frame.f_back.f_code.co_filename.startswith("<ipython") or \
-                frame.f_back.f_code.co_filename in addl_filenames) and event == "return":
+        elif (is_ipython_frame(frame) or frame.f_back.f_code.co_filename in addl_filenames) and \
+                event == "return":
             track_value(arg)
 
         if event == "return" and name in vars_not_found:
diff --git a/pybryt/execution/utils.py b/pybryt/execution/utils.py
new file mode 100644
index 0000000..cf5078e
--- /dev/null
+++ b/pybryt/execution/utils.py
@@ -0,0 +1,20 @@
+"""Execution utilities"""
+
+import os
+
+from types import FrameType
+
+
+def is_ipython_frame(frame: FrameType) -> bool:
+    """
+    Determine whether a frame is being executed by IPython.
+
+    Args:
+        frame (``types.FrameType``): the frame to examine
+
+    Returns:
+        ``bool``: whether the frame is an IPython frame
+    """
+    filename = frame.f_code.co_filename
+    parent_dir = os.path.split(os.path.split(filename)[0])[1]
+    return filename.startswith("<ipython") or parent_dir.startswith("ipykernel_")
diff --git a/requirements.txt b/requirements.txt
index a16f589..1bda40e 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -7,4 +7,4 @@ dill
 click
 astunparse
 Cython
-ipykernel==5.5.5
+ipykernel


Test patch:
diff --git a/tests/execution/test_tracing.py b/tests/execution/test_tracing.py
index a8d26ba..f0fff67 100644
--- a/tests/execution/test_tracing.py
+++ b/tests/execution/test_tracing.py
@@ -131,6 +131,16 @@ def test_trace_function():
     assert calls[0] == ("<ipython-abc123>", "foo")
     assert calls[1] == ("/path/to/foo.py", "bar")
 
+    # test ipykernel v6 frame filename
+    frame = generate_mocked_frame("/var/8k/ipykernel_495995/29304985.py", "foo", 3)
+    (observed, calls), cir = create_collector(addl_filenames=[tracked_filepath])
+
+    arr = np.random.uniform(-100, 100, size=(100, 100))
+    cir(frame, "return", arr)
+    assert len(observed) == 1
+    assert np.allclose(observed[0][0], arr)
+    assert observed[0][1] == 1
+
 
 def test_tracing_control():
     """