Repository: spyder-ide/spyder
Pull request: PR: Enclose contents of load_more_data method in try/except NameError Block
<!--- Make sure to read the Contributing Guidelines:                   --->
<!--- https://github.com/spyder-ide/spyder/blob/master/CONTRIBUTING.md --->
<!--- and follow PEP 8, PEP 257 and Spyder's code style:               --->
<!--- https://github.com/spyder-ide/spyder/wiki/Dev:-Coding-Style      --->

## Description of Changes

* [ ] Wrote at least one-line docstrings (for any new functions)
* [ ] Added unit test(s) covering the changes (if testable)
* [ ] Included a screenshot (if affecting the UI)

<!--- Explain what you've done and why --->

Enclosed the entire contents of the load_more_data method in a try/except NameError block to solve issue 7880


### Issue(s) Resolved

<!--- List the issue(s) below, in the form "Fixes #1234"; one per line --->

Fixes #7880 


### Affirmation

By submitting this Pull Request or typing my (user)name below,
I affirm the [Developer Certificate of Origin](https://developercertificate.org)
with respect to all commits and content included in this PR,
and understand I am releasing the same under Spyder's MIT (Expat) license.

<!--- TYPE YOUR USER/NAME AFTER THE FOLLOWING: --->
I certify the above statement is true and correct:
Juanis2112
<!--- Thanks for your help making Spyder better for everyone! --->

Problem statement:
Error when closing dataframe while data is being fetched
## Description

### What steps will reproduce the problem?

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

I was using the data viewer to open a pandas DataFrame with shape = (1788, 3), after I closed the data viewer, this error appeared.

### Traceback
```python-traceback
  File "C:\ProgramData\Anaconda3\lib\site-packages\spyder\widgets\variableexplorer\dataframeeditor.py", line 513, in <lambda>
    lambda val: self.load_more_data(val, rows=True))
NameError: free variable 'self' referenced before assignment in enclosing scope
```

## Versions

* Spyder version: 3.3.1 
* Python version: 3.6.6
* Qt version: 5.9.6
* PyQt5 version: 5.9.2
* Operating System: Windows 10

### Dependencies

```
pyflakes >=0.6.0  :  2.0.0 (OK)
pycodestyle >=2.3 :  2.4.0 (OK)
pygments >=2.0    :  2.2.0 (OK)
sphinx >=0.6.6    :  1.7.8 (OK)
rope >=0.9.4      :  0.11.0 (OK)
jedi >=0.9.0      :  0.12.1 (OK)
nbconvert >=4.0   :  5.3.1 (OK)
pandas >=0.13.1   :  0.23.4 (OK)
numpy >=1.7       :  1.15.1 (OK)
sympy >=0.7.3     :  1.2 (OK)
cython >=0.21     :  0.28.5 (OK)
qtconsole >=4.2.0 :  4.4.1 (OK)
IPython >=4.0     :  6.5.0 (OK)
matplotlib >=2.0.0:  2.2.3 (OK)
pylint >=0.25     :  2.1.1 (OK)
```

Hints from issue discussion:
Thanks for reporting. We'll take a look at this in a future release.
Note: This happens when users are scrolling a big dataframe and closing it before it gets all the data that's being fetched.
@juanis2112, this is an easy one for you. Please enclose the entire contents of the `load_more_data` method, placed here:

https://github.com/spyder-ide/spyder/blob/master/spyder/plugins/variableexplorer/widgets/dataframeeditor.py#L512

in a `try/except NameError` block.

And also do the same for the `load_more_data` method in arrayeditor, here:

https://github.com/spyder-ide/spyder/blob/master/spyder/plugins/variableexplorer/widgets/arrayeditor.py#L420

Commit message: Enclose contents of load_more_data method in try/except NameError Block

Patch to review:
diff --git a/spyder/plugins/variableexplorer/widgets/arrayeditor.py b/spyder/plugins/variableexplorer/widgets/arrayeditor.py
index 261bd081384..eec7508f187 100644
--- a/spyder/plugins/variableexplorer/widgets/arrayeditor.py
+++ b/spyder/plugins/variableexplorer/widgets/arrayeditor.py
@@ -418,39 +418,44 @@ def __init__(self, parent, model, dtype, shape):
                                lambda val: self.load_more_data(val, rows=True))
     
     def load_more_data(self, value, rows=False, columns=False):
-        old_selection = self.selectionModel().selection()
-        old_rows_loaded = old_cols_loaded = None
         
