##// END OF EJS Templates
Merge pull request #11283 from oscar6echo/improve-autoreload...
Matthias Bussonnier -
r24522:197a017e merge
parent child Browse files
Show More
@@ -0,0 +1,30 b''
1 magic `%autoreload 2` now captures new methods added to classes. Earlier, only methods existing as of the initial import were being tracked and updated.
2
3 This new feature helps dual environement development - Jupyter+IDE - where the code gradually moves from notebook cells to package files, as it gets structured.
4
5 **Example**: An instance of the class `MyClass` will be able to access the method `cube()` after it is uncommented and the file `file1.py` saved on disk.
6
7 ````python
8 # notebook
9
10 from mymodule import MyClass
11 first = MyClass(5)
12 ````
13
14 ````python
15 # mymodule/file1.py
16
17 class MyClass:
18
19 def __init__(self, a=10):
20 self.a = a
21
22 def square(self):
23 print('compute square')
24 return self.a*self.a
25
26 # def cube(self):
27 # print('compute cube')
28 # return self.a*self.a*self.a
29 ````
30
@@ -269,7 +269,7 b' def update_function(old, new):'
269
269
270 def update_class(old, new):
270 def update_class(old, new):
271 """Replace stuff in the __dict__ of a class, and upgrade
271 """Replace stuff in the __dict__ of a class, and upgrade
272 method code objects"""
272 method code objects, and add new methods, if any"""
273 for key in list(old.__dict__.keys()):
273 for key in list(old.__dict__.keys()):
274 old_obj = getattr(old, key)
274 old_obj = getattr(old, key)
275 try:
275 try:
@@ -291,6 +291,13 b' def update_class(old, new):'
291 except (AttributeError, TypeError):
291 except (AttributeError, TypeError):
292 pass # skip non-writable attributes
292 pass # skip non-writable attributes
293
293
294 for key in list(new.__dict__.keys()):
295 if key not in list(old.__dict__.keys()):
296 try:
297 setattr(old, key, getattr(new, key))
298 except (AttributeError, TypeError):
299 pass # skip non-writable attributes
300
294
301
295 def update_property(old, new):
302 def update_property(old, new):
296 """Replace get/set/del functions of a property"""
303 """Replace get/set/del functions of a property"""
@@ -151,6 +151,64 b' class TestAutoreload(Fixture):'
151 with tt.AssertNotPrints(('[autoreload of %s failed:' % mod_name), channel='stderr'):
151 with tt.AssertNotPrints(('[autoreload of %s failed:' % mod_name), channel='stderr'):
152 self.shell.run_code("pass") # trigger another reload
152 self.shell.run_code("pass") # trigger another reload
153
153
154 def test_reload_class_attributes(self):
155 self.shell.magic_autoreload("2")
156 mod_name, mod_fn = self.new_module(textwrap.dedent("""
157 class MyClass:
158
159 def __init__(self, a=10):
160 self.a = a
161 self.b = 22
162 # self.toto = 33
163
164 def square(self):
165 print('compute square')
166 return self.a*self.a
167 """
168 )
169 )
170 self.shell.run_code("from %s import MyClass" % mod_name)
171 self.shell.run_code("first = MyClass(5)")
172 self.shell.run_code("first.square()")
173 with nt.assert_raises(AttributeError):
174 self.shell.run_code("first.cube()")
175 with nt.assert_raises(AttributeError):
176 self.shell.run_code("first.power(5)")
177 self.shell.run_code("first.b")
178 with nt.assert_raises(AttributeError):
179 self.shell.run_code("first.toto")
180
181 # remove square, add power
182
183 self.write_file(
184 mod_fn,
185 textwrap.dedent(
186 """
187 class MyClass:
188
189 def __init__(self, a=10):
190 self.a = a
191 self.b = 11
192
193 def power(self, p):
194 print('compute power '+str(p))
195 return self.a**p
196 """
197 ),
198 )
199
200 self.shell.run_code("second = MyClass(5)")
201
202 for object_name in {'first', 'second'}:
203 self.shell.run_code("{object_name}.power(5)".format(object_name=object_name))
204 with nt.assert_raises(AttributeError):
205 self.shell.run_code("{object_name}.cube()".format(object_name=object_name))
206 with nt.assert_raises(AttributeError):
207 self.shell.run_code("{object_name}.square()".format(object_name=object_name))
208 self.shell.run_code("{object_name}.b".format(object_name=object_name))
209 self.shell.run_code("{object_name}.a".format(object_name=object_name))
210 with nt.assert_raises(AttributeError):
211 self.shell.run_code("{object_name}.toto".format(object_name=object_name))
154
212
155 def _check_smoketest(self, use_aimport=True):
213 def _check_smoketest(self, use_aimport=True):
156 """
214 """
@@ -340,3 +398,7 b' x = -99'
340
398
341 def test_smoketest_autoreload(self):
399 def test_smoketest_autoreload(self):
342 self._check_smoketest(use_aimport=False)
400 self._check_smoketest(use_aimport=False)
401
402
403
404
@@ -386,6 +386,12 b' def run_iptest():'
386 monkeypatch_xunit()
386 monkeypatch_xunit()
387
387
388 arg1 = sys.argv[1]
388 arg1 = sys.argv[1]
389 if arg1.startswith('IPython/'):
390 if arg1.endswith('.py'):
391 arg1 = arg1[:-3]
392 sys.argv[1] = arg1.replace('/', '.')
393
394 arg1 = sys.argv[1]
389 if arg1 in test_sections:
395 if arg1 in test_sections:
390 section = test_sections[arg1]
396 section = test_sections[arg1]
391 sys.argv[1:2] = section.includes
397 sys.argv[1:2] = section.includes
General Comments 0
You need to be logged in to leave comments. Login now