##// END OF EJS Templates
Lots of work on exception handling, including tests for traceback printing....
Fernando Perez -
Show More
@@ -0,0 +1,32 b''
1 """Error script. DO NOT EDIT FURTHER! It will break exception doctests!!!"""
2 import sys
3
4 def div0():
5 "foo"
6 x = 1
7 y = 0
8 x/y
9
10 def sysexit(stat, mode):
11 raise SystemExit(stat, 'Mode = %s' % mode)
12
13 def bar(mode):
14 "bar"
15 if mode=='div':
16 div0()
17 elif mode=='exit':
18 try:
19 stat = int(sys.argv[2])
20 except:
21 stat = 1
22 sysexit(stat, mode)
23 else:
24 raise ValueError('Unknown mode')
25
26 if __name__ == '__main__':
27 try:
28 mode = sys.argv[1]
29 except IndexError:
30 mode = 'div'
31
32 bar(mode)
@@ -1243,7 +1243,8 b' class InteractiveShell(Component, Magic):'
1243 """
1243 """
1244 self.showtraceback((etype,value,tb),tb_offset=0)
1244 self.showtraceback((etype,value,tb),tb_offset=0)
1245
1245
1246 def showtraceback(self,exc_tuple = None,filename=None,tb_offset=None):
1246 def showtraceback(self,exc_tuple = None,filename=None,tb_offset=None,
1247 exception_only=False):
1247 """Display the exception that just occurred.
1248 """Display the exception that just occurred.
1248
1249
1249 If nothing is known about the exception, this is the method which
1250 If nothing is known about the exception, this is the method which
@@ -1255,17 +1256,23 b' class InteractiveShell(Component, Magic):'
1255 SyntaxError exception, don't try to analyze the stack manually and
1256 SyntaxError exception, don't try to analyze the stack manually and
1256 simply call this method."""
1257 simply call this method."""
1257
1258
1258
1259 # Though this won't be called by syntax errors in the input line,
1260 # there may be SyntaxError cases whith imported code.
1261
1262 try:
1259 try:
1263 if exc_tuple is None:
1260 if exc_tuple is None:
1264 etype, value, tb = sys.exc_info()
1261 etype, value, tb = sys.exc_info()
1265 else:
1262 else:
1266 etype, value, tb = exc_tuple
1263 etype, value, tb = exc_tuple
1267
1264
1265 if etype is None:
1266 if hasattr(sys, 'last_type'):
1267 etype, value, tb = sys.last_type, sys.last_value, \
1268 sys.last_traceback
1269 else:
1270 self.write('No traceback available to show.\n')
1271 return
1272
1268 if etype is SyntaxError:
1273 if etype is SyntaxError:
1274 # Though this won't be called by syntax errors in the input
1275 # line, there may be SyntaxError cases whith imported code.
1269 self.showsyntaxerror(filename)
1276 self.showsyntaxerror(filename)
1270 elif etype is UsageError:
1277 elif etype is UsageError:
1271 print "UsageError:", value
1278 print "UsageError:", value
@@ -1281,13 +1288,21 b' class InteractiveShell(Component, Magic):'
1281 if etype in self.custom_exceptions:
1288 if etype in self.custom_exceptions:
1282 self.CustomTB(etype,value,tb)
1289 self.CustomTB(etype,value,tb)
1283 else:
1290 else:
1291 if exception_only:
1292 m = ('An exception has occurred, use %tb to see the '
1293 'full traceback.')
1294 print m
1295 self.InteractiveTB.show_exception_only(etype, value)
1296 else:
1284 self.InteractiveTB(etype,value,tb,tb_offset=tb_offset)
1297 self.InteractiveTB(etype,value,tb,tb_offset=tb_offset)
1285 if self.InteractiveTB.call_pdb:
1298 if self.InteractiveTB.call_pdb:
1286 # pdb mucks up readline, fix it back
1299 # pdb mucks up readline, fix it back
1287 self.set_completer()
1300 self.set_completer()
1301
1288 except KeyboardInterrupt:
1302 except KeyboardInterrupt:
1289 self.write("\nKeyboardInterrupt\n")
1303 self.write("\nKeyboardInterrupt\n")
1290
1304
1305
1291 def showsyntaxerror(self, filename=None):
1306 def showsyntaxerror(self, filename=None):
1292 """Display the syntax error that just occurred.
1307 """Display the syntax error that just occurred.
1293
1308
@@ -1299,7 +1314,7 b' class InteractiveShell(Component, Magic):'
1299 """
1314 """
1300 etype, value, last_traceback = sys.exc_info()
1315 etype, value, last_traceback = sys.exc_info()
1301
1316
1302 # See note about these variables in showtraceback() below
1317 # See note about these variables in showtraceback() above
1303 sys.last_type = etype
1318 sys.last_type = etype
1304 sys.last_value = value
1319 sys.last_value = value
1305 sys.last_traceback = last_traceback
1320 sys.last_traceback = last_traceback
@@ -1865,6 +1880,9 b' class InteractiveShell(Component, Magic):'
1865 # We are off again...
1880 # We are off again...
1866 __builtin__.__dict__['__IPYTHON__active'] -= 1
1881 __builtin__.__dict__['__IPYTHON__active'] -= 1
1867
1882
1883 # Turn off the exit flag, so the mainloop can be restarted if desired
1884 self.exit_now = False
1885
1868 def safe_execfile(self, fname, *where, **kw):
1886 def safe_execfile(self, fname, *where, **kw):
1869 """A safe version of the builtin execfile().
1887 """A safe version of the builtin execfile().
1870
1888
@@ -1880,7 +1898,8 b' class InteractiveShell(Component, Magic):'
1880 One or two namespaces, passed to execfile() as (globals,locals).
1898 One or two namespaces, passed to execfile() as (globals,locals).
1881 If only one is given, it is passed as both.
1899 If only one is given, it is passed as both.
1882 exit_ignore : bool (False)
1900 exit_ignore : bool (False)
1883 If True, then don't print errors for non-zero exit statuses.
1901 If True, then silence SystemExit for non-zero status (it is always
1902 silenced for zero status, as it is so common).
1884 """
1903 """
1885 kw.setdefault('exit_ignore', False)
1904 kw.setdefault('exit_ignore', False)
1886
1905
@@ -1905,40 +1924,21 b' class InteractiveShell(Component, Magic):'
1905
1924
1906 with prepended_to_syspath(dname):
1925 with prepended_to_syspath(dname):
1907 try:
1926 try:
1908 if sys.platform == 'win32' and sys.version_info < (2,5,1):
1909 # Work around a bug in Python for Windows. The bug was
1910 # fixed in in Python 2.5 r54159 and 54158, but that's still
1911 # SVN Python as of March/07. For details, see:
1912 # http://projects.scipy.org/ipython/ipython/ticket/123
1913 try:
1914 globs,locs = where[0:2]
1915 except:
1916 try:
1917 globs = locs = where[0]
1918 except:
1919 globs = locs = globals()
1920 exec file(fname) in globs,locs
1921 else:
1922 execfile(fname,*where)
1927 execfile(fname,*where)
1923 except SyntaxError:
1924 self.showsyntaxerror()
1925 warn('Failure executing file: <%s>' % fname)
1926 except SystemExit, status:
1928 except SystemExit, status:
1927 # Code that correctly sets the exit status flag to success (0)
1929 # If the call was made with 0 or None exit status (sys.exit(0)
1928 # shouldn't be bothered with a traceback. Note that a plain
1930 # or sys.exit() ), don't bother showing a traceback, as both of
1929 # sys.exit() does NOT set the message to 0 (it's empty) so that
1931 # these are considered normal by the OS:
1930 # will still get a traceback. Note that the structure of the
1932 # > python -c'import sys;sys.exit(0)'; echo $?
1931 # SystemExit exception changed between Python 2.4 and 2.5, so
1933 # 0
1932 # the checks must be done in a version-dependent way.
1934 # > python -c'import sys;sys.exit()'; echo $?
1933 show = False
1935 # 0
1934 if status.args[0]==0 and not kw['exit_ignore']:
1936 # For other exit status, we show the exception unless
1935 show = True
1937 # explicitly silenced, but only in short form.
1936 if show:
1938 if status.code not in (0, None) and not kw['exit_ignore']:
1937 self.showtraceback()
1939 self.showtraceback(exception_only=True)
1938 warn('Failure executing file: <%s>' % fname)
1939 except:
1940 except:
1940 self.showtraceback()
1941 self.showtraceback()
1941 warn('Failure executing file: <%s>' % fname)
1942
1942
1943 def safe_execfile_ipy(self, fname):
1943 def safe_execfile_ipy(self, fname):
1944 """Like safe_execfile, but for .ipy files with IPython syntax.
1944 """Like safe_execfile, but for .ipy files with IPython syntax.
@@ -2152,9 +2152,8 b' class InteractiveShell(Component, Magic):'
2152 sys.excepthook = old_excepthook
2152 sys.excepthook = old_excepthook
2153 except SystemExit:
2153 except SystemExit:
2154 self.resetbuffer()
2154 self.resetbuffer()
2155 self.showtraceback()
2155 self.showtraceback(exception_only=True)
2156 warn("Type %exit or %quit to exit IPython "
2156 warn("To exit: use any of 'exit', 'quit', %Exit or Ctrl-D.", level=1)
2157 "(%Exit or %Quit do so unconditionally).",level=1)
2158 except self.custom_exceptions:
2157 except self.custom_exceptions:
2159 etype,value,tb = sys.exc_info()
2158 etype,value,tb = sys.exc_info()
2160 self.CustomTB(etype,value,tb)
2159 self.CustomTB(etype,value,tb)
@@ -3603,4 +3603,10 b' Defaulting color scheme to \'NoColor\'"""'
3603 """
3603 """
3604 self.shell.enable_pylab(s)
3604 self.shell.enable_pylab(s)
3605
3605
3606 def magic_tb(self, s):
3607 """Print the last traceback with the currently active exception mode.
3608
3609 See %xmode for changing exception reporting modes."""
3610 self.shell.showtraceback()
3611
3606 # end Magic
3612 # end Magic
@@ -13,31 +13,15 b' import tempfile'
13 import nose.tools as nt
13 import nose.tools as nt
14
14
15 # our own packages
15 # our own packages
16 from IPython.core import iplib
17 from IPython.core import ipapi
18 from IPython.testing import decorators as dec
16 from IPython.testing import decorators as dec
17 from IPython.testing.globalipapp import get_ipython
19
18
20 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
21 # Globals
20 # Globals
22 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
23
22
24 # Useful global ipapi object and main IPython one. Unfortunately we have a
23 # Get the public instance of IPython
25 # long precedent of carrying the 'ipapi' global object which is injected into
24 ip = get_ipython()
26 # the system namespace as _ip, but that keeps a pointer to the actual IPython
27 # InteractiveShell instance, which is named IP. Since in testing we do need
28 # access to the real thing (we want to probe beyond what ipapi exposes), make
29 # here a global reference to each. In general, things that are exposed by the
30 # ipapi instance should be read from there, but we also will often need to use
31 # the actual IPython one.
32
33 # Get the public instance of IPython, and if it's None, make one so we can use
34 # it for testing
35 ip = ipapi.get()
36 if ip is None:
37 # IPython not running yet, make one from the testing machinery for
38 # consistency when the test suite is being run via iptest
39 from IPython.testing.plugin import ipdoctest
40 ip = ipapi.get()
41
25
42 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
43 # Test functions
27 # Test functions
@@ -73,3 +57,189 b' def test_reset():'
73 nvars_expected = 0
57 nvars_expected = 0
74
58
75 yield nt.assert_equals(len(ns), nvars_expected)
59 yield nt.assert_equals(len(ns), nvars_expected)
60
61
62 # Tests for reporting of exceptions in various modes, handling of SystemExit,
63 # and %tb functionality. This is really a mix of testing ultraTB and iplib.
64
65 def doctest_tb_plain():
66 """
67 In [18]: xmode plain
68 Exception reporting mode: Plain
69
70 In [19]: run simpleerr.py
71 Traceback (most recent call last):
72 ...line 32, in <module>
73 bar(mode)
74 ...line 16, in bar
75 div0()
76 ...line 8, in div0
77 x/y
78 ZeroDivisionError: integer division or modulo by zero
79 """
80
81
82 def doctest_tb_context():
83 """
84 In [3]: xmode context
85 Exception reporting mode: Context
86
87 In [4]: run simpleerr.py
88 ---------------------------------------------------------------------------
89 ZeroDivisionError Traceback (most recent call last)
90 <BLANKLINE>
91 ... in <module>()
92 30 mode = 'div'
93 31
94 ---> 32 bar(mode)
95 33
96 34
97 <BLANKLINE>
98 ... in bar(mode)
99 14 "bar"
100 15 if mode=='div':
101 ---> 16 div0()
102 17 elif mode=='exit':
103 18 try:
104 <BLANKLINE>
105 ... in div0()
106 6 x = 1
107 7 y = 0
108 ----> 8 x/y
109 9
110 10 def sysexit(stat, mode):
111 <BLANKLINE>
112 ZeroDivisionError: integer division or modulo by zero
113 """
114
115
116 def doctest_tb_verbose():
117 """
118 In [5]: xmode verbose
119 Exception reporting mode: Verbose
120
121 In [6]: run simpleerr.py
122 ---------------------------------------------------------------------------
123 ZeroDivisionError Traceback (most recent call last)
124 <BLANKLINE>
125 ... in <module>()
126 30 mode = 'div'
127 31
128 ---> 32 bar(mode)
129 global bar = <function bar at ...>
130 global mode = 'div'
131 33
132 34
133 <BLANKLINE>
134 ... in bar(mode='div')
135 14 "bar"
136 15 if mode=='div':
137 ---> 16 div0()
138 global div0 = <function div0 at ...>
139 17 elif mode=='exit':
140 18 try:
141 <BLANKLINE>
142 ... in div0()
143 6 x = 1
144 7 y = 0
145 ----> 8 x/y
146 x = 1
147 y = 0
148 9
149 10 def sysexit(stat, mode):
150 <BLANKLINE>
151 ZeroDivisionError: integer division or modulo by zero
152 """
153
154
155 def doctest_tb_sysexit():
156 """
157 In [17]: %xmode plain
158 Exception reporting mode: Plain
159
160 In [18]: %run simpleerr.py exit
161 An exception has occurred, use %tb to see the full traceback.
162 SystemExit: (1, 'Mode = exit')
163
164 In [19]: %run simpleerr.py exit 2
165 An exception has occurred, use %tb to see the full traceback.
166 SystemExit: (2, 'Mode = exit')
167
168 In [20]: %tb
169 Traceback (most recent call last):
170 File ... in <module>
171 bar(mode)
172 File ... line 22, in bar
173 sysexit(stat, mode)
174 File ... line 11, in sysexit
175 raise SystemExit(stat, 'Mode = %s' % mode)
176 SystemExit: (2, 'Mode = exit')
177
178 In [21]: %xmode context
179 Exception reporting mode: Context
180
181 In [22]: %tb
182 ---------------------------------------------------------------------------
183 SystemExit Traceback (most recent call last)
184 <BLANKLINE>
185 ...<module>()
186 30 mode = 'div'
187 31
188 ---> 32 bar(mode)
189 33
190 34
191 <BLANKLINE>
192 ...bar(mode)
193 20 except:
194 21 stat = 1
195 ---> 22 sysexit(stat, mode)
196 23 else:
197 24 raise ValueError('Unknown mode')
198 <BLANKLINE>
199 ...sysexit(stat, mode)
200 9
201 10 def sysexit(stat, mode):
202 ---> 11 raise SystemExit(stat, 'Mode = %s' % mode)
203 12
204 13 def bar(mode):
205 <BLANKLINE>
206 SystemExit: (2, 'Mode = exit')
207
208 In [23]: %xmode verbose
209 Exception reporting mode: Verbose
210
211 In [24]: %tb
212 ---------------------------------------------------------------------------
213 SystemExit Traceback (most recent call last)
214 <BLANKLINE>
215 ... in <module>()
216 30 mode = 'div'
217 31
218 ---> 32 bar(mode)
219 global bar = <function bar at ...>
220 global mode = 'exit'
221 33
222 34
223 <BLANKLINE>
224 ... in bar(mode='exit')
225 20 except:
226 21 stat = 1
227 ---> 22 sysexit(stat, mode)
228 global sysexit = <function sysexit at ...>
229 stat = 2
230 mode = 'exit'
231 23 else:
232 24 raise ValueError('Unknown mode')
233 <BLANKLINE>
234 ... in sysexit(stat=2, mode='exit')
235 9
236 10 def sysexit(stat, mode):
237 ---> 11 raise SystemExit(stat, 'Mode = %s' % mode)
238 global SystemExit = undefined
239 stat = 2
240 mode = 'exit'
241 12
242 13 def bar(mode):
243 <BLANKLINE>
244 SystemExit: (2, 'Mode = exit')
245 """
@@ -312,6 +312,11 b' def _format_traceback_lines(lnum, index, lines, Colors, lvals=None,scheme=None):'
312 # Module classes
312 # Module classes
313 class TBTools:
313 class TBTools:
314 """Basic tools used by all traceback printer classes."""
314 """Basic tools used by all traceback printer classes."""
315 #: Default output stream, can be overridden at call time. A special value
316 #: of 'stdout' *as a string* can be given to force extraction of sys.stdout
317 #: at runtime. This allows testing exception printing with doctests, that
318 #: swap sys.stdout just at execution time.
319 out_stream = sys.stderr
315
320
316 def __init__(self,color_scheme = 'NoColor',call_pdb=False):
321 def __init__(self,color_scheme = 'NoColor',call_pdb=False):
317 # Whether to call the interactive pdb debugger after printing
322 # Whether to call the interactive pdb debugger after printing
@@ -380,12 +385,28 b' class ListTB(TBTools):'
380 Term.cerr.flush()
385 Term.cerr.flush()
381
386
382 def text(self,etype, value, elist,context=5):
387 def text(self, etype, value, elist, context=5):
383 """Return a color formatted string with the traceback info."""
388 """Return a color formatted string with the traceback info.
389
390 Parameters
391 ----------
392 etype : exception type
393 Type of the exception raised.
394
395 value : object
396 Data stored in the exception
397
398 elist : list
399 List of frames, see class docstring for details.
400
401 Returns
402 -------
403 String with formatted exception.
404 """
384
405
385 Colors = self.Colors
406 Colors = self.Colors
386 out_string = ['%s%s%s\n' % (Colors.topline,'-'*60,Colors.Normal)]
407 out_string = []
387 if elist:
408 if elist:
388 out_string.append('Traceback %s(most recent call last)%s:' % \
409 out_string.append('Traceback %s(most recent call last)%s:' %
389 (Colors.normalEm, Colors.Normal) + '\n')
410 (Colors.normalEm, Colors.Normal) + '\n')
390 out_string.extend(self._format_list(elist))
411 out_string.extend(self._format_list(elist))
391 lines = self._format_exception_only(etype, value)
412 lines = self._format_exception_only(etype, value)
@@ -492,15 +513,29 b' class ListTB(TBTools):'
492 else:
513 else:
493 list.append('%s\n' % str(stype))
514 list.append('%s\n' % str(stype))
494
515
495 # vds:>>
516 # sync with user hooks
496 if have_filedata:
517 if have_filedata:
497 ipinst = ipapi.get()
518 ipinst = ipapi.get()
498 if ipinst is not None:
519 if ipinst is not None:
499 ipinst.hooks.synchronize_with_editor(filename, lineno, 0)
520 ipinst.hooks.synchronize_with_editor(filename, lineno, 0)
500 # vds:<<
501
521
502 return list
522 return list
503
523
524 def show_exception_only(self, etype, value):
525 """Only print the exception type and message, without a traceback.
526
527 Parameters
528 ----------
529 etype : exception type
530 value : exception value
531 """
532 # This method needs to use __call__ from *this* class, not the one from
533 # a subclass whose signature or behavior may be different
534 Term.cout.flush()
535 ostream = sys.stdout if self.out_stream == 'stdout' else Term.cerr
536 print >> ostream, ListTB.text(self, etype, value, []),
537 ostream.flush()
538
504 def _some_str(self, value):
539 def _some_str(self, value):
505 # Lifted from traceback.py
540 # Lifted from traceback.py
506 try:
541 try:
@@ -980,6 +1015,7 b' class AutoFormattedTB(FormattedTB):'
980 except:
1015 except:
981 AutoTB() # or AutoTB(out=logfile) where logfile is an open file object
1016 AutoTB() # or AutoTB(out=logfile) where logfile is an open file object
982 """
1017 """
1018
983 def __call__(self,etype=None,evalue=None,etb=None,
1019 def __call__(self,etype=None,evalue=None,etb=None,
984 out=None,tb_offset=None):
1020 out=None,tb_offset=None):
985 """Print out a formatted exception traceback.
1021 """Print out a formatted exception traceback.
@@ -992,7 +1028,7 b' class AutoFormattedTB(FormattedTB):'
992 given at initialization time. """
1028 given at initialization time. """
993
1029
994 if out is None:
1030 if out is None:
995 out = Term.cerr
1031 out = sys.stdout if self.out_stream=='stdout' else self.out_stream
996 Term.cout.flush()
1032 Term.cout.flush()
997 if tb_offset is not None:
1033 if tb_offset is not None:
998 tb_offset, self.tb_offset = self.tb_offset, tb_offset
1034 tb_offset, self.tb_offset = self.tb_offset, tb_offset
@@ -20,6 +20,7 b' import os'
20 import sys
20 import sys
21
21
22 from . import tools
22 from . import tools
23 from IPython.utils.genutils import Term
23
24
24 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
25 # Functions
26 # Functions
@@ -96,7 +97,7 b' class ipnsdict(dict):'
96
97
97 def get_ipython():
98 def get_ipython():
98 # This will get replaced by the real thing once we start IPython below
99 # This will get replaced by the real thing once we start IPython below
99 return None
100 return start_ipython()
100
101
101 def start_ipython():
102 def start_ipython():
102 """Start a global IPython shell, which we need for IPython-specific syntax.
103 """Start a global IPython shell, which we need for IPython-specific syntax.
@@ -133,6 +134,10 b' def start_ipython():'
133 ip = ipapp.IPythonApp(argv, user_ns=user_ns, user_global_ns=global_ns)
134 ip = ipapp.IPythonApp(argv, user_ns=user_ns, user_global_ns=global_ns)
134 ip.initialize()
135 ip.initialize()
135 ip.shell.builtin_trap.set()
136 ip.shell.builtin_trap.set()
137 # Set stderr to stdout so nose can doctest exceptions
138 ## Term.cerr = sys.stdout
139 ## sys.stderr = sys.stdout
140 ip.shell.InteractiveTB.out_stream = 'stdout'
136 # Butcher the logger
141 # Butcher the logger
137 ip.shell.log = lambda *a,**k: None
142 ip.shell.log = lambda *a,**k: None
138
143
@@ -143,8 +148,8 b' def start_ipython():'
143 sys.excepthook = _excepthook
148 sys.excepthook = _excepthook
144
149
145 # So that ipython magics and aliases can be doctested (they work by making
150 # So that ipython magics and aliases can be doctested (they work by making
146 # a call into a global _ip object)
151 # a call into a global _ip object). Also make the top-level get_ipython
147
152 # now return this without calling here again
148 _ip = ip.shell
153 _ip = ip.shell
149 get_ipython = _ip.get_ipython
154 get_ipython = _ip.get_ipython
150 __builtin__._ip = _ip
155 __builtin__._ip = _ip
@@ -155,11 +160,6 b' def start_ipython():'
155 # doctest machinery would miss them.
160 # doctest machinery would miss them.
156 ip.shell.system = xsys
161 ip.shell.system = xsys
157
162
158 # Also patch our %run function in.
159 ## im = new.instancemethod(_run_ns_sync,_ip, _ip.__class__)
160 ## ip.shell.magic_run_ori = _ip.magic_run
161 ## ip.shell.magic_run = im
162
163 # XXX - For some very bizarre reason, the loading of %history by default is
163 # XXX - For some very bizarre reason, the loading of %history by default is
164 # failing. This needs to be fixed later, but for now at least this ensures
164 # failing. This needs to be fixed later, but for now at least this ensures
165 # that tests that use %hist run to completion.
165 # that tests that use %hist run to completion.
@@ -167,3 +167,5 b' def start_ipython():'
167 history.init_ipython(ip.shell)
167 history.init_ipython(ip.shell)
168 if not hasattr(ip.shell,'magic_history'):
168 if not hasattr(ip.shell,'magic_history'):
169 raise RuntimeError("Can't load magics, aborting")
169 raise RuntimeError("Can't load magics, aborting")
170
171 return _ip
General Comments 0
You need to be logged in to leave comments. Login now