##// END OF EJS Templates
Improve history API docs
Thomas Kluyver -
Show More
@@ -1,849 +1,854 b''
1 1 """ History related magics and functionality """
2 2 #-----------------------------------------------------------------------------
3 3 # Copyright (C) 2010-2011 The IPython Development Team.
4 4 #
5 5 # Distributed under the terms of the BSD License.
6 6 #
7 7 # The full license is in the file COPYING.txt, distributed with this software.
8 8 #-----------------------------------------------------------------------------
9 9
10 10 #-----------------------------------------------------------------------------
11 11 # Imports
12 12 #-----------------------------------------------------------------------------
13 13 from __future__ import print_function
14 14
15 15 # Stdlib imports
16 16 import atexit
17 17 import datetime
18 18 import os
19 19 import re
20 20 try:
21 21 import sqlite3
22 22 except ImportError:
23 23 try:
24 24 from pysqlite2 import dbapi2 as sqlite3
25 25 except ImportError:
26 26 sqlite3 = None
27 27 import threading
28 28
29 29 # Our own packages
30 30 from IPython.config.configurable import Configurable
31 31 from IPython.external.decorator import decorator
32 from IPython.utils.decorators import undoc
32 33 from IPython.utils.path import locate_profile
33 34 from IPython.utils import py3compat
34 35 from IPython.utils.traitlets import (
35 36 Any, Bool, Dict, Instance, Integer, List, Unicode, TraitError,
36 37 )
37 38 from IPython.utils.warn import warn
38 39
39 40 #-----------------------------------------------------------------------------
40 41 # Classes and functions
41 42 #-----------------------------------------------------------------------------
42 43
44 @undoc
43 45 class DummyDB(object):
44 46 """Dummy DB that will act as a black hole for history.
45 47
46 48 Only used in the absence of sqlite"""
47 49 def execute(*args, **kwargs):
48 50 return []
49 51
50 52 def commit(self, *args, **kwargs):
51 53 pass
52 54
53 55 def __enter__(self, *args, **kwargs):
54 56 pass
55 57
56 58 def __exit__(self, *args, **kwargs):
57 59 pass
58 60
59 61
60 62 @decorator
61 63 def needs_sqlite(f, self, *a, **kw):
62 """return an empty list in the absence of sqlite"""
64 """Decorator: return an empty list in the absence of sqlite."""
63 65 if sqlite3 is None or not self.enabled:
64 66 return []
65 67 else:
66 68 return f(self, *a, **kw)
67 69
68 70
69 71 if sqlite3 is not None:
70 72 DatabaseError = sqlite3.DatabaseError
71 73 else:
74 @undoc
72 75 class DatabaseError(Exception):
73 76 "Dummy exception when sqlite could not be imported. Should never occur."
74 77
75 78 @decorator
76 79 def catch_corrupt_db(f, self, *a, **kw):
77 80 """A decorator which wraps HistoryAccessor method calls to catch errors from
78 81 a corrupt SQLite database, move the old database out of the way, and create
79 82 a new one.
80 83 """
81 84 try:
82 85 return f(self, *a, **kw)
83 86 except DatabaseError:
84 87 if os.path.isfile(self.hist_file):
85 88 # Try to move the file out of the way
86 89 base,ext = os.path.splitext(self.hist_file)
87 90 newpath = base + '-corrupt' + ext
88 91 os.rename(self.hist_file, newpath)
89 92 self.init_db()
90 93 print("ERROR! History file wasn't a valid SQLite database.",
91 94 "It was moved to %s" % newpath, "and a new file created.")
92 95 return []
93 96
94 97 else:
95 98 # The hist_file is probably :memory: or something else.
96 99 raise
97 100
98 101
99 102
100 103 class HistoryAccessor(Configurable):
101 104 """Access the history database without adding to it.
102 105
103 106 This is intended for use by standalone history tools. IPython shells use
104 107 HistoryManager, below, which is a subclass of this."""
105 108
106 109 # String holding the path to the history file
107 110 hist_file = Unicode(config=True,
108 111 help="""Path to file to use for SQLite history database.
109 112
110 113 By default, IPython will put the history database in the IPython
111 114 profile directory. If you would rather share one history among
112 115 profiles, you can set this value in each, so that they are consistent.
113 116
114 117 Due to an issue with fcntl, SQLite is known to misbehave on some NFS
115 118 mounts. If you see IPython hanging, try setting this to something on a
116 119 local disk, e.g::
117 120
118 121 ipython --HistoryManager.hist_file=/tmp/ipython_hist.sqlite
119 122
120 123 """)
121 124
122 125 enabled = Bool(True, config=True,
123 126 help="""enable the SQLite history
124 127
125 128 set enabled=False to disable the SQLite history,
126 129 in which case there will be no stored history, no SQLite connection,
127 130 and no background saving thread. This may be necessary in some
128 131 threaded environments where IPython is embedded.
129 132 """
130 133 )
131 134
132 135 connection_options = Dict(config=True,
133 136 help="""Options for configuring the SQLite connection
134 137
135 138 These options are passed as keyword args to sqlite3.connect
136 139 when establishing database conenctions.
137 140 """
138 141 )
139 142
140 143 # The SQLite database
141 144 db = Any()
142 145 def _db_changed(self, name, old, new):
143 146 """validate the db, since it can be an Instance of two different types"""
144 147 connection_types = (DummyDB,)
145 148 if sqlite3 is not None:
146 149 connection_types = (DummyDB, sqlite3.Connection)
147 150 if not isinstance(new, connection_types):
148 151 msg = "%s.db must be sqlite3 Connection or DummyDB, not %r" % \
149 152 (self.__class__.__name__, new)
150 153 raise TraitError(msg)
151 154
152 155 def __init__(self, profile='default', hist_file=u'', **traits):
153 156 """Create a new history accessor.
154 157
155 158 Parameters
156 159 ----------
157 160 profile : str
158 161 The name of the profile from which to open history.
159 162 hist_file : str
160 163 Path to an SQLite history database stored by IPython. If specified,
161 164 hist_file overrides profile.
162 config :
165 config : :class:`~IPython.config.loader.Config`
163 166 Config object. hist_file can also be set through this.
164 167 """
165 168 # We need a pointer back to the shell for various tasks.
166 169 super(HistoryAccessor, self).__init__(**traits)
167 170 # defer setting hist_file from kwarg until after init,
168 171 # otherwise the default kwarg value would clobber any value
169 172 # set by config
170 173 if hist_file:
171 174 self.hist_file = hist_file
172 175
173 176 if self.hist_file == u'':
174 177 # No one has set the hist_file, yet.
175 178 self.hist_file = self._get_hist_file_name(profile)
176 179
177 180 if sqlite3 is None and self.enabled:
178 181 warn("IPython History requires SQLite, your history will not be saved")
179 182 self.enabled = False
180 183
181 184 self.init_db()
182 185
183 186 def _get_hist_file_name(self, profile='default'):
184 187 """Find the history file for the given profile name.
185 188
186 189 This is overridden by the HistoryManager subclass, to use the shell's
187 190 active profile.
188 191
189 192 Parameters
190 193 ----------
191 194 profile : str
192 195 The name of a profile which has a history file.
193 196 """
194 197 return os.path.join(locate_profile(profile), 'history.sqlite')
195 198
196 199 @catch_corrupt_db
197 200 def init_db(self):
198 201 """Connect to the database, and create tables if necessary."""
199 202 if not self.enabled:
200 203 self.db = DummyDB()
201 204 return
202 205
203 206 # use detect_types so that timestamps return datetime objects
204 207 kwargs = dict(detect_types=sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES)
205 208 kwargs.update(self.connection_options)
206 209 self.db = sqlite3.connect(self.hist_file, **kwargs)
207 210 self.db.execute("""CREATE TABLE IF NOT EXISTS sessions (session integer
208 211 primary key autoincrement, start timestamp,
209 212 end timestamp, num_cmds integer, remark text)""")
210 213 self.db.execute("""CREATE TABLE IF NOT EXISTS history
211 214 (session integer, line integer, source text, source_raw text,
212 215 PRIMARY KEY (session, line))""")
213 216 # Output history is optional, but ensure the table's there so it can be
214 217 # enabled later.
215 218 self.db.execute("""CREATE TABLE IF NOT EXISTS output_history
216 219 (session integer, line integer, output text,
217 220 PRIMARY KEY (session, line))""")
218 221 self.db.commit()
219 222
220 223 def writeout_cache(self):
221 224 """Overridden by HistoryManager to dump the cache before certain
222 225 database lookups."""
223 226 pass
224 227
225 228 ## -------------------------------
226 229 ## Methods for retrieving history:
227 230 ## -------------------------------
228 231 def _run_sql(self, sql, params, raw=True, output=False):
229 232 """Prepares and runs an SQL query for the history database.
230 233
231 234 Parameters
232 235 ----------
233 236 sql : str
234 237 Any filtering expressions to go after SELECT ... FROM ...
235 238 params : tuple
236 239 Parameters passed to the SQL query (to replace "?")
237 240 raw, output : bool
238 241 See :meth:`get_range`
239 242
240 243 Returns
241 244 -------
242 245 Tuples as :meth:`get_range`
243 246 """
244 247 toget = 'source_raw' if raw else 'source'
245 248 sqlfrom = "history"
246 249 if output:
247 250 sqlfrom = "history LEFT JOIN output_history USING (session, line)"
248 251 toget = "history.%s, output_history.output" % toget
249 252 cur = self.db.execute("SELECT session, line, %s FROM %s " %\
250 253 (toget, sqlfrom) + sql, params)
251 254 if output: # Regroup into 3-tuples, and parse JSON
252 255 return ((ses, lin, (inp, out)) for ses, lin, inp, out in cur)
253 256 return cur
254 257
255 258 @needs_sqlite
256 259 @catch_corrupt_db
257 def get_session_info(self, session=0):
258 """get info about a session
260 def get_session_info(self, session):
261 """Get info about a session.
259 262
260 263 Parameters
261 264 ----------
262 265
263 266 session : int
264 267 Session number to retrieve.
265 268
266 269 Returns
267 270 -------
268 271
269 272 session_id : int
270 273 Session ID number
271 start : datetime
274 start : datetime
272 275 Timestamp for the start of the session.
273 end : datetime
276 end : datetime
274 277 Timestamp for the end of the session, or None if IPython crashed.
275 num_cmds : int
278 num_cmds : int
276 279 Number of commands run, or None if IPython crashed.
277 remark : unicode
280 remark : unicode
278 281 A manually set description.
279 282 """
280 283 query = "SELECT * from sessions where session == ?"
281 284 return self.db.execute(query, (session,)).fetchone()
282 285
283 286 @catch_corrupt_db
284 287 def get_last_session_id(self):
285 288 """Get the last session ID currently in the database.
286 289
287 290 Within IPython, this should be the same as the value stored in
288 291 :attr:`HistoryManager.session_number`.
289 292 """
290 293 for record in self.get_tail(n=1, include_latest=True):
291 294 return record[0]
292 295
293 296 @catch_corrupt_db
294 297 def get_tail(self, n=10, raw=True, output=False, include_latest=False):
295 298 """Get the last n lines from the history database.
296 299
297 300 Parameters
298 301 ----------
299 302 n : int
300 303 The number of lines to get
301 304 raw, output : bool
302 305 See :meth:`get_range`
303 306 include_latest : bool
304 307 If False (default), n+1 lines are fetched, and the latest one
305 308 is discarded. This is intended to be used where the function
306 309 is called by a user command, which it should not return.
307 310
308 311 Returns
309 312 -------
310 313 Tuples as :meth:`get_range`
311 314 """
312 315 self.writeout_cache()
313 316 if not include_latest:
314 317 n += 1
315 318 cur = self._run_sql("ORDER BY session DESC, line DESC LIMIT ?",
316 319 (n,), raw=raw, output=output)
317 320 if not include_latest:
318 321 return reversed(list(cur)[1:])
319 322 return reversed(list(cur))
320 323
321 324 @catch_corrupt_db
322 325 def search(self, pattern="*", raw=True, search_raw=True,
323 326 output=False, n=None, unique=False):
324 327 """Search the database using unix glob-style matching (wildcards
325 328 * and ?).
326 329
327 330 Parameters
328 331 ----------
329 332 pattern : str
330 333 The wildcarded pattern to match when searching
331 334 search_raw : bool
332 335 If True, search the raw input, otherwise, the parsed input
333 336 raw, output : bool
334 337 See :meth:`get_range`
335 338 n : None or int
336 339 If an integer is given, it defines the limit of
337 340 returned entries.
338 341 unique : bool
339 342 When it is true, return only unique entries.
340 343
341 344 Returns
342 345 -------
343 346 Tuples as :meth:`get_range`
344 347 """
345 348 tosearch = "source_raw" if search_raw else "source"
346 349 if output:
347 350 tosearch = "history." + tosearch
348 351 self.writeout_cache()
349 352 sqlform = "WHERE %s GLOB ?" % tosearch
350 353 params = (pattern,)
351 354 if unique:
352 355 sqlform += ' GROUP BY {0}'.format(tosearch)
353 356 if n is not None:
354 357 sqlform += " ORDER BY session DESC, line DESC LIMIT ?"
355 358 params += (n,)
356 359 elif unique:
357 360 sqlform += " ORDER BY session, line"
358 361 cur = self._run_sql(sqlform, params, raw=raw, output=output)
359 362 if n is not None:
360 363 return reversed(list(cur))
361 364 return cur
362 365
363 366 @catch_corrupt_db
364 367 def get_range(self, session, start=1, stop=None, raw=True,output=False):
365 368 """Retrieve input by session.
366 369
367 370 Parameters
368 371 ----------
369 372 session : int
370 373 Session number to retrieve.
371 374 start : int
372 375 First line to retrieve.
373 376 stop : int
374 377 End of line range (excluded from output itself). If None, retrieve
375 378 to the end of the session.
376 379 raw : bool
377 380 If True, return untranslated input
378 381 output : bool
379 382 If True, attempt to include output. This will be 'real' Python
380 383 objects for the current session, or text reprs from previous
381 384 sessions if db_log_output was enabled at the time. Where no output
382 385 is found, None is used.
383 386
384 387 Returns
385 388 -------
386 An iterator over the desired lines. Each line is a 3-tuple, either
387 (session, line, input) if output is False, or
388 (session, line, (input, output)) if output is True.
389 entries
390 An iterator over the desired lines. Each line is a 3-tuple, either
391 (session, line, input) if output is False, or
392 (session, line, (input, output)) if output is True.
389 393 """
390 394 if stop:
391 395 lineclause = "line >= ? AND line < ?"
392 396 params = (session, start, stop)
393 397 else:
394 398 lineclause = "line>=?"
395 399 params = (session, start)
396 400
397 401 return self._run_sql("WHERE session==? AND %s" % lineclause,
398 402 params, raw=raw, output=output)
399 403
400 404 def get_range_by_str(self, rangestr, raw=True, output=False):
401 405 """Get lines of history from a string of ranges, as used by magic
402 406 commands %hist, %save, %macro, etc.
403 407
404 408 Parameters
405 409 ----------
406 410 rangestr : str
407 411 A string specifying ranges, e.g. "5 ~2/1-4". See
408 412 :func:`magic_history` for full details.
409 413 raw, output : bool
410 414 As :meth:`get_range`
411 415
412 416 Returns
413 417 -------
414 418 Tuples as :meth:`get_range`
415 419 """
416 420 for sess, s, e in extract_hist_ranges(rangestr):
417 421 for line in self.get_range(sess, s, e, raw=raw, output=output):
418 422 yield line
419 423
420 424
421 425 class HistoryManager(HistoryAccessor):
422 426 """A class to organize all history-related functionality in one place.
423 427 """
424 428 # Public interface
425 429
426 430 # An instance of the IPython shell we are attached to
427 431 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
428 432 # Lists to hold processed and raw history. These start with a blank entry
429 433 # so that we can index them starting from 1
430 434 input_hist_parsed = List([""])
431 435 input_hist_raw = List([""])
432 436 # A list of directories visited during session
433 437 dir_hist = List()
434 438 def _dir_hist_default(self):
435 439 try:
436 440 return [py3compat.getcwd()]
437 441 except OSError:
438 442 return []
439 443
440 444 # A dict of output history, keyed with ints from the shell's
441 445 # execution count.
442 446 output_hist = Dict()
443 447 # The text/plain repr of outputs.
444 448 output_hist_reprs = Dict()
445 449
446 450 # The number of the current session in the history database
447 451 session_number = Integer()
448 452
449 453 db_log_output = Bool(False, config=True,
450 454 help="Should the history database include output? (default: no)"
451 455 )
452 456 db_cache_size = Integer(0, config=True,
453 457 help="Write to database every x commands (higher values save disk access & power).\n"
454 458 "Values of 1 or less effectively disable caching."
455 459 )
456 460 # The input and output caches
457 461 db_input_cache = List()
458 462 db_output_cache = List()
459 463
460 464 # History saving in separate thread
461 465 save_thread = Instance('IPython.core.history.HistorySavingThread')
462 466 try: # Event is a function returning an instance of _Event...
463 467 save_flag = Instance(threading._Event)
464 468 except AttributeError: # ...until Python 3.3, when it's a class.
465 469 save_flag = Instance(threading.Event)
466 470
467 471 # Private interface
468 472 # Variables used to store the three last inputs from the user. On each new
469 473 # history update, we populate the user's namespace with these, shifted as
470 474 # necessary.
471 475 _i00 = Unicode(u'')
472 476 _i = Unicode(u'')
473 477 _ii = Unicode(u'')
474 478 _iii = Unicode(u'')
475 479
476 480 # A regex matching all forms of the exit command, so that we don't store
477 481 # them in the history (it's annoying to rewind the first entry and land on
478 482 # an exit call).
479 483 _exit_re = re.compile(r"(exit|quit)(\s*\(.*\))?$")
480 484
481 485 def __init__(self, shell=None, config=None, **traits):
482 486 """Create a new history manager associated with a shell instance.
483 487 """
484 488 # We need a pointer back to the shell for various tasks.
485 489 super(HistoryManager, self).__init__(shell=shell, config=config,
486 490 **traits)
487 491 self.save_flag = threading.Event()
488 492 self.db_input_cache_lock = threading.Lock()
489 493 self.db_output_cache_lock = threading.Lock()
490 494 if self.enabled and self.hist_file != ':memory:':
491 495 self.save_thread = HistorySavingThread(self)
492 496 self.save_thread.start()
493 497
494 498 self.new_session()
495 499
496 500 def _get_hist_file_name(self, profile=None):
497 501 """Get default history file name based on the Shell's profile.
498 502
499 503 The profile parameter is ignored, but must exist for compatibility with
500 504 the parent class."""
501 505 profile_dir = self.shell.profile_dir.location
502 506 return os.path.join(profile_dir, 'history.sqlite')
503 507
504 508 @needs_sqlite
505 509 def new_session(self, conn=None):
506 510 """Get a new session number."""
507 511 if conn is None:
508 512 conn = self.db
509 513
510 514 with conn:
511 515 cur = conn.execute("""INSERT INTO sessions VALUES (NULL, ?, NULL,
512 516 NULL, "") """, (datetime.datetime.now(),))
513 517 self.session_number = cur.lastrowid
514 518
515 519 def end_session(self):
516 520 """Close the database session, filling in the end time and line count."""
517 521 self.writeout_cache()
518 522 with self.db:
519 523 self.db.execute("""UPDATE sessions SET end=?, num_cmds=? WHERE
520 524 session==?""", (datetime.datetime.now(),
521 525 len(self.input_hist_parsed)-1, self.session_number))
522 526 self.session_number = 0
523 527
524 528 def name_session(self, name):
525 529 """Give the current session a name in the history database."""
526 530 with self.db:
527 531 self.db.execute("UPDATE sessions SET remark=? WHERE session==?",
528 532 (name, self.session_number))
529 533
530 534 def reset(self, new_session=True):
531 535 """Clear the session history, releasing all object references, and
532 536 optionally open a new session."""
533 537 self.output_hist.clear()
534 538 # The directory history can't be completely empty
535 539 self.dir_hist[:] = [py3compat.getcwd()]
536 540
537 541 if new_session:
538 542 if self.session_number:
539 543 self.end_session()
540 544 self.input_hist_parsed[:] = [""]
541 545 self.input_hist_raw[:] = [""]
542 546 self.new_session()
543 547
544 548 # ------------------------------
545 549 # Methods for retrieving history
546 550 # ------------------------------
547 551 def get_session_info(self, session=0):
548 """get info about a session
552 """Get info about a session.
549 553
550 554 Parameters
551 555 ----------
552 556
553 557 session : int
554 558 Session number to retrieve. The current session is 0, and negative
555 numbers count back from current session, so -1 is previous session.
559 numbers count back from current session, so -1 is the previous session.
556 560
557 561 Returns
558 562 -------
559 563
560 564 session_id : int
561 565 Session ID number
562 start : datetime
566 start : datetime
563 567 Timestamp for the start of the session.
564 end : datetime
568 end : datetime
565 569 Timestamp for the end of the session, or None if IPython crashed.
566 num_cmds : int
570 num_cmds : int
567 571 Number of commands run, or None if IPython crashed.
568 remark : unicode
572 remark : unicode
569 573 A manually set description.
570 574 """
571 575 if session <= 0:
572 576 session += self.session_number
573 577
574 578 return super(HistoryManager, self).get_session_info(session=session)
575 579
576 580 def _get_range_session(self, start=1, stop=None, raw=True, output=False):
577 581 """Get input and output history from the current session. Called by
578 582 get_range, and takes similar parameters."""
579 583 input_hist = self.input_hist_raw if raw else self.input_hist_parsed
580 584
581 585 n = len(input_hist)
582 586 if start < 0:
583 587 start += n
584 588 if not stop or (stop > n):
585 589 stop = n
586 590 elif stop < 0:
587 591 stop += n
588 592
589 593 for i in range(start, stop):
590 594 if output:
591 595 line = (input_hist[i], self.output_hist_reprs.get(i))
592 596 else:
593 597 line = input_hist[i]
594 598 yield (0, i, line)
595 599
596 600 def get_range(self, session=0, start=1, stop=None, raw=True,output=False):
597 601 """Retrieve input by session.
598 602
599 603 Parameters
600 604 ----------
601 605 session : int
602 606 Session number to retrieve. The current session is 0, and negative
603 607 numbers count back from current session, so -1 is previous session.
604 608 start : int
605 609 First line to retrieve.
606 610 stop : int
607 611 End of line range (excluded from output itself). If None, retrieve
608 612 to the end of the session.
609 613 raw : bool
610 614 If True, return untranslated input
611 615 output : bool
612 616 If True, attempt to include output. This will be 'real' Python
613 617 objects for the current session, or text reprs from previous
614 618 sessions if db_log_output was enabled at the time. Where no output
615 619 is found, None is used.
616 620
617 621 Returns
618 622 -------
619 An iterator over the desired lines. Each line is a 3-tuple, either
620 (session, line, input) if output is False, or
621 (session, line, (input, output)) if output is True.
623 entries
624 An iterator over the desired lines. Each line is a 3-tuple, either
625 (session, line, input) if output is False, or
626 (session, line, (input, output)) if output is True.
622 627 """
623 628 if session <= 0:
624 629 session += self.session_number
625 630 if session==self.session_number: # Current session
626 631 return self._get_range_session(start, stop, raw, output)
627 632 return super(HistoryManager, self).get_range(session, start, stop, raw,
628 633 output)
629 634
630 635 ## ----------------------------
631 636 ## Methods for storing history:
632 637 ## ----------------------------
633 638 def store_inputs(self, line_num, source, source_raw=None):
634 639 """Store source and raw input in history and create input cache
635 variables _i*.
640 variables ``_i*``.
636 641
637 642 Parameters
638 643 ----------
639 644 line_num : int
640 645 The prompt number of this input.
641 646
642 647 source : str
643 648 Python input.
644 649
645 650 source_raw : str, optional
646 651 If given, this is the raw input without any IPython transformations
647 652 applied to it. If not given, ``source`` is used.
648 653 """
649 654 if source_raw is None:
650 655 source_raw = source
651 656 source = source.rstrip('\n')
652 657 source_raw = source_raw.rstrip('\n')
653 658
654 659 # do not store exit/quit commands
655 660 if self._exit_re.match(source_raw.strip()):
656 661 return
657 662
658 663 self.input_hist_parsed.append(source)
659 664 self.input_hist_raw.append(source_raw)
660 665
661 666 with self.db_input_cache_lock:
662 667 self.db_input_cache.append((line_num, source, source_raw))
663 668 # Trigger to flush cache and write to DB.
664 669 if len(self.db_input_cache) >= self.db_cache_size:
665 670 self.save_flag.set()
666 671
667 672 # update the auto _i variables
668 673 self._iii = self._ii
669 674 self._ii = self._i
670 675 self._i = self._i00
671 676 self._i00 = source_raw
672 677
673 678 # hackish access to user namespace to create _i1,_i2... dynamically
674 679 new_i = '_i%s' % line_num
675 680 to_main = {'_i': self._i,
676 681 '_ii': self._ii,
677 682 '_iii': self._iii,
678 683 new_i : self._i00 }
679 684
680 685 if self.shell is not None:
681 686 self.shell.push(to_main, interactive=False)
682 687
683 688 def store_output(self, line_num):
684 689 """If database output logging is enabled, this saves all the
685 690 outputs from the indicated prompt number to the database. It's
686 691 called by run_cell after code has been executed.
687 692
688 693 Parameters
689 694 ----------
690 695 line_num : int
691 696 The line number from which to save outputs
692 697 """
693 698 if (not self.db_log_output) or (line_num not in self.output_hist_reprs):
694 699 return
695 700 output = self.output_hist_reprs[line_num]
696 701
697 702 with self.db_output_cache_lock:
698 703 self.db_output_cache.append((line_num, output))
699 704 if self.db_cache_size <= 1:
700 705 self.save_flag.set()
701 706
702 707 def _writeout_input_cache(self, conn):
703 708 with conn:
704 709 for line in self.db_input_cache:
705 710 conn.execute("INSERT INTO history VALUES (?, ?, ?, ?)",
706 711 (self.session_number,)+line)
707 712
708 713 def _writeout_output_cache(self, conn):
709 714 with conn:
710 715 for line in self.db_output_cache:
711 716 conn.execute("INSERT INTO output_history VALUES (?, ?, ?)",
712 717 (self.session_number,)+line)
713 718
714 719 @needs_sqlite
715 720 def writeout_cache(self, conn=None):
716 721 """Write any entries in the cache to the database."""
717 722 if conn is None:
718 723 conn = self.db
719 724
720 725 with self.db_input_cache_lock:
721 726 try:
722 727 self._writeout_input_cache(conn)
723 728 except sqlite3.IntegrityError:
724 729 self.new_session(conn)
725 730 print("ERROR! Session/line number was not unique in",
726 731 "database. History logging moved to new session",
727 732 self.session_number)
728 733 try:
729 734 # Try writing to the new session. If this fails, don't
730 735 # recurse
731 736 self._writeout_input_cache(conn)
732 737 except sqlite3.IntegrityError:
733 738 pass
734 739 finally:
735 740 self.db_input_cache = []
736 741
737 742 with self.db_output_cache_lock:
738 743 try:
739 744 self._writeout_output_cache(conn)
740 745 except sqlite3.IntegrityError:
741 746 print("!! Session/line number for output was not unique",
742 747 "in database. Output will not be stored.")
743 748 finally:
744 749 self.db_output_cache = []
745 750
746 751
747 752 class HistorySavingThread(threading.Thread):
748 753 """This thread takes care of writing history to the database, so that
749 754 the UI isn't held up while that happens.
750 755
751 756 It waits for the HistoryManager's save_flag to be set, then writes out
752 757 the history cache. The main thread is responsible for setting the flag when
753 758 the cache size reaches a defined threshold."""
754 759 daemon = True
755 760 stop_now = False
756 761 enabled = True
757 762 def __init__(self, history_manager):
758 763 super(HistorySavingThread, self).__init__(name="IPythonHistorySavingThread")
759 764 self.history_manager = history_manager
760 765 self.enabled = history_manager.enabled
761 766 atexit.register(self.stop)
762 767
763 768 @needs_sqlite
764 769 def run(self):
765 770 # We need a separate db connection per thread:
766 771 try:
767 772 self.db = sqlite3.connect(self.history_manager.hist_file,
768 773 **self.history_manager.connection_options
769 774 )
770 775 while True:
771 776 self.history_manager.save_flag.wait()
772 777 if self.stop_now:
773 778 return
774 779 self.history_manager.save_flag.clear()
775 780 self.history_manager.writeout_cache(self.db)
776 781 except Exception as e:
777 782 print(("The history saving thread hit an unexpected error (%s)."
778 783 "History will not be written to the database.") % repr(e))
779 784
780 785 def stop(self):
781 786 """This can be called from the main thread to safely stop this thread.
782 787
783 788 Note that it does not attempt to write out remaining history before
784 789 exiting. That should be done by calling the HistoryManager's
785 790 end_session method."""
786 791 self.stop_now = True
787 792 self.history_manager.save_flag.set()
788 793 self.join()
789 794
790 795
791 796 # To match, e.g. ~5/8-~2/3
792 797 range_re = re.compile(r"""
793 798 ((?P<startsess>~?\d+)/)?
794 799 (?P<start>\d+)?
795 800 ((?P<sep>[\-:])
796 801 ((?P<endsess>~?\d+)/)?
797 802 (?P<end>\d+))?
798 803 $""", re.VERBOSE)
799 804
800 805
801 806 def extract_hist_ranges(ranges_str):
802 807 """Turn a string of history ranges into 3-tuples of (session, start, stop).
803 808
804 809 Examples
805 810 --------
806 list(extract_input_ranges("~8/5-~7/4 2"))
811 >>> list(extract_input_ranges("~8/5-~7/4 2"))
807 812 [(-8, 5, None), (-7, 1, 4), (0, 2, 3)]
808 813 """
809 814 for range_str in ranges_str.split():
810 815 rmatch = range_re.match(range_str)
811 816 if not rmatch:
812 817 continue
813 818 start = rmatch.group("start")
814 819 if start:
815 820 start = int(start)
816 821 end = rmatch.group("end")
817 822 # If no end specified, get (a, a + 1)
818 823 end = int(end) if end else start + 1
819 824 else: # start not specified
820 825 if not rmatch.group('startsess'): # no startsess
821 826 continue
822 827 start = 1
823 828 end = None # provide the entire session hist
824 829
825 830 if rmatch.group("sep") == "-": # 1-3 == 1:4 --> [1, 2, 3]
826 831 end += 1
827 832 startsess = rmatch.group("startsess") or "0"
828 833 endsess = rmatch.group("endsess") or startsess
829 834 startsess = int(startsess.replace("~","-"))
830 835 endsess = int(endsess.replace("~","-"))
831 836 assert endsess >= startsess, "start session must be earlier than end session"
832 837
833 838 if endsess == startsess:
834 839 yield (startsess, start, end)
835 840 continue
836 841 # Multiple sessions in one range:
837 842 yield (startsess, start, None)
838 843 for sess in range(startsess+1, endsess):
839 844 yield (sess, 1, None)
840 845 yield (endsess, 1, end)
841 846
842 847
843 848 def _format_lineno(session, line):
844 849 """Helper function to format line numbers properly."""
845 850 if session == 0:
846 851 return str(line)
847 852 return "%s#%s" % (session, line)
848 853
849 854
General Comments 0
You need to be logged in to leave comments. Login now