Repository: spyder-ide/spyder

Issue:
Unable to save workspace on Linux
<!--- **PLEASE READ:** Before submitting here, please carefully consult our *Troubleshooting Guide*: https://github.com/spyder-ide/spyder/wiki/Troubleshooting-Guide-and-FAQ and search the issues page for your error/problem, as most posted bugs are duplicates or easy fixes.

If you don't find anything, please provide a detailed step-by-step description (in English) of the problem and what led up to it below. Issue reports without a clear way to reproduce them will be closed. Thanks! --->

## Problem Description
Unable to save workspace 
![screenshot from 2018-05-24 17-15-10](https://user-images.githubusercontent.com/30615882/40486617-6e4c54fa-5f7f-11e8-95e0-36d34f377ea1.png)


### What steps will reproduce the problem?
Just create a python list, and try to save using save data icon

## Package Versions

* Spyder version: 3.2.8 
* Python version: 3.6.3
* Qt version: 5.6.2
* PyQt5 version: 5.6
* Operating system: Linux 4.13.0-43-generic

### Dependencies

```
pyflakes >=0.6.0 :  1.5.0 (OK)
pycodestyle >=2.3:  2.3.1 (OK)
pygments >=2.0   :  2.2.0 (OK)
pandas >=0.13.1  :  0.20.1 (OK)
numpy >=1.7      :  1.13.3 (OK)
sphinx >=0.6.6   :  1.5.6 (OK)
rope >=0.9.4     :  0.9.4-1 (OK)
jedi >=0.9.0     :  0.10.2 (OK)
psutil >=0.3     :  5.2.2 (OK)
nbconvert >=4.0  :  5.1.1 (OK)
sympy >=0.7.3    :  1.0 (OK)
cython >=0.21    :  0.25.2 (OK)
qtconsole >=4.2.0:  4.3.0 (OK)
IPython >=4.0    :  5.3.0 (OK)
pylint >=0.25    :  1.6.4 (OK)
```



Discussion:
I also reported this issue 7 months back (#5647), till now not getting solution. This is creating big trouble for me. Please help.

And save option of spyder works for windows, but not working on ubuntu.
Yup, I can reproduce this running the latest ``3.x`` on Fedora 28 LXQt, while it works just fine on my Windows 8.1 machine. Thanks for posting a minimal example to help isolate it.
@ccordoba12 I successfully isolated the problem. It simply appears to be that the file extension ``.spydata`` is not getting added to the filename on Linux, since if I manually add it to the filename in the save file dialog, (``test_data.spydata``) then everything saves and loads perfectly. We can just check in [namespacebrowser](https://github.com/spyder-ide/spyder/blob/3.x/spyder/widgets/variableexplorer/namespacebrowser.py) if ``filename`` doesn't have an extension, and if not, then we add it before trying to save. @ccordoba12 that sound like a good plan or is there a better way?
Yeah, it sounds good. Please proceed with it.
Dear sir, i didn't understand the above discussions. Please help me for
solutions.

On Thu, May 24, 2018 at 10:35 PM, Carlos Cordoba <notifications@github.com>
wrote:

> Yeah, it sounds good. Please proceed with it.
>
> —
> You are receiving this because you authored the thread.
> Reply to this email directly, view it on GitHub
> <https://github.com/spyder-ide/spyder/issues/7196#issuecomment-391789022>,
> or mute the thread
> <https://github.com/notifications/unsubscribe-auth/AdMpSvimGFsveR5xSxepyftimk5EJmVqks5t1uhjgaJpZM4UML0c>
> .
>



-- 
Hitesh Tekchandani
PhD Research Scholar
NIT Raipur
India

@hiteshnitetc As I mentioned,

> if [you] manually add it to the filename in the save file dialog, (``test_data.spydata``) then everything saves and loads perfectly. 

so just type ``.spydata`` after the filename (whatever you want to name it; I used ``test_data`` above) when saving and that'll fix it for now. We'll have a permanent fix in hopefully in the next version; its a very easy solution, I just need to get to it.
@CAM-Gerlach Very thnx. You saved my lot of time. Otherwise i was saving all variable using pickle. I am wondering, why this simple solution i didn't try. 
Glad to hear it! Sometimes the simplest solutions are the easiest to miss; I know that all too well myself.
@CAM-Gerlach I don't know if you have worked on this one yet but I did some research and might have a fix. You can assign me this one if you don't want it.
@bcolsen It should be a pretty simple fix (see my comment above outlining the solution) and I've been pretty busy working on a big grant proposal lately, so you're welcome to go for it.
It's not too bad, but do need a pop up so it doesn't silently overwrite files.
@CAM-Gerlach Now i am unable save data from variable explorer after adding .spydata to file name. 
What version of Spyder, Spyder-Kernels and Python are you using? What changed since it worked? What *specific* error messages/behavior are you getting? Have you upgraded to the latest version of Spyder and Spyder-Kernels (3.3.3 and 0.4.2)? We've implemented some significant improvements in saving and loading ``.spydata`` files.

> Now 

After what, specifically? What did you change/update that resulted in it not working

> after adding .spydata to file name.

Did you try *not* adding ``.spydata``?
@CAM-Gerlach  previously i was using spyder 3.3 , now i updated to 3.3 , it's working , thnx.
> previously i was using spyder 3.3 now i updated to 3.3

@hiteshnitetc ?
@CAM-Gerlach ooh really sorry for typo. i was using 3.2 now upgraded to 3.3
@hiteshnitetc Those still aren't real versions, sorry. Do you mean 3.3.2 to 3.3.3? Or 3.2.x to 3.3.x?
@CAM Gerlach,l yes i mean 3.3.2 to 3.3.3

On Tue, Mar 26, 2019 at 8:39 PM CAM Gerlach <notifications@github.com>
wrote:

> @hiteshnitetc <https://github.com/hiteshnitetc> Those still aren't real
> versions, sorry. Do you mean 3.3.2 to 3.3.3? Or 3.2.x to 3.3.x?
>
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub
> <https://github.com/spyder-ide/spyder/issues/7196#issuecomment-476693160>,
> or mute the thread
> <https://github.com/notifications/unsubscribe-auth/AdMpSmJplFWfUwVSKCh2anfh_x-6eKJBks5vajgTgaJpZM4UML0c>
> .
>


-- 
Hitesh Tekchandani
PhD Research Scholar
NIT Raipur
India

Hi @bcolsen and @ccordoba12,
I could give this one a try if you guys agree.
@arteagac We're always happy for some help! Feel free to go ahead with a PR, and let us know if you have questions. Thanks!
Awesome! I'll work on it.
@arteagac This is a Spyder wide problem that will happen with every save dialogue on linux not just with the data explorer.  The qt file picker(only used in linux) doesn't support adding extensions to files based on the extension. 

To fix it we could: 

1. Contribute the feature upstream to qt https://bugreports.qt.io/plugins/servlet/mobile#issue/QTBUG-27186

2. Write a wrapper around the save file dialogue function to add the extension from filterSelected and check for over writes. I couldn't get this to check the name with the file dialogue still open. It would be annoying to click no to the overwrite and then be left without a saved file of a dilague to change it.

3. Figure out how to get native(at least gnome and kde) file selection dialogues on Linux through anaconda and not use the silly qtfiledialogue that rarely actually ever gets used.(I would absolutely love this)

4. Do something else that I didn't think of :-)

This is why I haven't fixed this yet. I hope this helps you find a few directions to look.
Hi @bcolsen, Thank you for such a detailed description of potential fixes for the bug. I think I will start exploring the options 2 and 3 you mentioned and I will let you know how it goes. Thanks again.
Hi @bcolsen,
I did some research and I can see why the issue is tricky.
Here are some thoughts about the potential fixes you mentioned:
1. A related Qt bug report says the issue was fixed on Qt v5.2.0 https://bugreports.qt.io/browse/QTBUG-33156. However, perhaps it does not apply to Linux as we are clearly still facing this issue.
2. This seems like a feasible solution. However, you are right about the undesirable overwrite behavior. To address this, after the user selects the file and the FileDialog closes, we could show a dialog asking the user if they really want to overwrite the existing file. **See the code at the end of this comment.** However, this does not seem like an elegant solution. 
3. I did some research but I could not find a solution for a native KDE/GNOME file dialog. We would need to involve some GTK libraries, which does not seem convenient.

I am not really sure how to proceed. Do you have any suggestions? My short experience suggests that perhaps option 2 is better than nothing but I am not really convinced.

```python
#################################################################
###### CODE FOR OPTION #2 (Wrapper that appends extension) ######
#################################################################
import sys
import os.path
from PyQt5.QtWidgets import QApplication, QWidget, QInputDialog, QLineEdit, QFileDialog, QMessageBox

class App(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()
    
    def initUI(self):
        self.setWindowTitle('File dialogs')
        self.setGeometry(10, 10, 640, 480)
        fileName, selFilter = QFileDialog.getSaveFileName(self,"Save","","Text Files (*.txt)")
        fileName += ".txt"
        if os.path.exists(fileName):
            reply = QMessageBox.question(self, 'Message',"Are you sure you want to overwrite "+fileName+"?", QMessageBox.Yes, QMessageBox.No)
            print("File overwritten" if reply == QMessageBox.Yes else "File NOT overwritten")
        print(fileName)

if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = App()
    sys.exit(app.exec_())
```
I played around with native Gtk save dialogs and they don't add the file extensions either.

I think option 2 is the best way to go currently, and your simple method outlined above is the way to go. I have 3 suggestions:

1. You should only add an extension if the user doesn't have one. I think this is how it works on windows. If the extension is "wrong" and that matters (for saving in the variable explorer it does matter), give an error that the extension must be one of....
2. If the user doesn't have an extension then use the extension returned by the `selfilter`
3. If there is an error in saving and the user says "no" to the suggested overwrite open the save dialogue again with their previous filename to allow the user to make a change to the name to remove the name overlap.
Thanks @bcolsen,
I will go ahead and implement it considering your suggestions.
One quick additional question, the extension that `selfilter` returns is in a text format like  `"Spyder data files (*.spydata)"`. I can extract the extension from this string using a regular expression or somethig similar; however, I am not sure if this is the right approach. Is there a better way to do this or I need to use text manipulation to extract such extension?
> One quick additional question, the extension that selfilter returns is in a text format like "Spyder data files (*.spydata)". I can extract the extension from this string using a regular expression or something similar; however, I am not sure if this is the right approach. Is there a better way to do this or I need to use text manipulation to extract such extension?

Those strings are produced from a dictionary in iofunctions so it should be fine extract the extension from the string. I would just use rfind and some string indexing to get the extension. It should always be of the form `"(*.XXXXXX)"` at the end of the sting.

Resolving patch:
diff --git a/spyder/plugins/variableexplorer/widgets/namespacebrowser.py b/spyder/plugins/variableexplorer/widgets/namespacebrowser.py
--- a/spyder/plugins/variableexplorer/widgets/namespacebrowser.py
+++ b/spyder/plugins/variableexplorer/widgets/namespacebrowser.py
@@ -181,15 +181,15 @@ def import_data(self, filenames=None):
             self.filename = str(filename)
             if os.name == "nt":
                 self.filename = remove_backslashes(self.filename)
-            ext = osp.splitext(self.filename)[1].lower()
+            extension = osp.splitext(self.filename)[1].lower()
 
-            if ext not in iofunctions.load_funcs:
+            if extension not in iofunctions.load_funcs:
                 buttons = QMessageBox.Yes | QMessageBox.Cancel
                 answer = QMessageBox.question(self, title,
                             _("<b>Unsupported file extension '%s'</b><br><br>"
                               "Would you like to import it anyway "
                               "(by selecting a known file format)?"
-                              ) % ext, buttons)
+                              ) % extension, buttons)
                 if answer == QMessageBox.Cancel:
                     return
                 formats = list(iofunctions.load_extensions.keys())
@@ -197,11 +197,11 @@ def import_data(self, filenames=None):
                                                 _('Open file as:'),
                                                 formats, 0, False)
                 if ok:
