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