##// END OF EJS Templates
Fix restoring more than 2 variables and add support for storing multiple variables.
Gökcen Eraslan -
Show More
@@ -1,226 +1,233 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 %store magic for lightweight persistence.
3 %store magic for lightweight persistence.
4
4
5 Stores variables, aliases and macros in IPython's database.
5 Stores variables, aliases and macros in IPython's database.
6
6
7 To automatically restore stored variables at startup, add this to your
7 To automatically restore stored variables at startup, add this to your
8 :file:`ipython_config.py` file::
8 :file:`ipython_config.py` file::
9
9
10 c.StoreMagics.autorestore = True
10 c.StoreMagics.autorestore = True
11 """
11 """
12
12
13 # Copyright (c) IPython Development Team.
13 # Copyright (c) IPython Development Team.
14 # Distributed under the terms of the Modified BSD License.
14 # Distributed under the terms of the Modified BSD License.
15
15
16 import inspect, os, sys, textwrap
16 import inspect, os, sys, textwrap
17
17
18 from IPython.core.error import UsageError
18 from IPython.core.error import UsageError
19 from IPython.core.magic import Magics, magics_class, line_magic
19 from IPython.core.magic import Magics, magics_class, line_magic
20 from traitlets import Bool
20 from traitlets import Bool
21
21
22
22
23 def restore_aliases(ip):
23 def restore_aliases(ip, alias=None):
24 staliases = ip.db.get('stored_aliases', {})
24 staliases = ip.db.get('stored_aliases', {})
25 for k,v in staliases.items():
25 if alias is None:
26 #print "restore alias",k,v # dbg
26 for k,v in staliases.items():
27 #self.alias_table[k] = v
27 #print "restore alias",k,v # dbg
28 ip.alias_manager.define_alias(k,v)
28 #self.alias_table[k] = v
29 ip.alias_manager.define_alias(k,v)
30 else:
31 ip.alias_manager.define_alias(alias, staliases[alias])
29
32
30
33
31 def refresh_variables(ip):
34 def refresh_variables(ip):
32 db = ip.db
35 db = ip.db
33 for key in db.keys('autorestore/*'):
36 for key in db.keys('autorestore/*'):
34 # strip autorestore
37 # strip autorestore
35 justkey = os.path.basename(key)
38 justkey = os.path.basename(key)
36 try:
39 try:
37 obj = db[key]
40 obj = db[key]
38 except KeyError:
41 except KeyError:
39 print("Unable to restore variable '%s', ignoring (use %%store -d to forget!)" % justkey)
42 print("Unable to restore variable '%s', ignoring (use %%store -d to forget!)" % justkey)
40 print("The error was:", sys.exc_info()[0])
43 print("The error was:", sys.exc_info()[0])
41 else:
44 else:
42 #print "restored",justkey,"=",obj #dbg
45 #print "restored",justkey,"=",obj #dbg
43 ip.user_ns[justkey] = obj
46 ip.user_ns[justkey] = obj
44
47
45
48
46 def restore_dhist(ip):
49 def restore_dhist(ip):
47 ip.user_ns['_dh'] = ip.db.get('dhist',[])
50 ip.user_ns['_dh'] = ip.db.get('dhist',[])
48
51
49
52
50 def restore_data(ip):
53 def restore_data(ip):
51 refresh_variables(ip)
54 refresh_variables(ip)
52 restore_aliases(ip)
55 restore_aliases(ip)
53 restore_dhist(ip)
56 restore_dhist(ip)
54
57
55
58
56 @magics_class
59 @magics_class
57 class StoreMagics(Magics):
60 class StoreMagics(Magics):
58 """Lightweight persistence for python variables.
61 """Lightweight persistence for python variables.
59
62
60 Provides the %store magic."""
63 Provides the %store magic."""
61
64
62 autorestore = Bool(False, help=
65 autorestore = Bool(False, help=
63 """If True, any %store-d variables will be automatically restored
66 """If True, any %store-d variables will be automatically restored
64 when IPython starts.
67 when IPython starts.
65 """
68 """
66 ).tag(config=True)
69 ).tag(config=True)
67
70
68 def __init__(self, shell):
71 def __init__(self, shell):
69 super(StoreMagics, self).__init__(shell=shell)
72 super(StoreMagics, self).__init__(shell=shell)
70 self.shell.configurables.append(self)
73 self.shell.configurables.append(self)
71 if self.autorestore:
74 if self.autorestore:
72 restore_data(self.shell)
75 restore_data(self.shell)
73
76
74 @line_magic
77 @line_magic
75 def store(self, parameter_s=''):
78 def store(self, parameter_s=''):
76 """Lightweight persistence for python variables.
79 """Lightweight persistence for python variables.
77
80
78 Example::
81 Example::
79
82
80 In [1]: l = ['hello',10,'world']
83 In [1]: l = ['hello',10,'world']
81 In [2]: %store l
84 In [2]: %store l
82 In [3]: exit
85 In [3]: exit
83
86
84 (IPython session is closed and started again...)
87 (IPython session is closed and started again...)
85
88
86 ville@badger:~$ ipython
89 ville@badger:~$ ipython
87 In [1]: l
90 In [1]: l
88 NameError: name 'l' is not defined
91 NameError: name 'l' is not defined
89 In [2]: %store -r
92 In [2]: %store -r
90 In [3]: l
93 In [3]: l
91 Out[3]: ['hello', 10, 'world']
94 Out[3]: ['hello', 10, 'world']
92
95
93 Usage:
96 Usage:
94
97
95 * ``%store`` - Show list of all variables and their current
98 * ``%store`` - Show list of all variables and their current
96 values
99 values
97 * ``%store spam`` - Store the *current* value of the variable spam
100 * ``%store spam bar`` - Store the *current* value of the variables spam
98 to disk
101 and bar to disk
99 * ``%store -d spam`` - Remove the variable and its value from storage
102 * ``%store -d spam`` - Remove the variable and its value from storage
100 * ``%store -z`` - Remove all variables from storage
103 * ``%store -z`` - Remove all variables from storage
101 * ``%store -r`` - Refresh all variables from store (overwrite
104 * ``%store -r`` - Refresh all variables, aliases and directory history
102 current vals)
105 from store (overwrite current vals)
103 * ``%store -r spam bar`` - Refresh specified variables from store
106 * ``%store -r spam bar`` - Refresh specified variables and aliases from store
104 (delete current val)
107 (delete current val)
105 * ``%store foo >a.txt`` - Store value of foo to new file a.txt
108 * ``%store foo >a.txt`` - Store value of foo to new file a.txt
106 * ``%store foo >>a.txt`` - Append value of foo to file a.txt
109 * ``%store foo >>a.txt`` - Append value of foo to file a.txt
107
110
108 It should be noted that if you change the value of a variable, you
111 It should be noted that if you change the value of a variable, you
109 need to %store it again if you want to persist the new value.
112 need to %store it again if you want to persist the new value.
110
113
111 Note also that the variables will need to be pickleable; most basic
114 Note also that the variables will need to be pickleable; most basic
112 python types can be safely %store'd.
115 python types can be safely %store'd.
113
116
114 Also aliases can be %store'd across sessions.
117 Also aliases can be %store'd across sessions.
115 To remove an alias from the storage, use the %unalias magic.
118 To remove an alias from the storage, use the %unalias magic.
116 """
119 """
117
120
118 opts,argsl = self.parse_options(parameter_s,'drz',mode='string')
121 opts,argsl = self.parse_options(parameter_s,'drz',mode='string')
119 args = argsl.split(None,1)
122 args = argsl.split()
120 ip = self.shell
123 ip = self.shell
121 db = ip.db
124 db = ip.db
122 # delete
125 # delete
123 if 'd' in opts:
126 if 'd' in opts:
124 try:
127 try:
125 todel = args[0]
128 todel = args[0]
126 except IndexError:
129 except IndexError:
127 raise UsageError('You must provide the variable to forget')
130 raise UsageError('You must provide the variable to forget')
128 else:
131 else:
129 try:
132 try:
130 del db['autorestore/' + todel]
133 del db['autorestore/' + todel]
131 except:
134 except:
132 raise UsageError("Can't delete variable '%s'" % todel)
135 raise UsageError("Can't delete variable '%s'" % todel)
133 # reset
136 # reset
134 elif 'z' in opts:
137 elif 'z' in opts:
135 for k in db.keys('autorestore/*'):
138 for k in db.keys('autorestore/*'):
136 del db[k]
139 del db[k]
137
140
138 elif 'r' in opts:
141 elif 'r' in opts:
139 if args:
142 if args:
140 for arg in args:
143 for arg in args:
141 try:
144 try:
142 obj = db['autorestore/' + arg]
145 obj = db['autorestore/' + arg]
143 except KeyError:
146 except KeyError:
144 print("no stored variable %s" % arg)
147 try:
148 restore_aliases(ip, alias=arg)
149 except KeyError:
150 print("no stored variable or alias %s" % arg)
145 else:
151 else:
146 ip.user_ns[arg] = obj
152 ip.user_ns[arg] = obj
147 else:
153 else:
148 restore_data(ip)
154 restore_data(ip)
149
155
150 # run without arguments -> list variables & values
156 # run without arguments -> list variables & values
151 elif not args:
157 elif not args:
152 vars = db.keys('autorestore/*')
158 vars = db.keys('autorestore/*')
153 vars.sort()
159 vars.sort()
154 if vars:
160 if vars:
155 size = max(map(len, vars))
161 size = max(map(len, vars))
156 else:
162 else:
157 size = 0
163 size = 0
158
164
159 print('Stored variables and their in-db values:')
165 print('Stored variables and their in-db values:')
160 fmt = '%-'+str(size)+'s -> %s'
166 fmt = '%-'+str(size)+'s -> %s'
161 get = db.get
167 get = db.get
162 for var in vars:
168 for var in vars:
163 justkey = os.path.basename(var)
169 justkey = os.path.basename(var)
164 # print 30 first characters from every var
170 # print 30 first characters from every var
165 print(fmt % (justkey, repr(get(var, '<unavailable>'))[:50]))
171 print(fmt % (justkey, repr(get(var, '<unavailable>'))[:50]))
166
172
167 # default action - store the variable
173 # default action - store the variable
168 else:
174 else:
169 # %store foo >file.txt or >>file.txt
175 # %store foo >file.txt or >>file.txt
170 if len(args) > 1 and args[1].startswith('>'):
176 if len(args) > 1 and args[1].startswith('>'):
171 fnam = os.path.expanduser(args[1].lstrip('>').lstrip())
177 fnam = os.path.expanduser(args[1].lstrip('>').lstrip())
172 if args[1].startswith('>>'):
178 if args[1].startswith('>>'):
173 fil = open(fnam, 'a')
179 fil = open(fnam, 'a')
174 else:
180 else:
175 fil = open(fnam, 'w')
181 fil = open(fnam, 'w')
176 with fil:
182 with fil:
177 obj = ip.ev(args[0])
183 obj = ip.ev(args[0])
178 print("Writing '%s' (%s) to file '%s'." % (args[0],
184 print("Writing '%s' (%s) to file '%s'." % (args[0],
179 obj.__class__.__name__, fnam))
185 obj.__class__.__name__, fnam))
180
186
181 if not isinstance (obj, str):
187 if not isinstance (obj, str):
182 from pprint import pprint
188 from pprint import pprint
183 pprint(obj, fil)
189 pprint(obj, fil)
184 else:
190 else:
185 fil.write(obj)
191 fil.write(obj)
186 if not obj.endswith('\n'):
192 if not obj.endswith('\n'):
187 fil.write('\n')
193 fil.write('\n')
188
194
189 return
195 return
190
196
191 # %store foo
197 # %store foo
192 try:
198 for arg in args:
193 obj = ip.user_ns[args[0]]
194 except KeyError:
195 # it might be an alias
196 name = args[0]
197 try:
199 try:
198 cmd = ip.alias_manager.retrieve_alias(name)
200 obj = ip.user_ns[arg]
199 except ValueError:
201 except KeyError:
200 raise UsageError("Unknown variable '%s'" % name)
202 # it might be an alias
201
203 name = arg
202 staliases = db.get('stored_aliases',{})
204 try:
203 staliases[name] = cmd
205 cmd = ip.alias_manager.retrieve_alias(name)
204 db['stored_aliases'] = staliases
206 except ValueError:
205 print("Alias stored: %s (%s)" % (name, cmd))
207 raise UsageError("Unknown variable '%s'" % name)
206 return
208
207
209 staliases = db.get('stored_aliases',{})
208 else:
210 staliases[name] = cmd
209 modname = getattr(inspect.getmodule(obj), '__name__', '')
211 db['stored_aliases'] = staliases
210 if modname == '__main__':
212 print("Alias stored: %s (%s)" % (name, cmd))
211 print(textwrap.dedent("""\
212 Warning:%s is %s
213 Proper storage of interactively declared classes (or instances
214 of those classes) is not possible! Only instances
215 of classes in real modules on file system can be %%store'd.
216 """ % (args[0], obj) ))
217 return
213 return
218 #pickled = pickle.dumps(obj)
214
219 db[ 'autorestore/' + args[0] ] = obj
215 else:
220 print("Stored '%s' (%s)" % (args[0], obj.__class__.__name__))
216 modname = getattr(inspect.getmodule(obj), '__name__', '')
217 if modname == '__main__':
218 print(textwrap.dedent("""\
219 Warning:%s is %s
220 Proper storage of interactively declared classes (or instances
221 of those classes) is not possible! Only instances
222 of classes in real modules on file system can be %%store'd.
223 """ % (arg, obj) ))
224 return
225 #pickled = pickle.dumps(obj)
226 db[ 'autorestore/' + arg ] = obj
227 print("Stored '%s' (%s)" % (arg, obj.__class__.__name__))
221
228
222
229
223 def load_ipython_extension(ip):
230 def load_ipython_extension(ip):
224 """Load the extension in IPython."""
231 """Load the extension in IPython."""
225 ip.register_magics(StoreMagics)
232 ip.register_magics(StoreMagics)
226
233
@@ -1,53 +1,66 b''
1 import tempfile, os
1 import tempfile, os
2
2
3 from traitlets.config.loader import Config
3 from traitlets.config.loader import Config
4 import nose.tools as nt
4 import nose.tools as nt
5
5
6
6
7 def setup_module():
7 def setup_module():
8 ip.magic('load_ext storemagic')
8 ip.magic('load_ext storemagic')
9
9
10 def test_store_restore():
10 def test_store_restore():
11 assert 'bar' not in ip.user_ns, "Error: some other test leaked `bar` in user_ns"
11 assert 'bar' not in ip.user_ns, "Error: some other test leaked `bar` in user_ns"
12 assert 'foo' not in ip.user_ns, "Error: some other test leaked `foo` in user_ns"
12 assert 'foo' not in ip.user_ns, "Error: some other test leaked `foo` in user_ns"
13 assert 'foobar' not in ip.user_ns, "Error: some other test leaked `foobar` in user_ns"
14 assert 'foobaz' not in ip.user_ns, "Error: some other test leaked `foobaz` in user_ns"
13 ip.user_ns['foo'] = 78
15 ip.user_ns['foo'] = 78
14 ip.magic('alias bar echo "hello"')
16 ip.magic('alias bar echo "hello"')
17 ip.user_ns['foobar'] = 79
18 ip.user_ns['foobaz'] = '80'
15 tmpd = tempfile.mkdtemp()
19 tmpd = tempfile.mkdtemp()
16 ip.magic('cd ' + tmpd)
20 ip.magic('cd ' + tmpd)
17 ip.magic('store foo')
21 ip.magic('store foo')
18 ip.magic('store bar')
22 ip.magic('store bar')
19
23 ip.magic('store foobar foobaz')
24
20 # Check storing
25 # Check storing
21 nt.assert_equal(ip.db['autorestore/foo'], 78)
26 nt.assert_equal(ip.db['autorestore/foo'], 78)
22 nt.assert_in('bar', ip.db['stored_aliases'])
27 nt.assert_in('bar', ip.db['stored_aliases'])
23
28 nt.assert_equal(ip.db['autorestore/foobar'], 79)
29 nt.assert_equal(ip.db['autorestore/foobaz'], '80')
30
24 # Remove those items
31 # Remove those items
25 ip.user_ns.pop('foo', None)
32 ip.user_ns.pop('foo', None)
33 ip.user_ns.pop('foobar', None)
34 ip.user_ns.pop('foobaz', None)
26 ip.alias_manager.undefine_alias('bar')
35 ip.alias_manager.undefine_alias('bar')
27 ip.magic('cd -')
36 ip.magic('cd -')
28 ip.user_ns['_dh'][:] = []
37 ip.user_ns['_dh'][:] = []
29
38
30 # Check restoring
39 # Check restoring
31 ip.magic('store -r')
40 ip.magic('store -r foo bar foobar foobaz')
32 nt.assert_equal(ip.user_ns['foo'], 78)
41 nt.assert_equal(ip.user_ns['foo'], 78)
33 assert ip.alias_manager.is_alias('bar')
42 assert ip.alias_manager.is_alias('bar')
43 nt.assert_equal(ip.user_ns['foobar'], 79)
44 nt.assert_equal(ip.user_ns['foobaz'], '80')
45
46 ip.magic('store -r') # restores _dh too
34 nt.assert_in(os.path.realpath(tmpd), ip.user_ns['_dh'])
47 nt.assert_in(os.path.realpath(tmpd), ip.user_ns['_dh'])
35
48
36 os.rmdir(tmpd)
49 os.rmdir(tmpd)
37
50
38 def test_autorestore():
51 def test_autorestore():
39 ip.user_ns['foo'] = 95
52 ip.user_ns['foo'] = 95
40 ip.magic('store foo')
53 ip.magic('store foo')
41 del ip.user_ns['foo']
54 del ip.user_ns['foo']
42 c = Config()
55 c = Config()
43 c.StoreMagics.autorestore = False
56 c.StoreMagics.autorestore = False
44 orig_config = ip.config
57 orig_config = ip.config
45 try:
58 try:
46 ip.config = c
59 ip.config = c
47 ip.extension_manager.reload_extension('storemagic')
60 ip.extension_manager.reload_extension('storemagic')
48 nt.assert_not_in('foo', ip.user_ns)
61 nt.assert_not_in('foo', ip.user_ns)
49 c.StoreMagics.autorestore = True
62 c.StoreMagics.autorestore = True
50 ip.extension_manager.reload_extension('storemagic')
63 ip.extension_manager.reload_extension('storemagic')
51 nt.assert_equal(ip.user_ns['foo'], 95)
64 nt.assert_equal(ip.user_ns['foo'], 95)
52 finally:
65 finally:
53 ip.config = orig_config
66 ip.config = orig_config
General Comments 0
You need to be logged in to leave comments. Login now