-                    ext = iofunctions.load_extensions[str(item)]
+                    extension = iofunctions.load_extensions[str(item)]
                 else:
                     return
 
-            load_func = iofunctions.load_funcs[ext]
+            load_func = iofunctions.load_funcs[extension]
                 
             # 'import_wizard' (self.setup_io)
             if isinstance(load_func, str):
@@ -220,7 +220,8 @@ def import_data(self, filenames=None):
             else:
                 QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
                 QApplication.processEvents()
-                error_message = self.shellwidget.load_data(self.filename, ext)
+                error_message = self.shellwidget.load_data(self.filename,
+                                                           extension)
                 QApplication.restoreOverrideCursor()
                 QApplication.processEvents()
     
@@ -240,19 +241,23 @@ def reset_namespace(self):
         self.shellwidget.reset_namespace(warning=warning, message=True)
         self.editor.automatic_column_width = True
 
-    def save_data(self, filename=None):
+    def save_data(self):
         """Save data"""
+        filename = self.filename
         if filename is None:
-            filename = self.filename
-            if filename is None:
-                filename = getcwd_or_home()
-            filename, _selfilter = getsavefilename(self, _("Save data"),
-                                                   filename,
-                                                   iofunctions.save_filters)
-            if filename:
-                self.filename = filename
-            else:
-                return False
+            filename = getcwd_or_home()
+        extension = osp.splitext(filename)[1].lower()
+        if not extension:
+            # Needed to prevent trying to save a data file without extension
+            # See spyder-ide/spyder#7196
+            filename = filename + '.spydata'
+        filename, _selfilter = getsavefilename(self, _("Save data"),
+                                               filename,
+                                               iofunctions.save_filters)
+        if filename:
+            self.filename = filename
+        else:
+            return False
 
         QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
         QApplication.processEvents()


Test patch: