Repository: spyder-ide/spyder

Issue:
Use argparse instead of optparse
optparse is deprecated see: https://docs.python.org/2.7/library/optparse.html

https://github.com/spyder-ide/spyder/blob/master/spyder/app/cli_options.py


Discussion:
Sure, I think it's a good move for Spyder 4.

Resolving patch:
diff --git a/bootstrap.py b/bootstrap.py
--- a/bootstrap.py
+++ b/bootstrap.py
@@ -19,47 +19,48 @@
 import os
 import os.path as osp
 import sys
-import optparse
+import argparse
 
 
 # --- Parse command line
 
-parser = optparse.OptionParser(
+parser = argparse.ArgumentParser(
     usage="python bootstrap.py [options] [-- spyder_options]",
     epilog="""\
 Arguments for Spyder's main script are specified after the --
 symbol (example: `python bootstrap.py -- --hide-console`).
 Type `python bootstrap.py -- --help` to read about Spyder
 options.""")
-parser.add_option('--gui', default=None,
+parser.add_argument('--gui', default=None,
                   help="GUI toolkit: pyqt5 (for PyQt5), pyqt (for PyQt4) or "
                        "pyside (for PySide, deprecated)")
-parser.add_option('--show-console', action='store_true', default=False,
+parser.add_argument('--show-console', action='store_true', default=False,
                   help="(Deprecated) Does nothing, now the default behavior "
                   "is to show the console")
-parser.add_option('--hide-console', action='store_true',
+parser.add_argument('--hide-console', action='store_true',
                   default=False, help="Hide parent console window (Windows only)")
-parser.add_option('--test', dest="test", action='store_true', default=False,
+parser.add_argument('--test', dest="test", action='store_true', default=False,
                   help="Test Spyder with a clean settings dir")
-parser.add_option('--no-apport', action='store_true',
+parser.add_argument('--no-apport', action='store_true',
                   default=False, help="Disable Apport exception hook (Ubuntu)")
-parser.add_option('--debug', action='store_true',
+parser.add_argument('--debug', action='store_true',
                   default=False, help="Run Spyder in debug mode")
+parser.add_argument('spyder_options', nargs='*')
 
-options, args = parser.parse_args()
+args = parser.parse_args()
 
 # Store variable to be used in self.restart (restart spyder instance)
 os.environ['SPYDER_BOOTSTRAP_ARGS'] = str(sys.argv[1:])
 
-assert options.gui in (None, 'pyqt5', 'pyqt', 'pyside'), \
-       "Invalid GUI toolkit option '%s'" % options.gui
+assert args.gui in (None, 'pyqt5', 'pyqt', 'pyside'), \
+       "Invalid GUI toolkit option '%s'" % args.gui
 
 # For testing purposes
-if options.test:
+if args.test:
     os.environ['SPYDER_TEST'] = 'True'
 
 # Prepare arguments for Spyder's main script
-sys.argv = [sys.argv[0]] + args
+sys.argv = [sys.argv[0]] + args.spyder_options
 
 
 print("Executing Spyder from source checkout")
@@ -90,7 +91,7 @@
      print("WARNING: 3rd party Python exception hook is active: '%s'"
             % sys.excepthook.__name__)
    else:
-     if not options.no_apport:
+     if not args.no_apport:
        print("WARNING: Ubuntu Apport exception hook is detected")
        print("         Use --no-apport option to disable it")
      else:
@@ -100,7 +101,7 @@
 
 # --- Continue
 
-if options.debug:
+if args.debug:
     # safety check - Spyder config should not be imported at this point
     if "spyder.config.base" in sys.modules:
         sys.exit("ERROR: Can't enable debug mode - Spyder is already imported")
@@ -119,7 +120,7 @@
 
 # Selecting the GUI toolkit: PyQt5 if installed, otherwise PySide or PyQt4
 # (Note: PyQt4 is still the officially supported GUI toolkit for Spyder)
-if options.gui is None:
+if args.gui is None:
     try:
         import PyQt5  # analysis:ignore
         print("02. PyQt5 is detected, selecting")
@@ -134,7 +135,7 @@
                   "(deprecated)")
 else:
     print ("02. Skipping GUI toolkit detection")
-    os.environ['QT_API'] = options.gui
+    os.environ['QT_API'] = args.gui
 
 
 # Checking versions (among other things, this has the effect of setting the
@@ -157,11 +158,11 @@
 
 # --- Executing Spyder
 
-if options.show_console:
+if args.show_console:
     print("(Deprecated) --show console does nothing, now the default behavior "
           "is to show the console, use --hide-console if you want to hide it")
 
-if options.hide_console and os.name == 'nt':
+if args.hide_console and os.name == 'nt':
     print("0x. Hiding parent console (Windows only)")
     sys.argv.append("--hide-console")  # Windows only: show parent console
 
diff --git a/spyder/app/cli_options.py b/spyder/app/cli_options.py
--- a/spyder/app/cli_options.py
+++ b/spyder/app/cli_options.py
@@ -4,44 +4,46 @@
 # Licensed under the terms of the MIT License
 # (see spyder/__init__.py for details)
 
-import optparse
+import argparse
 
-def get_options():
+def get_options(argv=None):
     """
     Convert options into commands
     return commands, message
     """
-    parser = optparse.OptionParser(usage="spyder [options] files")
-    parser.add_option('--new-instance', action='store_true', default=False,
+    parser = argparse.ArgumentParser(usage="spyder [options] files")
+    parser.add_argument('--new-instance', action='store_true', default=False,
                       help="Run a new instance of Spyder, even if the single "
                            "instance mode has been turned on (default)")
-    parser.add_option('--defaults', dest="reset_to_defaults",
+    parser.add_argument('--defaults', dest="reset_to_defaults",
                       action='store_true', default=False,
                       help="Reset configuration settings to defaults")
-    parser.add_option('--reset', dest="reset_config_files",
+    parser.add_argument('--reset', dest="reset_config_files",
                       action='store_true', default=False,
                       help="Remove all configuration files!")
-    parser.add_option('--optimize', action='store_true', default=False,
+    parser.add_argument('--optimize', action='store_true', default=False,
                       help="Optimize Spyder bytecode (this may require "
                            "administrative privileges)")
-    parser.add_option('-w', '--workdir', dest="working_directory", default=None,
+    parser.add_argument('-w', '--workdir', dest="working_directory", default=None,
                       help="Default working directory")
-    parser.add_option('--hide-console', action='store_true', default=False,
+    parser.add_argument('--hide-console', action='store_true', default=False,
                       help="Hide parent console window (Windows)")
-    parser.add_option('--show-console', action='store_true', default=False,
+    parser.add_argument('--show-console', action='store_true', default=False,
                       help="(Deprecated) Does nothing, now the default behavior "
                       "is to show the console")
-    parser.add_option('--multithread', dest="multithreaded",
+    parser.add_argument('--multithread', dest="multithreaded",
                       action='store_true', default=False,
                       help="Internal console is executed in another thread "
                            "(separate from main application thread)")
-    parser.add_option('--profile', action='store_true', default=False,
+    parser.add_argument('--profile', action='store_true', default=False,
                       help="Profile mode (internal test, "
                            "not related with Python profiling)")
-    parser.add_option('--window-title', type=str, default=None,
+    parser.add_argument('--window-title', type=str, default=None,
                       help="String to show in the main window title")
-    parser.add_option('-p', '--project', default=None, type=str,
+    parser.add_argument('-p', '--project', default=None, type=str,
                       dest="open_project",
                       help="Path that contains an Spyder project")
-    options, args = parser.parse_args()
+    parser.add_argument('files', nargs='*')
+    options = parser.parse_args(argv)
+    args = options.files
     return options, args
diff --git a/spyder/app/mainwindow.py b/spyder/app/mainwindow.py
--- a/spyder/app/mainwindow.py
+++ b/spyder/app/mainwindow.py
@@ -3036,7 +3036,7 @@ def main():
     # **** Collect command line options ****
     # Note regarding Options:
     # It's important to collect options before monkey patching sys.exit,
-    # otherwise, optparse won't be able to exit if --help option is passed
+    # otherwise, argparse won't be able to exit if --help option is passed
     options, args = get_options()
 
     if options.show_console:


Test patch:
diff --git a/spyder/app/tests/test_cli_options.py b/spyder/app/tests/test_cli_options.py
new file mode 100644
--- /dev/null
+++ b/spyder/app/tests/test_cli_options.py
@@ -0,0 +1,64 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright © Spyder Project Contributors
+# Licensed under the terms of the MIT License
+#
+
+"""
+Tests for cli_options.py
+"""
+import pytest
+from spyder.app import cli_options
+
+
+def test_get_options():
+    getopt = cli_options.get_options
+
+    options, args = getopt([])
+    assert not options.new_instance
+    assert not options.reset_to_defaults
+    assert not options.reset_config_files
+    assert not options.optimize
+    assert options.working_directory is None
+    assert not options.hide_console
+    assert not options.show_console
+    assert not options.multithreaded
+    assert not options.profile
+    assert options.window_title is None
+    assert options.open_project is None
+    assert options.files == []
+    assert args == []
+
+    options, args = getopt(['--new-instance'])
+    assert options.new_instance
+
+    options, args = getopt(['--defaults', '--reset'])
+    assert options.reset_to_defaults
+    assert options.reset_config_files
+
+    options, args = getopt(['--optimize', '--workdir', 'test dir'])
+    assert options.optimize
+    assert options.working_directory == 'test dir'
+
+    options, args = getopt('--window-title MyWindow'.split())
+    assert options.window_title == 'MyWindow'
+
+    options, args = getopt('-p myproject test_file.py another_file.py'.split())
+    assert options.open_project == 'myproject'
+    assert options.files == ['test_file.py', 'another_file.py']
+    assert args == ['test_file.py', 'another_file.py']
+
+    with pytest.raises(SystemExit):
+        options, args = getopt(['--version'])
+
+    # Requires string.
+    with pytest.raises(SystemExit):
+        options, args = getopt(['-w'])
+
+    # Requires string.
+    with pytest.raises(SystemExit):
+        options, args = getopt(['-p'])
+
+
+if __name__ == "__main__":
+    pytest.main()