-        if rows and value == self.verticalScrollBar().maximum():
-            old_rows_loaded = self.model().rows_loaded
-            self.model().fetch_more(rows=rows)
-            
-        if columns and value == self.horizontalScrollBar().maximum():
-            old_cols_loaded = self.model().cols_loaded
-            self.model().fetch_more(columns=columns)
+        try:
+            old_selection = self.selectionModel().selection()
+            old_rows_loaded = old_cols_loaded = None
             
-        if old_rows_loaded is not None or old_cols_loaded is not None:
-            # if we've changed anything, update selection
-            new_selection = QItemSelection()
-            for part in old_selection:
-                top = part.top()
-                bottom = part.bottom()
-                if (old_rows_loaded is not None and 
-                    top == 0 and bottom == (old_rows_loaded-1)):
-                    # complete column selected (so expand it to match updated range)
-                    bottom = self.model().rows_loaded-1
-                left = part.left()
-                right = part.right()
-                if (old_cols_loaded is not None
-                    and left == 0 and right == (old_cols_loaded-1)):
-                    # compete row selected (so expand it to match updated range)
-                    right = self.model().cols_loaded-1
-                top_left = self.model().index(top, left)
-                bottom_right = self.model().index(bottom, right)
-                part = QItemSelectionRange(top_left, bottom_right)
-                new_selection.append(part)
-            self.selectionModel().select(new_selection, self.selectionModel().ClearAndSelect)
-                    
+            if rows and value == self.verticalScrollBar().maximum():
+                old_rows_loaded = self.model().rows_loaded
+                self.model().fetch_more(rows=rows)
+                
+            if columns and value == self.horizontalScrollBar().maximum():
+                old_cols_loaded = self.model().cols_loaded
+                self.model().fetch_more(columns=columns)
+                
+            if old_rows_loaded is not None or old_cols_loaded is not None:
+                # if we've changed anything, update selection
+                new_selection = QItemSelection()
+                for part in old_selection:
+                    top = part.top()
+                    bottom = part.bottom()
+                    if (old_rows_loaded is not None and 
+                        top == 0 and bottom == (old_rows_loaded-1)):
+                        # complete column selected (so expand it to match updated range)
+                        bottom = self.model().rows_loaded-1
+                    left = part.left()
+                    right = part.right()
+                    if (old_cols_loaded is not None
+                        and left == 0 and right == (old_cols_loaded-1)):
+                        # compete row selected (so expand it to match updated range)
+                        right = self.model().cols_loaded-1
+                    top_left = self.model().index(top, left)
+                    bottom_right = self.model().index(bottom, right)
+                    part = QItemSelectionRange(top_left, bottom_right)
+                    new_selection.append(part)
+                self.selectionModel().select(new_selection, self.selectionModel().ClearAndSelect)
+
+        # Needed to handle a NameError while fetching data when closing
+        except NameError:
+            pass
 
     def resize_to_contents(self):
         """Resize cells to contents"""
diff --git a/spyder/plugins/variableexplorer/widgets/dataframeeditor.py b/spyder/plugins/variableexplorer/widgets/dataframeeditor.py
index 1f93e69957c..06f4d5a7cdc 100644
--- a/spyder/plugins/variableexplorer/widgets/dataframeeditor.py
+++ b/spyder/plugins/variableexplorer/widgets/dataframeeditor.py
@@ -511,12 +511,16 @@ def __init__(self, parent, model, header, hscroll, vscroll):
 
     def load_more_data(self, value, rows=False, columns=False):
         """Load more rows and columns to display."""
-        if rows and value == self.verticalScrollBar().maximum():
-            self.model().fetch_more(rows=rows)
-            self.sig_fetch_more_rows.emit()
-        if columns and value == self.horizontalScrollBar().maximum():
-            self.model().fetch_more(columns=columns)
-            self.sig_fetch_more_columns.emit()
+        try:
+            if rows and value == self.verticalScrollBar().maximum():
+                self.model().fetch_more(rows=rows)
+                self.sig_fetch_more_rows.emit()
+            if columns and value == self.horizontalScrollBar().maximum():
+                self.model().fetch_more(columns=columns)
+                self.sig_fetch_more_columns.emit()
+        # Needed to handle a NameError while fetching data when closing
+        except NameError:
+            pass
 
     def sortByColumn(self, index):
         """Implement a column sort."""


Code review comments:
diff_hunk: @@ -511,12 +511,16 @@ def __init__(self, parent, model, header, hscroll, vscroll):
 
     def load_more_data(self, value, rows=False, columns=False):
         """Load more rows and columns to display."""
-        if rows and value == self.verticalScrollBar().maximum():
-            self.model().fetch_more(rows=rows)
-            self.sig_fetch_more_rows.emit()
-        if columns and value == self.horizontalScrollBar().maximum():
-            self.model().fetch_more(columns=columns)
-            self.sig_fetch_more_columns.emit()
+        try:
+            if rows and value == self.verticalScrollBar().maximum():
+                self.model().fetch_more(rows=rows)
+                self.sig_fetch_more_rows.emit()
+            if columns and value == self.horizontalScrollBar().maximum():
+                self.model().fetch_more(columns=columns)
+                self.sig_fetch_more_columns.emit()
+        # Needed to handle a NameError while fetching data when closing
path: spyder/plugins/variableexplorer/widgets/dataframeeditor.py
text: @user1:
Please add the number of the issue here too, and move the comment line inside the `except` block as well :)

diff_hunk: @@ -418,39 +418,44 @@ def __init__(self, parent, model, dtype, shape):
                                lambda val: self.load_more_data(val, rows=True))
     
     def load_more_data(self, value, rows=False, columns=False):
-        old_selection = self.selectionModel().selection()
-        old_rows_loaded = old_cols_loaded = None
         
-        if rows and value == self.verticalScrollBar().maximum():
-            old_rows_loaded = self.model().rows_loaded
-            self.model().fetch_more(rows=rows)
-            
-        if columns and value == self.horizontalScrollBar().maximum():
-            old_cols_loaded = self.model().cols_loaded
-            self.model().fetch_more(columns=columns)
+        try:
+            old_selection = self.selectionModel().selection()
+            old_rows_loaded = old_cols_loaded = None
             
-        if old_rows_loaded is not None or old_cols_loaded is not None:
-            # if we've changed anything, update selection
-            new_selection = QItemSelection()
-            for part in old_selection:
-                top = part.top()
-                bottom = part.bottom()
-                if (old_rows_loaded is not None and 
-                    top == 0 and bottom == (old_rows_loaded-1)):
-                    # complete column selected (so expand it to match updated range)
-                    bottom = self.model().rows_loaded-1
-                left = part.left()
-                right = part.right()
-                if (old_cols_loaded is not None
-                    and left == 0 and right == (old_cols_loaded-1)):
-                    # compete row selected (so expand it to match updated range)
-                    right = self.model().cols_loaded-1
-                top_left = self.model().index(top, left)
-                bottom_right = self.model().index(bottom, right)
-                part = QItemSelectionRange(top_left, bottom_right)
-                new_selection.append(part)
-            self.selectionModel().select(new_selection, self.selectionModel().ClearAndSelect)
-                    
+            if rows and value == self.verticalScrollBar().maximum():
+                old_rows_loaded = self.model().rows_loaded
+                self.model().fetch_more(rows=rows)
+                
+            if columns and value == self.horizontalScrollBar().maximum():
+                old_cols_loaded = self.model().cols_loaded
+                self.model().fetch_more(columns=columns)
+                
+            if old_rows_loaded is not None or old_cols_loaded is not None:
+                # if we've changed anything, update selection
+                new_selection = QItemSelection()
+                for part in old_selection:
+                    top = part.top()
+                    bottom = part.bottom()
+                    if (old_rows_loaded is not None and 
+                        top == 0 and bottom == (old_rows_loaded-1)):
+                        # complete column selected (so expand it to match updated range)
+                        bottom = self.model().rows_loaded-1
+                    left = part.left()
+                    right = part.right()
+                    if (old_cols_loaded is not None
+                        and left == 0 and right == (old_cols_loaded-1)):
+                        # compete row selected (so expand it to match updated range)
+                        right = self.model().cols_loaded-1
+                    top_left = self.model().index(top, left)
+                    bottom_right = self.model().index(bottom, right)
+                    part = QItemSelectionRange(top_left, bottom_right)
+                    new_selection.append(part)
+                self.selectionModel().select(new_selection, self.selectionModel().ClearAndSelect)
+
+        # Needed to handle a NameError while fetching data when closing
+        except NameError:
path: spyder/plugins/variableexplorer/widgets/arrayeditor.py
text: @user1:
Please also add to the comment the number of the issue, something like `See issue 7880`. Also, please move the comment to the inside of the `except` block