Repository: wandb/wandb

Issue:
AttributeError: 'PyDevTerminalInteractiveShell' object has no attribute 'kernel'

wandb, version 0.8.34
Python 3.7.4
Linux


### Description

Using `wandb` with PyCharm in the interactive terminal. It seems that `wandb` is detecting the interactive terminal as a `jupyter` environment. PyCharm uses the pydev console, which is kinda like Jupyter, but not 100% compatible.

Happened upon upgrade from `0.8.33` -> `0.8.34`.

```
Error in callback <function _init_jupyter.<locals>.cleanup at 0x7f612c1905f0> (for post_run_cell):
Traceback (most recent call last):
  File "/home/mccoy/.local/share/virtualenvs/training-gym-ZeodOefO/lib/python3.7/site-packages/IPython/core/events.py", line 88, in trigger
    func(*args, **kwargs)
  File "/home/mccoy/.local/share/virtualenvs/training-gym-ZeodOefO/lib/python3.7/site-packages/backcall/backcall.py", line 104, in adapted
    return callback(*args, **kwargs)
  File "/home/mccoy/.local/share/virtualenvs/training-gym-ZeodOefO/lib/python3.7/site-packages/wandb/__init__.py", line 502, in cleanup
    run._stop_jupyter_agent()
  File "/home/mccoy/.local/share/virtualenvs/training-gym-ZeodOefO/lib/python3.7/site-packages/wandb/wandb_run.py", line 185, in _stop_jupyter_agent
    self._jupyter_agent.stop()
  File "/home/mccoy/.local/share/virtualenvs/training-gym-ZeodOefO/lib/python3.7/site-packages/wandb/jupyter.py", line 139, in stop
    self.save_history()
  File "/home/mccoy/.local/share/virtualenvs/training-gym-ZeodOefO/lib/python3.7/site-packages/wandb/jupyter.py", line 185, in save_history
    'language_info': self.shell.kernel.language_info
AttributeError: 'PyDevTerminalInteractiveShell' object has no attribute 'kernel'
```



Discussion:
Ahh, thanks for letting us know.  We'll work on a new release that handles the PyCharm shell.

Resolving patch:
diff --git a/wandb/jupyter.py b/wandb/jupyter.py
--- a/wandb/jupyter.py
+++ b/wandb/jupyter.py
@@ -170,22 +170,29 @@ def save_history(self):
                 outputs = []
             if self.outputs.get(execution_count):
                 for out in self.outputs[execution_count]:
-                    outputs.append(v4.new_output(output_type="display_data", data=out["data"], metadata=out["metadata"]))
+                    outputs.append(v4.new_output(output_type="display_data", data=out["data"], metadata=out["metadata"] or {}))
             cells.append(v4.new_code_cell(
                 execution_count=execution_count,
                 source=exc[0],
                 outputs=outputs
             ))
-        nb = v4.new_notebook(cells=cells, metadata={
-            'kernelspec': {
-                'display_name': 'Python %i' % sys.version_info[0],
-                'name': 'python%i' % sys.version_info[0],
-                'language': 'python'
-            },
-            'language_info': self.shell.kernel.language_info
-        })
-        state_path = os.path.join("code", "_session_history.ipynb")
         try:
+            if hasattr(self.shell, "kernel"):
+                language_info = self.shell.kernel.language_info
+            else:
+                language_info = {
+                    'name': "python",
+                    "version": sys.version
+                }
+            nb = v4.new_notebook(cells=cells, metadata={
+                'kernelspec': {
+                    'display_name': 'Python %i' % sys.version_info[0],
+                    'name': 'python%i' % sys.version_info[0],
+                    'language': 'python'
+                },
+                'language_info': language_info
+            })
+            state_path = os.path.join("code", "_session_history.ipynb")
             wandb.run.config._set_wandb("session_history", state_path)
             wandb.run.config.persist()
             wandb.util.mkdir_exists_ok(os.path.join(wandb.run.dir, "code"))
diff --git a/wandb/run_manager.py b/wandb/run_manager.py
--- a/wandb/run_manager.py
+++ b/wandb/run_manager.py
@@ -278,7 +278,8 @@ def _update(self):
 
     def finish(self):
         if self._thread:
-            self._thread.join()
+            # Cancel the current thread to keep moving
+            self._thread.cancel()
             self._thread = None
 
         self._update()


Test patch: