Skip to content

Instantly share code, notes, and snippets.

@ptoche
Last active May 28, 2020 11:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ptoche/903d7f37f67c228abf9c2c8c157f816a to your computer and use it in GitHub Desktop.
Save ptoche/903d7f37f67c228abf9c2c8c157f816a to your computer and use it in GitHub Desktop.
Spyder hacks: backup and edit config files for each environment (Upcoming release Spyder 5 will hopefully offer something better than this soon)
"""
Spyder hacks: backup and edit config files for each environment.
Upcoming release Spyder 5 will hopefully offer something better than this soon.
Created 28 May 2020
@author: Patrick Toche
"""
# import common packages here, otherwise import inside each function (helps me understand which packages are needed where and how much they are needed)
import os
def make_ini_names(filename=None, copyname=None, verbose=False):
"""
Set names for the original and backup ini files.
The default ini file is at `.spyder-py3/config/spyder.ini`.
The default bak file is at `.spyder-py3/config/backups/spyder_ini_%Y_%m_%d_%H_%M.bak`.
"""
from datetime import datetime
if not filename:
filename = os.path.join(os.path.expanduser('~'), '.spyder-py3/config/spyder.ini')
filepath = os.path.dirname(filename)
dirs = os.path.normpath(filepath).split(os.sep)[:-1]
dirs.append('backups')
copypath = os.sep.join(dirs)
if not copyname:
copyname = datetime.now().strftime(os.path.join(copypath, 'backups/spyder_ini_%Y_%m_%d_%H_%M.bak'))
return [filename, copyname]
def backup_ini(filename=None, copyname=None, verbose=True):
"""
Make a copy of the Spyder ini file for backup.
Both the source path (argument `filename`) and destination path (argument `copyname`) may be changed.
"""
from shutil import copy2 # shutil.copy2 copies metadata+permissions
# set the file names, if not provided by user:
if not filename:
filename = make_ini_names(filename=None, copyname=None, verbose=False)[0]
# set the initiation file backup name, if not provided by user:
if not copyname:
copyname = make_ini_names(filename=None, copyname=None, verbose=False)[1]
copy2(filename, copyname)
if verbose:
print('A copy of your ini file was saved as\n', copyname)
return None
def edit_ini_keys(config, verbose=False):
"""
First remove existing shortcuts, then set new ones.
This is done by editing the config list.
This function does not allow user input: These edits are my own preferences!
To edit shortcuts (key bindings), edit this function.
Many more examples of actions assigned to keys may be found in the ini file.
"""
try:
# Key F12
# Default F12: 'editor/breakpoint'
# Default editor/run selection: 'F9'
config['shortcuts']['editor/breakpoint'] = 'Shift+Return'
config['shortcuts']['editor/run selection'] = 'F12'
# Recycling value from editor/run cell and advance (was 'Shift+Return')
# Key F11
# Default F11: '_/fullscreen mode'
# MacOS Catalina: Keyboard -> Shortcuts -> Mission Control -> Show Desktop = F11
# Default editor/run cell: 'Meta+Return'
config['shortcuts']['_/fullscreen mode'] = 'Meta+Return'
config['shortcuts']['editor/run cell'] = 'F11'
# Swapping values
# Key F10
# Default F10: 'profiler/run profiler'
# Default editor/re-run last cell: 'Alt+Return'
config['shortcuts']['profiler/run profiler'] = 'Alt+Return'
config['shortcuts']['editor/re-run last cell'] = 'F10'
# Swapping values
# Key F9
# Default F9: 'editor/run selection'
# Default editor/run cell and advance: 'Shift+Return'
config['shortcuts']['editor/run cell and advance'] = 'F9'
# 'editor/run selection' already set above
# Key F8
# Default F8: 'pylint/run analysis'
# Keeping default value
# Key F7
# Default F7: ''
# Keeping default value (unassigned)
# Key F6
# Default F6: '_/re-run last script'
# Keeping default value
# Key F5
# Default F5: '_/run'
# Keeping default value
# Key F4
# Default F4: ''
# Keeping default value (unassigned)
# Key F3
# Default F3: 'find_replace/find next'
# Keeping default value
# Key F2
# Default F2: ''
# Keeping default value (unassigned)
# Key F1
# Default F1: '_/spyder documentation'
# Keeping default value
# Key Ctrl+Right
# Default Ctrl+Right: 'editor/next word'
# Default editor/end of line: 'Meta+E'
config['shortcuts']['editor/next word'] = 'Meta+E'
config['shortcuts']['editor/end of line'] = 'Ctrl+Right'
# Swapping values
# Key Ctrl+Left
# Default Ctrl+Left: 'editor/previous word'
# Default editor/start of line: 'Meta+A'
config['shortcuts']['editor/previous word'] = 'Meta+A'
config['shortcuts']['editor/start of line'] = 'Ctrl+Left'
# Swapping values
except Exception as e:
print('A problem occurred while attempting to set key bindings. Error message:\n', e)
return config
def edit_spyder_ini(filename=None, copyname=None, verbose=False, theme='spyder/dark'):
"""
Automate preference settings by editing the config file.
"""
import configparser
# set the file names, if not provided by user:
if not filename:
filename = make_ini_names(filename=None, copyname=None, verbose=False)[0]
# set the initiation file backup name, if not provided by user:
if not copyname:
copyname = make_ini_names(filename=None, copyname=None, verbose=False)[1]
# setup the parser:
config = configparser.ConfigParser()
config.read(filename)
# edit shortcuts:
edit_ini_keys(config, verbose=verbose)
# Set a theme (Default: 'spyder/dark')
config['appearance']['selected'] = theme
# Save a backup of the ini file
backup_ini(filename=filename, copyname=copyname, verbose=False)
# Save the edited ini file
with open(filename, 'w') as f:
config.write(f)
if verbose:
print('Your Spyder ini file has been modified. A backup copy was saved.\
\nTo revert changes, delete the original ini file and replace it with the backup.')
print('The original ini file was ', filename)
print('The backup ini file was saved as ', copyname)
return None
def make_temp_file(envname=None, filename=None):
"""
Create a temp file for a named environment inside a subdirectory.
If the file already exists, load it (I like to keep/edit the temp files across sessions).
"""
from pathlib import Path # to create a directory if needed
from spyder.utils import encoding # not needed, but more general
from spyder.py3compat import to_text_string # not needed, but more general
from IPython import get_ipython # to load a new instance of a file in the editor
if not filename:
filename = os.path.join(os.path.expanduser('~'), '.spyder-py3/temp.py')
# if no environment is supplied, use `temp.py` in `root` directory,
root = os.path.dirname(filename)
# otherwise create a `temp.py` inside an `<envname>` directory
if not envname:
envname =''
envdir = root
editor_location = "It resides in the <root> environment."
else:
# if directory `<envname>` does not exist, create it:
envdir = os.path.join(root, 'envs', envname)
Path(envdir).mkdir(parents=True, exist_ok=True)
editor_location = "It resides in the <"+envname+"> environment."
tempfile = os.path.join(envdir, os.path.basename(filename))
# if `temp.py` file does not exist, create it
if not os.path.isfile(tempfile):
# Creating temporary file
default = ['# -*- coding: utf-8 -*-',
'"""', '' "Spyder Editor", '',
"This is a temporary script file", '',
editor_location, '',
'"""', '', '']
text = os.linesep.join([encoding.to_unicode(qstr)
for qstr in default])
encoding.write(to_text_string(text), tempfile, 'utf-8')
# now load the new temp file
ipython = get_ipython()
ipython.magic(f"%edit {tempfile}")
return None
# BASIC TESTS:
# Backup Spyder's ini file before editing
backup_ini()
# Edit Spyder's ini file (a backup is automatically created)
edit_spyder_ini()
# Create an environment-specific temp file
make_temp_file()
make_temp_file('myenv')
# FROM HERE ON IT'S WORK IN PROGRESS!
# TO DO: get spyder to load ini file while running
def get_code(code):
"""
borrowed from https://github.com/vatlab/sos-notebook
"""
if code.startswith('%') or code.startswith('!'):
lines = re.split(r'(?<!\\)\n', code, 1)
# remove lines joint by \
lines[0] = lines[0].replace('\\\n', '')
else:
lines = code.split('\n', 1)
pieces = self._interpolate_text(
lines[0], quiet=False).strip().split(None, 1)
if len(pieces) == 2:
command_line = pieces[1]
else:
command_line = ''
remaining_code = lines[1] if len(lines) > 1 else ''
return command_line, remaining_code
def reset_ini(filename, options):
"""
logic borrowed from: https://github.com/vatlab/sos/blob/master/misc/spyder/spyder_kernel.py
"""
import shlex
import subprocess
import sys
import argparse
if not filename:
filename = make_ini_names(filename=None, copyname=None, verbose=False)[0]
parser = argparse.ArgumentParser(prog='%edit',
description='load a Spyder ini file in an open session')
parser.add_argument('filenames', nargs='+')
parser.add_argument('-c', '--cd', action='store_true', dest='spyder_hack')
TO DO : FIGURE OUT WHAT SELF IS AND HOW TO PARSE OPTIONS...
options, remaining_code = self.get_code(code)
try:
args = parser.parse_args(shlex.split(options))
except SystemExit:
return
args.filenames = [os.path.expanduser(x) for x in args.filenames]
import1 = "import sys"
import2 = "from spyder.app.start import send_args_to_spyder"
code = "send_args_to_spyder([{}])".format(','.join('"{}"'.format(x) for x in args.filenames))
cmd = "{0} -c '{1}; {2}; {3}'".format(sys.executable,
import1, import2, code)
subprocess.call(cmd, shell=True)
return None
reset(ini_file)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment