Repository: spyder-ide/spyder

Issue:
KeyError when renaming file in the editor
## Description

### What steps will reproduce the problem?

<!--- You can use Markdown here --->

rename package name and problem encounterd, it is not the first time.

### Traceback
```python-traceback
  File "/Users/michaelf/opt/anaconda3/envs/conda-env/lib/python3.7/site-packages/spyder/plugins/editor/plugin.py", line 2175, in renamed_tree
    self.renamed(source=fname, dest=new_filename)
  File "/Users/michaelf/opt/anaconda3/envs/conda-env/lib/python3.7/site-packages/spyder/plugins/editor/plugin.py", line 2166, in renamed
    filename, to_text_string(dest))
  File "/Users/michaelf/opt/anaconda3/envs/conda-env/lib/python3.7/site-packages/spyder/plugins/editor/utils/autosave.py", line 422, in file_renamed
    old_hash = self.file_hashes[old_name]
KeyError: '/Users/michaelf/momentum/ml-fit-model/pareto-model/fit-kmeans-model/run_script.py'
ERROR:spyder.plugins.editor.utils.autosave:KeyError when retrieving hash of /Users/michaelf/momentum/ml-fit-model/pareto-model/pareto-model/run_script.py
ERROR:spyder.plugins.editor.utils.autosave:KeyError when retrieving hash of /Users/michaelf/momentum/ml-fit-model/pareto-model/pareto-model/run_script.py
```

## Versions

* Spyder version: 4.1.1 
* Python version: 3.7.6
* Qt version: 5.12.5
* PyQt5 version: 5.12.3
* Operating System: Darwin 19.4.0

### Dependencies

```

# Mandatory:
applaunchservices >=0.1.7      :  0.2.1 (OK)
atomicwrites >=1.2.0           :  1.3.0 (OK)
chardet >=2.0.0                :  3.0.4 (OK)
cloudpickle >=0.5.0            :  1.3.0 (OK)
diff_match_patch >=20181111    :  20181111 (OK)
intervaltree                   :  None (OK)
IPython >=4.0                  :  7.13.0 (OK)
jedi =0.15.2                   :  0.15.2 (OK)
nbconvert >=4.0                :  5.6.1 (OK)
numpydoc >=0.6.0               :  0.9.2 (OK)
parso =0.5.2                   :  0.5.2 (OK)
pexpect >=4.4.0                :  4.8.0 (OK)
pickleshare >=0.4              :  0.7.5 (OK)
psutil >=5.3                   :  5.7.0 (OK)
pygments >=2.0                 :  2.6.1 (OK)
pylint >=0.25                  :  2.4.4 (OK)
pyls >=0.31.9;<0.32.0          :  0.31.10 (OK)
qdarkstyle >=2.8               :  2.8.1 (OK)
qtawesome >=0.5.7              :  0.7.1 (OK)
qtconsole >=4.6.0              :  4.7.3 (OK)
qtpy >=1.5.0                   :  1.9.0 (OK)
rtree >=0.8.3                  :  0.9.4 (OK)
sphinx >=0.6.6                 :  3.0.1 (OK)
spyder_kernels >=1.9.0;<1.10.0 :  1.9.0 (OK)
watchdog                       :  None (OK)
zmq >=17                       :  19.0.0 (OK)

# Optional:
cython >=0.21                  :  None (OK)
matplotlib >=2.0.0             :  None (OK)
numpy >=1.7                    :  1.18.1 (OK)
pandas >=0.13.1                :  1.0.3 (OK)
scipy >=0.17.0                 :  None (OK)
sympy >=0.7.3                  :  None (OK)
```



Discussion:
@jitseniesen, this bug looks legit. Could you take a look at it?
@michael-fridman1 What do you mean with "rename package name"? Did you rename a .py file, presumably '/Users/michaelf/momentum/ml-fit-model/pareto-model/fit-kmeans-model/run_script.py, or one of the directories? Do you remember how you did the rename?
I guess we need to do something similar to PR #11647. For some mysterious reason, the hash of a file is not recorded in the autosave system, leading to this KeyError. We need to handle this. It shouldn't be too hard so I'll have something ready in a few days. I just don't like it that I can't find the root cause.
Thanks @jitseniesen!

Resolving patch:
diff --git a/spyder/plugins/editor/utils/autosave.py b/spyder/plugins/editor/utils/autosave.py
--- a/spyder/plugins/editor/utils/autosave.py
+++ b/spyder/plugins/editor/utils/autosave.py
@@ -419,9 +419,16 @@ def file_renamed(self, old_name, new_name):
             old_name (str): name of file before it is renamed
             new_name (str): name of file after it is renamed
         """
-        old_hash = self.file_hashes[old_name]
+        try:
+            old_hash = self.file_hashes[old_name]
+        except KeyError:
+            # This should not happen, but it does: spyder-ide/spyder#12396
+            logger.error('KeyError when handling rename %s -> %s',
+                         old_name, new_name)
+            old_hash = None
         self.remove_autosave_file(old_name)
-        del self.file_hashes[old_name]
-        self.file_hashes[new_name] = old_hash
+        if old_hash is not None:
+            del self.file_hashes[old_name]
+            self.file_hashes[new_name] = old_hash
         index = self.stack.has_filename(new_name)
         self.maybe_autosave(index)


Test patch:
diff --git a/spyder/plugins/editor/utils/tests/test_autosave.py b/spyder/plugins/editor/utils/tests/test_autosave.py
--- a/spyder/plugins/editor/utils/tests/test_autosave.py
+++ b/spyder/plugins/editor/utils/tests/test_autosave.py
@@ -237,7 +237,8 @@ def test_autosave_remove_autosave_file(mocker, exception):
     assert mock_dialog.called == exception
 
 
-def test_autosave_file_renamed(mocker, tmpdir):
+@pytest.mark.parametrize('have_hash', [True, False])
+def test_autosave_file_renamed(mocker, tmpdir, have_hash):
     """Test that AutosaveForStack.file_renamed removes the old autosave file,
     creates a new one, and updates `name_mapping` and `file_hashes`."""
     mock_remove = mocker.patch('os.remove')
@@ -256,6 +257,10 @@ def test_autosave_file_renamed(mocker, tmpdir):
     new_autosavefile = str(tmpdir.join('new_foo.py'))
     addon.name_mapping = {'old_foo.py': old_autosavefile}
     addon.file_hashes = {'old_foo.py': 1, old_autosavefile: 42}
+    if have_hash:
+        addon.file_hashes = {'old_foo.py': 1, old_autosavefile: 42}
+    else:
+        addon.file_hashes = {old_autosavefile: 42}
 
     addon.file_renamed('old_foo.py', 'new_foo.py')
 
@@ -263,7 +268,10 @@ def test_autosave_file_renamed(mocker, tmpdir):
     mock_stack._write_to_file.assert_called_with(
         mock_fileinfo, new_autosavefile)
     assert addon.name_mapping == {'new_foo.py': new_autosavefile}
-    assert addon.file_hashes == {'new_foo.py': 1, new_autosavefile: 3}
+    if have_hash:
+        assert addon.file_hashes == {'new_foo.py': 1, new_autosavefile: 3}
+    else:
+        assert addon.file_hashes == {new_autosavefile: 3}
 
 
 if __name__ == "__main__":